Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 76 additions & 61 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

# Cancel superseded PR runs only. Main/post-merge uses a unique run_id group so
# in-progress and queued proof runs are never cancelled.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_COMPRESS: "1"
CCACHE_MAXSIZE: 500M

jobs:
build:
strategy:
Expand All @@ -18,60 +33,44 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install Eigen3 (Linux)
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libeigen3-dev
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libeigen3-dev ccache

- name: Install Eigen3 (macOS)
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install eigen
echo "CMAKE_PREFIX_PATH_EXTRA=$(brew --prefix eigen)" >> "$GITHUB_ENV"

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}

- name: Build
run: cmake --build build -j 4

- name: Test
run: ctest --test-dir build --output-on-failure

- name: C ABI runtime source check
run: python3 scripts/check_c_abi_runtime.py

- name: Broker-state hash coverage check
run: python3 scripts/check_broker_state_hash_coverage.py

- name: Pending-order POD mirror up-to-date check
run: python3 scripts/gen_pending_order_mirror.py --check

- name: Install PineForge
run: cmake --install build --prefix "$GITHUB_WORKSPACE/pf_install"

- name: find_package smoke build
shell: bash
env:
WORKSPACE: ${{ github.workspace }}
BUILD_TYPE: ${{ matrix.build_type }}
run: |
set -euo pipefail
EXTRA="${CMAKE_PREFIX_PATH_EXTRA:-}"
PREFIX="${WORKSPACE}/pf_install"
# Pass prefix list to CMake with ';' — ':' is for env vars on Unix only.
CMAKE_PFX="${PREFIX}"
if [[ -n "$EXTRA" ]]; then
CMAKE_PFX="${PREFIX};${EXTRA}"
fi
cmake -S cmake/smoke_consumer -B smoke_build \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-DCMAKE_PREFIX_PATH="${CMAKE_PFX}"
cmake --build smoke_build -j 4
expected="$(tr -d '[:space:]' < VERSION)"
./smoke_build/smoke_version | grep -qxF "${expected}"
export HOMEBREW_NO_AUTO_UPDATE=1
brew install eigen ccache
echo "CMAKE_PREFIX_PATH=$(brew --prefix eigen)${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH}" >> "$GITHUB_ENV"

- name: Restore ccache
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.build_type }}-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.build_type }}-

- name: Verify (${{ matrix.build_type }})
run: python3 scripts/ci_verify.py ${{ matrix.build_type == 'Release' && 'release' || 'debug' }} --build-dir build --jobs 4 --ccache

- name: Stage and summarize diagnostics
if: always()
run: python3 scripts/collect_ci_diagnostics.py --build-dir build --profile ${{ matrix.build_type == 'Release' && 'release' || 'debug' }}

- name: Retain CI diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: ci-diagnostics-${{ matrix.os }}-${{ matrix.build_type }}
path: ci-diagnostics/
if-no-files-found: warn

# Dedicated AddressSanitizer + UndefinedBehaviorSanitizer lane. Without this,
# the determinism / NaN-propagation / handle-reuse / C-ABI-lifecycle probes
Expand All @@ -81,25 +80,41 @@ jobs:
# PINEFORGE_ENABLE_SANITIZERS).
sanitizers:
runs-on: ubuntu-latest
env:
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1:abort_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install Eigen3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libeigen3-dev

- name: Configure (ASan + UBSan)
run: cmake -B build-asan -DCMAKE_BUILD_TYPE=Debug -DPINEFORGE_ENABLE_SANITIZERS=ON

- name: Build
run: cmake --build build-asan -j 4

- name: Test under sanitizers
env:
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1:abort_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: ctest --test-dir build-asan --output-on-failure
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libeigen3-dev ccache

- name: Restore ccache
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ runner.os }}-${{ runner.arch }}-sanitizers-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-${{ runner.arch }}-sanitizers-

- name: Verify (sanitizers)
run: python3 scripts/ci_verify.py sanitizers --build-dir build-asan --jobs 4 --ccache

- name: Stage and summarize diagnostics
if: always()
run: python3 scripts/collect_ci_diagnostics.py --build-dir build-asan --profile sanitizers

