Skip to content

fix(engine): make dead strategy.close semantically inert - #143

Merged
luisleo526 merged 1 commit into
mainfrom
codex/issue-141-dead-close-invariance
Aug 1, 2026
Merged

luisleo526 merged 1 commit into
mainfrom
codex/issue-141-dead-close-invariance

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Summary

  • make deferred-flip carry depend on runtime order state instead of the global script_has_strategy_close_ AST bit
  • retain the legacy bit as a no-op so existing generated classes remain source-compatible
  • add a metamorphic regression test proving an unreachable strategy.close cannot change bracket-exit fills
  • update existing characterization coverage and runtime documentation

Fixes #141.

Root cause

Codegen set script_has_strategy_close_ from textual AST presence, without reachability analysis. The runtime used that global bit to gate placement-time reversal carry, so adding an unreachable strategy.close changed later priced-entry quantities even though the executed broker commands were identical. A strategy.exit bracket can close the carry source on its own, so the runtime decision must use the order's captured state rather than unrelated source text.

Validation

  • full build succeeded
  • CTest: 123/123 passed
  • fresh locked Corpus A/B: 312/312 on both sides; 0 tier DOWN, 0 metric DOWN, 0 gate DOWN; the only changed probe remained Excellent and improved six metrics
  • full downstream strategy A/B: terminal classifications, grade rows, and trade tapes were identical; 0 tier DOWN, 0 countAbsDelta regressions, 0 Excellent-to-below-Strong regressions, 0 unresolved failures
  • downstream E+S remained neutral; owner explicitly approved the byte-identical no-regression result for this correctness fix

Copilot AI review requested due to automatic review settings August 1, 2026 08:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the deferred-flip carry behavior so that adding an unreachable strategy.close in Pine source cannot change runtime backtest fills. It removes the runtime dependency on the compile-time script_has_strategy_close_ AST bit, keeps that bit only for generated-code compatibility, and adds regression/characterization coverage plus runtime documentation updates.

Changes:

  • Remove script_has_strategy_close_ from the deferred-flip predicate in BacktestEngine::enter_market_from_flat.
  • Reframe script_has_strategy_close_ as a legacy, semantically inert compatibility member in engine headers and runtime docs.
  • Update/add tests to prove unreachable strategy.close is inert and adjust characterization expectations where carry behavior is now pinned correctly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/engine_orders.cpp Drops the compile-time flag from the deferred-flip carry condition so behavior depends only on order-captured runtime state.
