Repository navigation
Support engine 1.0: in-process runner reads C ABI 4, default runtime pineforge-release 1.0.0 - #14
Merged
Merged
Conversation
Move the default runtime image and the server Dockerfile to pineforge-release 1.0.0 (engine 1.0.0, codegen 1.0.0), pinned by its multi-arch index digest. Tests now expect engine 1.0.0's C ABI 4: - unit tests pin the ABI 4 report layout, the refusal of ABI 2 and 3 libraries, and the open_at_end trade field; - the Docker suite compiles a strategy inside the pinned image, checks every mirrored struct against the image's pineforge.h with offsetof, and compares PineForgeBacktestRunner's report with the image's own run_json.py report for the same library. The Docker fixture strategy now states its order size (fixed, 1). Codegen 1.0 gives an omitted size TradingView's Pine v6 default, 100% of equity, and at that size every entry in the fixture's short series is refused for want of funds, so the run had no trades to compare. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mirror pineforge.h v1.0.0 field by field: - pf_trade_t gains open_at_end (appended in ABI 3), so a trade row is 104 bytes; the copied trades carry it as a boolean; - pf_report_t gains broker_state_hash and broker_state_hash_len (ABI 4), so the caller-allocated report is 960 bytes; report_free releases the array and the detached report does not copy it; - sharpe_tv / sortino_tv keep offsets 48 / 56 and their report keys; the header now spells them sharpe_monthly / sortino_monthly. The runner accepts ABI 4 only, the exact match pineforge.h asks of every caller and the release image's harness enforces. ABI 2 (engine v0.10.2 to v0.12.3) and ABI 3 (engine v0.13.x) libraries are refused before any strategy call, with a rebuild hint: ABI 2 trade rows are 8 bytes shorter, and ABI 3 reports lack the broker-state hash. BacktestReport documents the unit of trade max_runup / max_drawdown: whole-trade excursions in account currency, net of entry fees, as the 1.0 header now states (older headers said price travel per unit of quantity; the engine's values did not change). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
hatchling now writes Metadata-Version 2.5. twine 6.2 rejects it
("'2.5' is not a valid metadata version"), which fails the CI and
release `twine check --strict` step; twine 7.0.0 (packaging >= 26.1)
accepts it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- README, backtesting and server guides: release 1.0.0 (engine 1.0.0, codegen 1.0.0) is the pinned default; 0.1.12 is the digest-pinned way to reproduce earlier results. The README quick start reports 89 trades on 1.0.0 and 88 on 0.1.12: the same 88 trades plus the open_at_end close of the position left open after the last bar. - API reference: PineForgeBacktestRunner reads ABI 4 (engine 1.x) and refuses ABI 2 and 3 with a rebuild hint; trade excursions are in account currency, net of entry fees; open_at_end is explained. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-ups: - The runtime image policy (and the README note that points to it) says that codegen 1.0 gives a strategy() without initial_capital, default_qty_type or default_qty_value TradingView's Pine v6 defaults: 100000, percent_of_equity and 100, where release 0.1.12 used 1000000, fixed and 1. At 100% of equity the default margin refuses entries that fill above their signal price, so such scripts can trade less on 1.0.0; declaring all three keeps order sizes equal. - A library without pf_abi_version names engine v0.10.1 or earlier and asks for engine 1.x; an ABI above 4 asks for a newer pineforge-data. - The range-end close is the last bar's close rounded to the tick size, without slippage, as pineforge.h states. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-ups: - the layout probe also compares each member's scalar kind (C11 _Generic against the ctypes type), so a same-size change of signedness, integer/double or pointer/integer fails, and drops a keys() comparison that could not fail; - the in-process run compares every summary counter the release report exposes (magnifier totals, timeframe seconds and ratio, aggregation); - a timed-out check container is removed through its cidfile, as the runtime does; - the fixture bars come from fixture_values(closes), and the published runtime test also pins the image's engine and codegen versions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pineforge-data now works with PineForge engine v1.0.0 and codegen 1.0.0, shipped together as
pineforge-release1.0.0.PineForgeBacktestRunnermirrors was checked against engine v1.0.0'sinclude/pineforge/pineforge.h, field by field. The Docker suite now repeats that check withoffsetofagainst the header inside the pinned image.pf_trade_tgainsopen_at_end(appended in ABI 3). A row is now 104 bytes, and the copied trades carryopen_at_endas a boolean.pf_report_tgainsbroker_state_hashandbroker_state_hash_len(ABI 4). The report is now 960 bytes. It is caller-allocated, so without these two fields the engine would write 16 bytes past the Python buffer.report_freereleases the array;BacktestReportdoes not copy it.sharpe_tvandsortino_tvkeep offsets 48 and 56 and their report keys. The header now spells themsharpe_monthlyandsortino_monthly.pf_bar_t,pf_trade_tick_t,pf_trade_stats_t,pf_metrics_t,pf_equity_point_t,pf_security_diag_t,pf_trace_entry_t, and therun_backtest_full, setter and stream signatures.EXPECTED_PF_ABIis 4, and the check stays an exact match.pineforge.hasks every caller for an exact match, and the image's ownrun_json.pyenforces one. The runner refuses ABI 2 libraries (engine v0.10.2 to v0.12.3) and ABI 3 libraries (engine v0.13.0 to v0.13.1) before any strategy call, and theEngineBacktestErrorsays what to do. For example:PineForge ABI mismatch: strategy reports 3, expected 4; engine v0.13.0 to v0.13.1 built it: rebuild the strategy with engine 1.x. The ABI 2 message also says that pineforge-data 0.2.0 reads ABI 2.max_runupandmax_drawdownare now documented as whole-trade excursions in account currency, net of entry fees. The pre-1.0 header called them "price travel ($/unit qty)", but the engine's values have not changed. Since v0.10.2 (ABI 2), every engine has computed a money amount, not a per-unit price move:runup * pv - entry_commission, multiplied by the account FX rate from late 0.10.x on. On the README quick start, engine 0.11.0 and engine 1.0.0 report identical excursions for all 88 trades both produce.release_contract.pyanddocker/server.Dockerfilenow pinpineforge-release:1.0.0@sha256:b271873d0e91a9eeeb4595daaded9bdfbdbcd3925d3ec1300a833395e2004c86. That digest is the multi-arch index (amd64 and arm64) with engine 1.0.0 and codegen 1.0.0. The trace refusal message now names 1.0.0, which still has no trace option.releaseextra now requirestwine>=7,<8. hatchling writes Metadata-Version 2.5, and twine 6.2.0 failstwine check --strictwith'2.5' is not a valid metadata version. twine 7.0.0 passes on both the wheel and the sdist.strategy()defaults. The runtime image policy (and the README note that points to it) now states what runs on both images show. A script that omitsinitial_capital,default_qty_typeordefault_qty_valuegets TradingView's Pine v6 defaults on 1.0.0: 100000,strategy.percent_of_equityand 100. Release 0.1.12 used 1000000,strategy.fixedand 1. At 100% of equity, the default margin refuses entries that fill above their signal price, so such scripts can trade less.pf_abi_versionis attributed to engine v0.10.1 or earlier and gets a rebuild hint; an ABI above 4 gets a hint to upgrade pineforge-data.tests/fixtures/sma_cross.pinenow declaresdefault_qty_type=strategy.fixed, default_qty_value=1. Codegen 1.0 fills an omitted order size with TradingView's Pine v6 default of 100% of equity (see the codegen 1.0.0 CHANGELOG). Under Pine v6's default 100% margin, every entry in the fixture's short series is refused for lack of funds at that size, which leaves 0 trades to compare. The same script trades at 90% of equity, or at 100% withmargin_long=50.Tests against engine 1.0.0
Host: Linux arm64 (Ubuntu 24.04) with Docker, Python 3.13.15. The steps are those of CI. "Before" is
9322f81: the 1.0.0 pins and the new tests, with the old runner and the old twine pin. "After" is this head,d983d9b.9322f81)d983d9b)ruff check .mypy srcpytestpython -m buildpython -m twine check --strict dist/*InvalidDistribution: Invalid distribution metadata: '2.5' is not a valid metadata versionmkdocs build --strictPINEFORGE_DOCKER_TEST=1 pytest tests/test_docker_integration.py./scripts/run_database_e2e.shThese tests failed before the fix:
test_runner_refuses_pre_1_0_libraries_with_a_rebuild_hint[2-…]and[3-…];test_report_structures_match_pineforge_abi_4_layout, wheresizeof(_PfTrade)was 96, not 104.test_report_structures_match_the_release_header, wherepf_trade_twas 96 bytes in ctypes and 104 in the header;test_in_process_runner_matches_the_release_harness, which failed withPineForge ABI mismatch: strategy reports 4, expected 2.README quick start and the in-process runner
Steps 1 to 3 were taken verbatim from the README of each revision. Step 1 installs the checkout instead of the published wheel.
main(589e678), default 0.1.12timestamp_ms=1751413500000 … close=105709.0; engine 1.0.0 and codegen 1.0.0 at the pinned digest; 89 trades: the same 88, plus 1open_at_endclose of the position left open after the last bar; net P&L 23.807288969668754PineForgeBacktestRunneropen_at_end; trades, metrics and equity curve identical to the image's ownrun_json.py; header and library report ABI 4; ctypes layout equals the C layoutmacos-universalrelease tarball,pineforge-codegen1.0.0 from PyPI and Apple clangopen_at_end, net profit 23.807288969668754, bit-identical to the Linux runOn the CI runners, the Docker job now also compiles one strategy inside the release image. CI for this head is green, including the amd64 Docker job (4 passed), so the layout probe and the in-process comparison also pass on x86_64. The layout probe compares each member's name, offset, size and scalar kind (a C11
_Generictag against the ctypes type).Compatibility
minorrelease (0.3.0) fits this PR. The repository has nobreaking-changelabel yet, so none is set here.open_at_endrow is added. Scripts that omit their capital or order size are sized differently (see above).An independent review found no critical issues. Its major finding (the undocumented codegen 1.0 sizing defaults) and its minor findings are addressed in
4820127andd983d9b.🤖 Generated with Claude Code