Repository navigation
Fix the remaining reversal and exit-sizing cases (#803) - #820
Conversation
…side parent's exits Two more cases of a named strategy.exit placed with a reversing or re-entering strategy.entry, each measured against TradingView with a fixed quantity of 3 reversed into 2 and a 50 percent take-profit (TradingView closes 1 at the limit and 1 at the stop in every case). With close_entries_rule = "ANY" the basis of a named exit is the whole held position. For an exit whose entry is pending on the OTHER side of the held position and holds no lots yet, that counted the old position (3) beside the pending entry (2): half of 5 is 2.5, which the take-profit clipped to the whole new position and left the stop nothing. The held book now contributes nothing to the basis when the pending parent of the exit opens the opposite side and the named entry holds no lots; every other case keeps the whole position. When a strategy.close of the old position and a same-side strategy.entry of another id share one bar, the exits of the new entry were removed when the position went flat, because the rule that keeps an exit for a pending parent only admitted a parent on the opposite side. The side test is dropped: a parent placed on the same bar before the exit keeps its exits whichever side it opens. tests/test_adapter_exit_class_any_same_side.cpp holds three rows taken from the TradingView tapes of these cases and two guards (a reused entry id behind a close keeps no exit, and an ANY book of two same-side entries keeps its basis). Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A script that closes part of its position with strategy.close(id, qty) and, on the same bar, enters the opposite side with strategy.entry did not open the new position: the entry was resolved as if the close had flattened the book, a plain transaction, so it closed the rest of the old position and opened nothing. TradingView closes the rest and opens the full new position (TradingView tape sha256: c3e77e4e697fe85e5ba7d0e27bd1a14e47e569ed1c0a49f84cf29e68d045af1e). place_entry now adds up the quantities of the closes placed before the entry on the same bar and marks the entry as following a close only when they cover the opposite book. A partial close leaves the entry a reversal, as it is without the close. A full close, close_all, a close-first entry and every other placement keep their behaviour; no state field is added. tests/test_adapter_partial_close_reversal.cpp holds the rows: a partial close then a reversing entry to each side, and the two full-close guards. The frozen digests of the quiet-bar battery (the twelve runs that contain this shape) and of three configurations of the live-state test move with this change; they are re-pinned, with the tape named next to each edit, in the change that finishes the series. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…der the same id A script that holds lots under an id, places strategy.exit orders against that id and, on the bar of a strategy.entry that reverses the position under the same id, kept those exits for the new position, where they went on to close it. TradingView closes the old lots at the reversal and ends the exits that were placed against them (TradingView tape sha256: 638438371ac6e073b61b689d01af352db6e602230f217c75336492de4e26033e). Under the default FIFO close rule an exit placed while its entry id held lots carries the book as its owner; an exit placed before the id held lots waits on the id's next fill. When the entry that closes lots of its own id is applied, the reversal now cancels the exits of the first kind for that id and keeps the second kind, as before. A reversal with no such exit keeps the previous selection and its scratch counters; close_entries_rule = "ANY" and every other placement are unchanged. No state field is added. tests/test_reversal_same_id_exits.cpp holds the rows: the reversal to each side with the exits placed on the reversal bar, the same exits placed after a close of the old position, the new entry under another id, and exits placed before the reversal. The frozen digests of one scenario of the quiet-bar battery (Random25) move with this change; they are re-pinned, with the tape named next to each edit, in the change that finishes the series. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Three changes of this series alter which exits and orders survive a reversing entry: an exit placed with a same-side entry on the bar where the position is closed is kept when the position goes flat, an entry placed after a partial close now closes the rest and opens the new position, and an exit placed while its entry id holds lots is cancelled when an entry of the same id reverses those lots. The frozen digests of the live-state equivalence test (10 of 108 configurations) and of the quiet-bar battery (16 of 276 rows) move with them; no other row moves. Each moved value is replaced by the harvest of the tree that holds all the changes, compiled with the harvest macros of the two tests; the harvest of the tree before the series reproduces every old value. The data files carry one comment block per cause with the old and new value of every moved row and the TradingView tape of that cause. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The class series (the ANY basis and same-side exits, the partial close then reversing entry, the same-id reversal and the re-pin of the frozen digests) now holds the engine 1.11.1 train, with the reversal exit sizing change and the other changes merged into it. The only textual conflict was the unreleased section of the changelog, where every line is kept; the adapter, its header and the test registrations merged without edits. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ay which rows are controls An entry placed after closes on the same bar is a reversal only when those closes leave part of the position open. The units of the closes are summed and compared with the held position; the compare now allows the quantity epsilon that the neighbouring compare of the process-orders-on-close path already allows, so closes of 0.1 and 0.7 on a position of 0.8, which sum one unit in the last place below 0.8 in doubles, count as covering it. The notes of the class tests now say which rows are controls that pass on the base and carry no tape of their own, and only the row that reproduces a TradingView tape cites it. The changelog names the two observation fields that read 0 for an entry placed after a partial close, and the quiet-bar note lists the counts that move with the re-pin. No pinned value changes. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The train now also holds the percent default quantity cap (#805). The merge had no textual conflict: the changelog, the adapter, its header and the test registrations merged without edits. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…artial closes do not The pending order of a reversing entry placed after closes on the same bar reports created_after_position_close_in_bar as 1 exactly when the units of those closes reach the held position, and as 0 when they leave part of it open. The rows read that field on the pending entry before it fills, for closes of 0.1 and 0.7 on a position of 0.8, closes of 1 and 2 on 3 and two percentage closes of 50 on 3, and for two controls that leave part of the position open (closes of 0.1 and 0.6 on 0.8, and one close of 0.25 on 0.8). The first row needs the quantity epsilon of the cover compare, because 0.1 and 0.7 sum one unit in the last place below 0.8 in doubles. The other two need the loop to add every close of the bar, not only the first. Each expectation is derived from one rule, written in the test. Refs #803. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
There was a problem hiding this comment.
Graphify reviewed this change.
Code graph out of date: Health delta baseline: last indexed commit f5e929d, 44 commit(s) behind this PR's base. Impact and hotspot numbers below may be wrong.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Not checked: tests were not run; no formal proof of the changed code.
Graphify review — findings
Fixes a reversing strategy.entry placed after a partial strategy.close so it closes the rest of the opposed position and opens the new one. place_entry now sums the units of every same-bar close and treats the book as flattened only when they cover the whole position; otherwise created_after_position_close_in_bar and prior_close_quantity report 0. A close with unresolved size still counts as covering the whole book. Narrows full-close exit cancellation with an independent_only mode that removes only independent-owner exit rows and leaves relative-exit, shadow and anchored-child rows alone. Flat retirement now keeps a named exit placed later on the same bar as a same-side entry of another id, and cancel_reversal_exit_owners takes the reversed entry id so a FIFO exit ends when a same-id entry reverses its lots. Extends the ANY close rule so a reversing entry's exit percentage is taken from the new entry rather than the whole position being replaced. The new test scenarios encode this behaviour, including a case that expects a requested close to leave the old position open.
Worth a look
- Early-entry scenario incorrectly expects a requested close to leave the old position open —
tests/test_adapter_exit_class_any_same_side.cpp:213· Escalate · medium- unverified hypothesis — survived the adversarial refute pass; needs human review
Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1990 functions depend on the 1859 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
find()— 10 callers, 4 callees - new:
check_exits()— 1 callers, 7 callees - new:
check_exits()— 1 callers, 6 callees - new:
run_case()— 1 callers, 6 callees
Verification — 1990 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit f5e929d, 44 commit(s) behind this PR's base.
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1941 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
1420 of 1420 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety (
noncode-change,uncovered-code) — the selection is not trustworthy on its own (see below). CI should run the whole suite.
benchmarks/tests/test_aggregate.py— full-run-safetybenchmarks/tests/test_check_provenance.py— full-run-safetybenchmarks/tests/test_compare.py— full-run-safetybenchmarks/tests/test_public_names.py— full-run-safetybenchmarks/tests/test_run_all.py— full-run-safetybenchmarks/tests/test_run_strategy_feeds.py— full-run-safetydocker/pineforge_sink_finalize_test.py— full-run-safetydocker/pineforge_sink_replay_rq_test.py— full-run-safetydocker/pineforge_sink_replay_test.py— full-run-safetydocker/pineforge_sink_to_json_test.py— full-run-safetydocker/quota_columns_test.py— full-run-safetydocker/quota_convert_test.py— full-run-safetydocker/quota_finalize_test.py— full-run-safetydocker/replay_rq_engine_test.py— full-run-safetydocker/run_json_artifact_window_test.py— full-run-safetydocker/run_json_bin_test.py— full-run-safetydocker/run_json_bounded_transport_test.py— full-run-safetydocker/run_json_bulk_equity_fuzz_test.py— full-run-safetydocker/run_json_bulk_equity_test.py— full-run-safetydocker/run_json_codes_test.py— full-run-safetydocker/run_json_converter_metadata_test.py— full-run-safetydocker/run_json_converter_session_test.py— full-run-safetydocker/run_json_diagnostics_test.py— full-run-safetydocker/run_json_equity_scan_test.py— full-run-safetydocker/run_json_fused_test.py— full-run-safetydocker/run_json_harnesses_test.py— full-run-safetydocker/run_json_metrics_only_test.py— full-run-safetydocker/run_json_misc2_test.py— full-run-safetydocker/run_json_misc_test.py— full-run-safetydocker/run_json_pfbar_test.py— full-run-safetydocker/run_json_provenance_test.py— full-run-safetydocker/run_json_quota_supervisor_test.py— full-run-safetydocker/run_json_replay_test.py— full-run-safetydocker/run_json_selected_sink_test.py— full-run-safetydocker/run_json_sink_lexical_test.py— full-run-safetydocker/run_json_sink_summary_test.py— full-run-safetydocker/run_json_sink_test.py— full-run-safetydocker/run_json_sink_vectors_test.py— full-run-safetydocker/run_json_spool_work_test.py— full-run-safetydocker/run_json_stringio_test.py— full-run-safetydocker/run_json_symbol_feeds_test.py— full-run-safetydocker/run_json_syminfo_test.py— full-run-safetydocker/run_json_trade_scan_test.py— full-run-safetydocker/run_json_window_budget_test.py— full-run-safetydocker/run_json_wrapper_work_test.py— full-run-safetyinclude/pineforge/native_run_spec.hpp— full-run-safetyscripts/derive_corpus_feeds_self_test.py— full-run-safetyscripts/fingerprint_self_test.py— full-run-safetyscripts/run_corpus_self_test.py— full-run-safetyscripts/test_abi_receipt_skips.py— full-run-safety- … and 1370 more
non-code file(s) changed (
CHANGELOG.md,tests/CMakeLists.txt) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
· 3 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| CHECK(got.open_at_end == expected.open_at_end); | ||
| } | ||
|
|
||
| void check_exits(const Scenario& spec, const std::vector<Bar>& bars, const Observed& got) { |
There was a problem hiding this comment.
check_exits()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| CHECK(got.open_at_end == expected.open_at_end); | ||
| } | ||
|
|
||
| void check_exits(const Scenario& spec, const std::vector<Bar>& bars, const Observed& got) { |
There was a problem hiding this comment.
check_exits()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| CHECK(got.exit_from_bracket == (exit_id == "TP" || exit_id == "SL")); | ||
| } | ||
|
|
||
| void run_case(const Scenario& spec) { |
There was a problem hiding this comment.
run_case()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Fixes the remaining reversal and exit-sizing cases reported in #803, on top of the fix already in this train (#817).
Product changes (
src/source/pine_adapter.cpp,include/pineforge/source/pine_adapter.hpp):Data: re-pins the frozen digests that the reversal exit changes move (
tests/test_adapter_quiet_bar_pinned.inc,tests/test_adapter_live_state_equivalence_data.hpp). No other pins move, and the x86_64 layout probe is unchanged.Tests: new
test_adapter_exit_class_any_same_side,test_adapter_partial_close_reversalandtest_reversal_same_id_exits. Their expectations are derived by arithmetic.CHANGELOG.mdhas one entry per case.🤖 Generated with Claude Code