include/pineforge/engine.hpp Documents script_has_strategy_close_ as legacy compatibility only (no runtime semantic effect).
tests/test_strategy_pyramiding.cpp Removes direct flag forcing and adds a metamorphic regression test for issue #141.
tests/test_same_id_stop_replace.cpp Updates commentary to reflect the new deferred-flip predicate (no compile-time gate).
tests/test_close_all_coqueued_entry.cpp Updates characterization to match generated-script behavior and pins expected carry outcome.
docs/cheatsheet-runtime-and-execution.md Updates runtime cheat sheet to reflect that the flag no longer gates deferred-flip behavior and that brackets can arm carry behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +218 to +221
Bar bars[4] = {
{100, 101, 99, 100, 1000, 60'000},
{100, 101, 99, 100, 1000, 120'000}, // L fills; arm S + XL
{100, 101, 94, 95, 1000, 180'000}, // XL closes L; S untouched
@luisleo526
luisleo526 merged commit 8df08f2 into main Aug 1, 2026
8 checks passed
@luisleo526
luisleo526 deleted the codex/issue-141-dead-close-invariance branch August 1, 2026 08:10
luisleo526 added a commit that referenced this pull request Aug 12, 2026
…efault sizing (#146)

* fix(engine): same-bar close+reverse sequencing under frozen default sizing

Generalize the TV-pinned SHORT-seed default-FIFO close collision oracle
from its fixed-qty exact book to frozen PERCENT_OF_EQUITY / CASH default
sizing with unequal seed/entry quantities (finding 272; derivation
evidence/mc-timing-and-sequencing-derivation-20260812.json, closeReverseSeq,
fitted 25/25 exact on alpha-forge-liquidity-matrix-v2).

TV rule: with a SHORT seed qty S (entered on an earlier bar) and the exact
same-bar book entry(Long); entry(Short); close(Long)[no-op];
close(Short)[frozen target S], all filling at the next open P, TV emits
(1) the old short S exiting via order 'Long', (2) a zero-PnL dur-0 LONG
round trip qty L (frozen default qty) 'Long'->'Short', (3) a second
zero-PnL dur-0 LONG round trip qty min(S, L)
'Close entry(s) order Short'->'Short', and (4) an end-of-bar SHORT of
exactly max(0, L - S) under the final entry's id — the real opposite entry
is not queued; ordinary signal processing resumes from that position.

Engine changes (all five touch points in src/engine_fills.cpp):
- exact-book tagging: qty-type-aware seed gate (FIXED keeps the constant
  default==seed equality; PERCENT/CASH require the complete placement-frozen
  snapshot and matching frozen qtys on both entries); the projected
  final-short admission generalizes 2*seed.qty to L + min(S, L) and, for
  PERCENT (pct<=100), additionally mirrors the KI-54 frozen reversal
  re-check without its fill-time epsilon so a tagged book can never be
  half-declined mid-transaction.
- materialization live check + fill: the close order materializes its
  placement-frozen target CAPPED at the live long book, min(S, L).
- final-short live check: accepts lot2 == min(S, lot1) and
  position == lot1 + lot2.
- final-short kernel: closes both LONG lots and re-opens SHORT with the
  residual lot1 - lot2 = max(0, L - S) at the same fill price under the
  final short's id/incarnation/comment; flat when L <= S.

The FIXED cohort's pinned L == S collapses every relaxed gate to the old
equalities and residual 0 (byte-identical; test_short_seed_close_collision
unchanged). #143 dead-strategy.close, the declined-reversal close-leg
suppression, gap-reject, and pyramiding paths are untouched; non-collision
stale closes are still Removed. New unit coverage:
tests/test_short_seed_collision_percent.cpp (percent remnant L>S with
follow-up close of the remnant, percent flat L<=S, CASH remnant, gap-up
KI-54 decline non-trigger control, partial-close non-trigger control).
Full ctest: 125/125.

Co-Authored-By: Claude Fable 5 <[email protected]>

* corpus: advance to the re-minted same-bar sequencing run

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
luisleo526 added a commit that referenced this pull request Sep 17, 2026
… re-armed stop's forced price to the tick (R4-D L10r)

order-dual-stop-both-touch-priority-01 trades #143/#144: two stop entries
touched on one bar filled in the reverse order and the second lot one tick
off.  A same-id Entry/Order replacement now retains its predecessor's
source_sequence so the book keeps the owner's creation order across
re-issues; flat-armed entry-stop arbitration compares source_sequence
instead of command_sequence; and a throttled-reopen re-arm's forced
execution price is rounded directionally to the tick like every other
stop fill.  Pinned with embedded bars in
tests/test_l10r_dual_stop_same_bar_order.cpp (W18c, Gemini).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RKCCnMS51GLmRbA3AHtBDb
luisleo526 added a commit that referenced this pull request Sep 29, 2026
…GINE) (#300)

Audit of every README and doc page of pineforge-engine against engine main
35db01c and the live measurement; each change names its source.

What was wrong, and the source of the truth:

- Parity numbers were the retired 4,190-probe round-39 era (badges, hero,
  scoreboard, lane table, contributing-llm.md, design/progress pages). Now the
  active baseline pineforge-parity-baseline-20260929-engine-35db01c8 (snapshot
  sha256 47cec517..., population cf857209...): 8,006 probes, 7,989 graded =
  7,905 excellent + 84 strong, 17 TradingView-side anomalies excluded,
  18 lanes, 2,759,431 TradingView trades (population tvTradeRows / 2). The
  "2.8M matched" count has no source in the snapshot and is gone. Public
  corpus: 311 excellent + 1 declared anomaly (scripts/check_corpus_parity.sh
  pinned headline; parity step of the XAU-CAL lane job on this tree), and
  anomaly-equity-mirror-strategy-equity-01 is that anomaly (it does not
  match trade-for-trade on the pinned corpus).
- Versions: no 1.0.0, 0.14.0 or rc tag exists (git tag; gh release list).
  CHANGELOG's "## 1.0.0" becomes "## Unreleased" and lists #292-#298, which
  it lacked (squash messages 7f807b9..35db01c); "34 more" PF_API is 39.
  README Releases: v0.13.1 entry added, v0.13.0 line corrected, the merged
  live/abi-v4 "Unreleased" line and the 1.0.0 line replaced. Pages that
  framed 1.0 as shipped now say planned.
- Front door 1 could not run: pineforge-codegen has no CLI (0.10.4 or main),
  PyPI 0.10.4 output does not compile against main (cc log), a plain
  -lpineforge link drops the runtime C ABI (run_strategy.py refuses it), and
  run_strategy.py needs --ohlcv/--no-trim-output without the corpus. The new
  block ran end to end from a clean clone (14 trades on the tutorial feed).
- Prerequisites: floating-point std::to_chars (src/str_utils.cpp:55) needs
  GCC 11 / libc++ 14; ctest --test-dir needs CMake 3.20; examples need
  -DPINEFORGE_BUILD_EXAMPLES=ON; the install also ships libpineforge_kernel.a.
- MCP section: the image, hosted server and npm package run engine 0.13.1 +
  codegen 0.10.4 (pineforge-release v0.1.25 notes); hosted server and npm
  package descriptions corrected from their live/ published sources.
- Benchmarks: every number reproduces (check_provenance 163/163); framing
  fixed (commit and method named, PineTS has had strategy.* since 0.9.17),
  recipes state their prerequisites, two wrong failing-gate cells fixed,
  personal host name scrubbed from 14 Google Benchmark JSONs with the
  manifest sha256 values updated (origin_sha256 keeps the measured file).
- Reference pages: native-engine.md, pine-to-native.md, coverage.md,
  pine_v6_coverage_detail.md, the contract pages, ci.md and the design notes
  described removed members and pre-#253/#254 behaviour, stale counts
  (src 48 -> 53 .cpp, 66 -> 70 indicator classes, 43/33/10 -> 46/35/11
  NativeRunSpec fields, floors 786/298 -> 790/300), wrong anchors, drawings
  as absent, and transpiler status of an old codegen (now "on codegen main,
  not in a release yet" where it differs from v0.10.4).
- Hygiene: internal host names, personal paths, private-repo references and
  campaign tool names marked or removed; the banner image's stale
  "V0.8.0 / 245/246 / 0 engine bugs" strip removed; a dead
  pineforge-workflow-live link replaced by the public pineforge-live repo.
- Two orphan pages, docs/cheatsheet-runtime-and-execution.md and
  docs/tv-parity-probe-spec.md, were wrong in their load-bearing claims
  (17 removed engine members, pointvalue/FX/slippage claims) and nothing
  linked them: deleted.
- Sample outputs were stale: every page and the tutorial README printed the
  tutorial MACD as 49 trades / -190.85; since 650b0cb (in v0.13.1) the report
  counts the range-end close, and every harness prints 50 / +569.97 (runs on
  a clean clone of this branch). examples-python-sweep.md's 10000 capital
  could not pay for its 1-BTC order (every combo 0 trades): 100000, output
  re-measured. examples-multi.md output format matches multi.py; the
  magnifier A/B page shows today's run. The report fields script_tf_ratio
  and needs_aggregation print their contract values again on this base
  (FIX-E1E2, #299; tutorial/run_mtf.py Table A: ratio 1/1/4/16, agg
  0/0/1/1), so the pages state them as plain facts.
- Doxygen: `file:line` in four pages autolinked to a dead "file:line" URL;
  spelled `path:line` now. Build: 0 warnings.

Review fixes (REVIEW-DOCS-ENGINE-final.md, verdict MERGE-WITH-FIXES, of the
first version of this change, 79bd84c1 + 6cb01aa0): its five P2 findings with
the reviewer's fix text, and every P3 row but the last.

P2
1. docs/pages/metrics.md: a timestamped FX curve no longer fails closed for a
   leveraged position across a rate change; that position takes the
   broker-open margin checkpoint at the step bar's open
   (tests/test_affordability_fx.cpp:152-154, :781).
2. docs/pages/report-schema.md: the #254 caveat sat inside the table, so the
   bar_magnifier_enabled row rendered as text; it now follows the table.
3. CONTRIBUTING.md, docs/pages/getting-started.md: compiler minimums are the
   ones README.md states (floating-point std::to_chars, src/str_utils.cpp:55:
   libstdc++ from GCC 11, libc++ 14, macOS 13.3; g++-9 and g++-10 fail).
4. docker/README.md: the sample is the published image's run (50 trades,
   17/33, +569.97, trade 1 entering 1777488300000).
5. docs/pages/mtf.md: expected Table A says main prints 0 in ratio and agg?
   until the #254 regression is fixed.

P3
- configuration.md, CHANGELOG.md, coverage.md: v5 scripts do not "keep" host
  defaults (both codegens refuse //@Version=5); code from an earlier codegen
  and a hand-built PineStrategyConfig do.
- streaming.md: "Every strategy_stream_* call above" (create/free return no
  status).
- examples-multi.md: a module also exports std::piecewise_construct.
- integration-cmake.md: EXACT 0.13.1 also matches a git-checkout build of
  main; the ABI promise starts "From 1.0.0".
- ffi-python.md, tutorial-macd.md: the mirror covers the report structs, not
  every struct (ffi-python.md:15-17).
- configuration.md: tracing costs one branch per traced call when disabled
  (report-schema.md:180), not zero.
- benchmarks/results/raw/README.md: "Two edits were made" (host_name was
  rewritten in every Google Benchmark file); benchmarks/README.md: the
  maintainer fetch runs with the campaign environment sourced. Wording only.
- AGENTS.md: the configure line exports compile commands, which the receipt
  check needs; AGENTS.md and docs/ci.md: promote-baseline.yml is installed
  from the campaign copy, not unchanged (31 lines differ).
- docs/ci.md: the debug profile also skips the twin-parity guard
  (TWIN_PARITY_PROFILES).
- mtf.md, timeframes.md: "reported for an engine fix" dropped (no public
  issue).
- native-feature-parity.md: the C API "landed on main", not "shipped".
- tutorial/README.md: Table B's script_tf_ratio prints 0 on main until the
  #254 regression is fixed.

Not changed here: benchmarks/results/summary.md and trade_comparison.md keep
the first version's text (hand edits of generated output; fixing the generator,
benchmarks/compare.py:361 and scripts/verify_corpus.py:1403, is an
engine-lane follow-up). No benchmark number or results file changes beyond
the raw/README.md and benchmarks/README.md wording above: BENCH-REFRESH is
re-measuring the benchmark.

Rebased onto 4dc496d (FIX-E1E2, #299), squashed to one commit. docs/ci.md
states the new floors: PR registration 773 (debug, sanitizers) and 782
(native), full-run release 792 and kernel 300 (scripts/ci_verify.py:320,
:594, :638). The one rebase conflict, a native-refactor-progress.md row,
keeps main's re-anchored lines and this change's host-name removal.

Sentences this commit deletes or rewrites past recognition, named by the
commit that introduced them (scripts/check_doc_reverts.py):
- a4ab775 (docs: source-verified runtime-overrides + order-execution cheatsheet): cheatsheet-runtime-and-execution.md (142)
- 480fd19 (Production-readiness probes + engine state-reset / finite-fill fixes (): tv-parity-probe-spec.md (61)
- 28ad088 (docs: add Part 3 (cross-cutting behaviors) to engine cheatsheet): cheatsheet-runtime-and-execution.md (60)
- a8f84b9 (docs(api): OpenCV-style C ABI reference site + CF Pages deploy): abi-stability.md (21), tutorial-macd.md (3), examples-magnifier.md (2), examples-multi.md (2), getting-started.md (2), index.md (2), report-schema.md (2), examples-rust.md (1), ffi-python.md (1), install.md (1), lifecycle.md (1)
- cc4a56e (docs: add Pine v6 runtime coverage report (docs/coverage.md)): coverage.md (37)
- 24dcc53 (docs: reconcile Pine v6 audit + coverage docs with the 2026-06-10 audi): pine_v6_coverage_detail.md (29)
- d093560 (INT26: wave H, the late picks and the corpus re-pin to b40aa8e (1.0.0-): native-refactor-progress.md (13), install.md (3), pine-to-native.md (2), CONTRIBUTING.md (1)
- 73817c1 (Lower generated Pine execution onto the native kernel and retire the l): cheatsheet-runtime-and-execution.md (8), README.md (3), tv-parity-probe-spec.md (2), reservation-expansion-candidate.md (1)
- b1fbf0b (docs: detailed Pine v6 coverage report (per-identifier audit)): pine_v6_coverage_detail.md (14)
- 622d653 (docs(README): rewrite for the round-11 engine — brand-first, OSS-first): README.md (12)
- 46b2ced (docs: ABI v4 live surface; cheatsheet corrections (trail_price is read): cheatsheet-runtime-and-execution.md (7), README.md (3), live-surface.md (2)
- c45cf5a (refactor: move retained-parent priority into Pine compatibility): README.md (7), pine-order-priority-boundary.md (4)
- c1084ff (docs: record round 39 fractional price admission parity): README.md (10)
- f268342 (feat: add historical account FX lifecycle (#125)): cheatsheet-runtime-and-execution.md (10)
- af4c688 (Document realtime streaming lifecycle): cheatsheet-runtime-and-execution.md (5), coverage.md (3), examples-python-sweep.md (1), tutorial-macd.md (1)
- 9507d8f (refactor(corpus): pull equity-mirror into corpus/parity-anomalies/): coverage.md (10)
- 76482e6 (fix(runtime): align TA coverage and corpus replay): coverage.md (9)
- cc0b22d (Refactor reservation growth and Pine market instruction ownership): fill-model.md (7), frozen-market-instruction.md (2)
- d5a5eaf (Align lifecycle and admission documentation with final v11 model): market-admission.md (9)
- a6b0b70 (docs(audit): exhaustive Pine v6 coverage audit — 38 critical, ~62 mino): pine_v6_audit_master.md (7)
- 1e360bd (INT27: wave I -- magnifier on TradingView's intrabars, recalculation f): cheatsheet-runtime-and-execution.md (5), magnifier.md (1)
- 1dd5430 (Add the native feature-parity design: gaps, kernel-vs-adapter rulings,): design/native-feature-parity.md (6)
- 74491fa (INT28: wave-J engine integration (TVDEF-DROPS, W3/W3B/W4/W5/W6/W6B/W8,): cheatsheet-runtime-and-execution.md (4), README.md (1)
- 7ad5d38 (docs: refresh parity scoreboard through round 23): README.md (5)
- 7f807b9 (Another symbol's bars reach a run as a feed: kernel instrument feeds +): coverage.md (4), mtf.md (1)
- 3b13183 (Refactor order admission and trigger lifecycle into generic submodels): exit-leg-lifecycle-reflection.md (5)
- e244d65 (docs(coverage): update pine_v6_coverage_detail.md for post-sprint stat): pine_v6_coverage_detail.md (5)
- f864be5 (refactor: isolate Pine cap policy from native order state): cheatsheet-runtime-and-execution.md (2), pine-intraday-cap-boundary.md (2)
- e7cdf05 (Add native execution terms: host-resolved sizing, exact reversal, prec): abi-stability.md (2), native-engine.md (2)
- b4dd9a3 (docs(mtf): add multi-timeframe tutorial + Doxygen page): mtf.md (4)
- 5c2be9d (docs: record round 38 whole-lot budget parity results): README.md (3)
- 6bfe983 (docs: align round 39 lane table and diagnostic evidence): README.md (3)
- 471a113 (Docs: the README carries the 200-strategy refresh -- tiers per engine ): README.md (3)
- 8df08f2 (fix(engine): make dead strategy.close semantically inert (#143)): cheatsheet-runtime-and-execution.md (3)
- d3cca9e (Docs: correct migration semantics and harden publication guards): cheatsheet-runtime-and-execution.md (2), pine-to-native.md (1)
- 52f4dde (Preserve complete mirror context and earlier standalone ABI controls): fill-model.md (3)
- 4fb9db3 (Docs: CONTRIBUTING.md rewritten for someone who has never seen this re): CONTRIBUTING.md (2)
- 2ab2bfb (docs(README): round-11 closed-test results and a full factual refresh): README.md (2)
- 0501468 (docs: task-13 fix round 1 — v4 report mirrors (schema, Rust), probe-su): README.md (1), cheatsheet-runtime-and-execution.md (1)
- cfbe677 (Docs: the session flags end a session where TradingView ends it (R5 la): cheatsheet-runtime-and-execution.md (2)
- 6aa1d13 (runtime: wire syminfo.pointvalue through all money paths; hold ScopedT): coverage.md (2)
- 8b7965a (Docs: onboarding written for the contributor who is a language model (): contributing-llm.md (2)
- 16f3652 (Docs: the two site front doors re-derived against HEAD (R5 lane L14-B,): index.md (2)
- 04b0588 (Add native resolved execution settlement and preserve paid entry costs): metrics.md (2)
- 149f77c (Replace seven pending-order flags with quantity and event facts): quantity-intent.md (2)
- 962960b (Session flags match TradingView on multi-window and extended-hours cha): pine_v6_coverage_detail.md (2)
- 5215291 (Merge branch 'round5/bar-indexed-extremes-ring' into round5/candidate): pine_v6_coverage_detail.md (2)
- b1b27f0 (Initial commit: PineForge runtime v0.1.0): CONTRIBUTING.md (1)
- bfdbe96 (docs: refresh parity scoreboard through round 24): README.md (1)
- cda51d5 (Record the round 35 parity scoreboard and raw trade gains): README.md (1)
- 0d76a09 (Wave G: no ordinary quantity request stops a run, cancelled exits stay): adr/0001-kernel-adapter-boundary.md (1)
- 0983770 (Unify remaining fill paths under native settlement (#247)): ci.md (1)
- 50339d0 (Docs: the §1 inventory carries its closure markers, and a gate keeps t): design/native-feature-parity.md (1)
- 8b3b481 (docs+fix: org rename to pineforge-4pass; AI capability-gap note in REA): examples-multi.md (1)
- c28c2db (Derive placement state from original admission observations): fill-model.md (1)
- 208f00b (docs: refresh ctest count + run_strategy usage example): install.md (1)
- 0c174d9 (Docs: the arm options, the adoption table and the grid re-lowering wai): native-engine.md (1)
- 4b05b62 (Adapter re-lowering: the TradingView margin call runs through the kern): native-engine.md (1)
- 8e558eb (Say what the per-bar broker hash is for, where its API is documented (): native-engine.md (1)
- 5f3299d (Build the standalone native execution path (#243)): native-engine.md (1)
- b18f75e (Native sizing bases and fractional reduces (R5 lane L3)): native-engine.md (1)
- 0ef00da (The pages say a working row tells an absent arm from an arm at 0.0 (R5): native-engine.md (1)
- 7328f4d (Docs: the migration page is a complete map, and a gate holds it comple): pine-to-native.md (1)
- ce64f06 (docs(audit): update Pine v6 master audit — mark 20 resolved critical i): pine_v6_audit_master.md (1)
- a19f5a8 (docs: repoint codegen references to pineforge-codegen-oss; purge old f): pine_v6_audit_master.md (1)
- 2db58d9 (runtime: reconcile per-trade MFE/MAE with TV excursion semantics; gate): pine_v6_audit_master.md (1)

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An unreachable strategy.close changes backtest results (60 trades, PnL 182.00) via script_has_strategy_close_

2 participants