- name: Retain CI diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: ci-diagnostics-sanitizers
path: ci-diagnostics/
if-no-files-found: warn

# The branch ruleset requires a status check named `build`, but the matrix
# job above reports as `build (ubuntu-latest, Release)` and friends -- a bare
Expand Down
97 changes: 75 additions & 22 deletions .github/workflows/native-live.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ on:
permissions:
contents: read

# Cancel superseded PR runs only. Main/post-merge and manual workflow_dispatch
# use a unique run_id group so proof runs are never cancelled.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_COMPRESS: "1"
CCACHE_MAXSIZE: 500M
CURL_VERSION: "8.14.1"
CURL_SHA256: "f4619a1e2474c4bbfedc88a7c2191209c8334b48fa1f4e53fd584cc12e9120dd"

jobs:
native-live:
runs-on: ubuntu-latest
Expand All @@ -18,40 +32,79 @@ jobs:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
fetch-depth: 1

- name: Install native build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build libeigen3-dev libsqlite3-dev libssl-dev zlib1g-dev
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y cmake ninja-build libeigen3-dev libsqlite3-dev libssl-dev zlib1g-dev ccache

- name: Restore ccache
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ runner.os }}-${{ runner.arch }}-native-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-${{ runner.arch }}-native-

- name: Identify native dependency environment
id: native-deps
shell: bash
run: |
mkdir -p build-native-deps
{
printf '%s\n' "${ImageOS:-unknown}" "${ImageVersion:-unknown}" "${GITHUB_WORKSPACE}"
cc --version
cmake --version
dpkg-query -W libssl-dev zlib1g-dev
} > build-native-deps/environment.txt
echo "identity=$(sha256sum build-native-deps/environment.txt | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT"

- name: Restore pinned WebSocket-enabled libcurl
id: curl-cache
uses: actions/cache@v4
with:
path: build-native-deps/curl-install
key: curl-${{ env.CURL_VERSION }}-${{ env.CURL_SHA256 }}-${{ steps.native-deps.outputs.identity }}-${{ hashFiles('.github/workflows/native-live.yml') }}-${{ runner.os }}-${{ runner.arch }}

- name: Build pinned WebSocket-enabled libcurl
env:
CURL_CACHE_HIT: ${{ steps.curl-cache.outputs.cache-hit }}
run: |
set -euo pipefail
cmake_file="build-native-deps/curl-install/lib/cmake/CURL/CURLConfig.cmake"
if [[ "${CURL_CACHE_HIT}" == "true" && -f "$cmake_file" ]]; then
echo "Using cached libcurl at ${cmake_file}"
exit 0
fi
mkdir -p build-native-deps
cd build-native-deps
curl --fail --location --proto '=https' --tlsv1.2 https://curl.se/download/curl-8.14.1.tar.xz -o curl.tar.xz
echo 'f4619a1e2474c4bbfedc88a7c2191209c8334b48fa1f4e53fd584cc12e9120dd curl.tar.xz' | sha256sum -c -
curl --fail --location --proto '=https' --tlsv1.2 \
"https://curl.se/download/curl-${CURL_VERSION}.tar.xz" -o curl.tar.xz
echo "${CURL_SHA256} curl.tar.xz" | sha256sum -c -
tar -xf curl.tar.xz
cmake -S curl-8.14.1 -B curl-build -G Ninja \
cmake -S "curl-${CURL_VERSION}" -B curl-build -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PWD/curl-install" \
-DBUILD_CURL_EXE=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON \
-DBUILD_TESTING=OFF -DCURL_USE_OPENSSL=ON -DENABLE_WEBSOCKETS=ON \
-DHTTP_ONLY=ON -DCURL_USE_LIBPSL=OFF -DUSE_LIBIDN2=OFF \
-DCURL_USE_LIBSSH2=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF
cmake --build curl-build -j 4
cmake --install curl-build
- name: Build native runner and tests
run: |
cmake -S . -B build-live -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DPINEFORGE_BUILD_LIVE_RUNNER=ON -DPINEFORGE_BUILD_TUTORIAL=OFF \
-DCURL_DIR="$PWD/build-native-deps/curl-install/lib/cmake/CURL"
cmake --build build-live -j 4
- name: Test native engine, parser, journal and transports
run: |
ctest --test-dir build-live --output-on-failure -j 4
# This required lane uses the pinned WS-enabled curl. A skip is not
# sufficient here even though ordinary system-curl builds may skip.
build-live/bin/test_native_live_websocket
python3 scripts/check_c_abi_runtime.py
python3 scripts/check_broker_state_hash_coverage.py
- name: Install native executable
run: |
cmake --install build-live --prefix "$PWD/build-live/install"
build-live/install/bin/pineforge-live --help

- name: Verify native live
run: python3 scripts/ci_verify.py native --build-dir build-live --jobs 4 --generator Ninja --curl-dir "${{ github.workspace }}/build-native-deps/curl-install/lib/cmake/CURL" --ccache --require-websocket

- name: Stage and summarize diagnostics
if: always()
env:
CURL_CACHE_HIT: ${{ steps.curl-cache.outputs.cache-hit }}
run: python3 scripts/collect_ci_diagnostics.py --build-dir build-live --profile native

- name: Retain CI diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: ci-diagnostics-native-live
path: ci-diagnostics/
if-no-files-found: warn
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ docs/production-readiness-findings.md

# Cloudflare wrangler local cache (never commit)
.wrangler/

# Reusable CI cache and retained diagnostics (never source bundles)
.ccache/
ci-diagnostics/
10 changes: 3 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@
Both C++ unit tests and full corpus verification must pass.

```bash
# 1. Build and run unit tests (ctest)
cmake -B build -S . \
-DCMAKE_BUILD_TYPE=Release \
-DPINEFORGE_BUILD_TESTS=ON \
-DPINEFORGE_BUILD_CORPUS_STRATEGIES=ON
cmake --build build -j$(nproc 2>/dev/null || echo 4)
ctest --test-dir build --output-on-failure
# 1. Run the same complete verification profile as CI
python3 scripts/ci_verify.py release --build-dir build --jobs 4
# Relevant additional profiles: debug, sanitizers, native (see docs/ci.md)

# 2. Run full validation corpus sweep (against TV exported trades)
./scripts/run_corpus.sh
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# PineForge

**The open-source PineScript v6 backtest engine that reproduces TradingView trade-for-trade.**
**An open-source C++17 engine for backtesting and forward execution, with PineScript support through code generation.**

[![CI](https://img.shields.io/github/actions/workflow/status/pineforge-4pass/pineforge-engine/ci.yml?branch=main&label=ci&logo=github)](https://github.com/pineforge-4pass/pineforge-engine/actions)
[![Parity](https://img.shields.io/badge/TradingView%20parity-4%2C190%20%2F%204%2C190%20probes-brightgreen)](#validation-scoreboard)
Expand Down Expand Up @@ -74,13 +74,13 @@ Prefer zero install? The hosted server at **[mcp.pineforge.dev/mcp](https://mcp.

```bash
git clone https://github.com/pineforge-4pass/pineforge-engine.git && cd pineforge-engine
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure # all enabled unit, replay and ABI checks
python3 scripts/ci_verify.py release --build-dir build --jobs 4
bash tutorial/run.sh # MACD on BTC/USDT, end to end
python3 tutorial/run_stream.py # OHLCV warm-up → realtime trades
```

The [shared local/CI verifier](docs/ci.md) includes source guards, tests and installed-package smoke checks. Its ABI check links callers against both the current library and a separately built, pinned historical library. Preparation uses local Git history and the configured compiler; see the [ABI fixture guide](tests/fixtures/settlement_cpp_abi/README.md) for shallow clones and repeated checks. CTest itself stays offline.

Prerequisites: CMake ≥ 3.16, a C++17 compiler (GCC ≥ 9, Clang ≥ 10, Apple Clang ≥ 12), Eigen 3.3+ (fetched automatically if absent), Python 3 for the tests (`-DPINEFORGE_BUILD_TESTS=OFF` for a library-only build). `cmake --install build --prefix /usr/local` installs `lib/libpineforge.a`, `include/pineforge/`, and the `find_package(PineForge)` config.

### Embedded in your own harness
Expand Down
Loading
Loading