Repository navigation
fix(security): tighten TF validation, split error messages by failure mode - #3
Merged
Merged
Conversation
Split the previously-conflated lower-TF diagnostics in validate_security_timeframes into four distinct failure modes: - invalid timeframe literal (e.g. "abc") names the offending input - request.security with a finer-than-input TF is now rejected even when the ratio is an integer divisor; the error directs callers to request.security_lower_tf - request.security_lower_tf with a non-integer-divisor ratio reports the actual ratio and explains why bars cannot be subdivided - request.security_lower_tf with an HTF-or-equal target is rejected with a message naming the lower-TF API requirement Document that the script_tf >= input_tf invariant is enforced separately in BacktestEngine::run() (engine_run.cpp ~line 199). Update existing test_request_security harnesses that relied on the old behavior of regular request.security silently emulating LTF.
Add test_security_tf_validation covering the seven invariants: HTF accept, finer-than-input request.security reject (with hint to LTF API), LTF non-divisor reject, LTF integer-divisor accept, LTF HTF-target reject, invalid TF literal reject, same-TF accept.
luisleo526
added a commit
that referenced
this pull request
Sep 16, 2026
…tion and carry the entry-bar masks (R4-D L10b) The legacy owner (ab9714b pine_scheduler.cpp: process_pending_orders -> update_per_trade_extremes -> process_margin_call) sampled every open lot's high/low/close for the bar before liquidating it, so a lot flattened by a margin call on its first extreme still owns the rest of the bar's favorable excursion. On the switched route the generic excursion fold stops when the lot closes, so corpus/anomaly-equity-mirror-strategy-equity-01 trade #3 reported favorableUsd 21.28 where the owner reports 27.34 (final-sweep class H16, 271 probes, 262 of them short entries). The adapter now re-runs the owner's per-lot sampler at margin-call submit (full bar on the scheduled path; the traversed prefix only under process_orders_on_close, pine_fills.cpp:2009-2023), and the source host sets the owner's entry-bar masks (pine_fills.cpp:42) on the lot of a priced entry applied mid-bar so that sampler never credits pre-fill range. tests/test_l10b_excursion_parity.cpp pins the 04-21 margin-call bars and a short percent-of-equity same-bar shape with the owner's literals. Executor: Grok (brief W14a); verified by root (27/27, 455 ctest, hash coverage OK; full corpus replay unchanged: the corpus lane has no margin-call divergence). Kernel untouched. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01ADabocUiCAh8YsUEsVNspC
luisleo526
added a commit
that referenced
this pull request
Sep 16, 2026
…ip-prep sibling fill first (R4-D L10e) After a same-pass close flattened the position, the native route forced the following opposite stop entry to the close's fill price (the open print) and let a later same-direction stop consume the from-flat carry. The legacy owner (ab9714b pine_fills.cpp:8002-8018, 3687-3788) books such a stop at its stop level unless the opening print already gapped through it, and when an older flip-prep sibling is also touched on the bar that older stop fills first. Corpus scenarios order-opposite-entry-close-same-pass-01 and pyramid-flip-stop-pyramiding-2-01 now replay identically to the owner (trade #3 short at 1800.00, not 1803.22); the local corpus replay goes from 259 to 261 identical scenarios with no new difference. tests/test_l10e_opposite_entry_close_same_pass.cpp pins both scenarios' trade #3 with the owner's literals on the exact bars. Executor: Grok (brief W15b); verified by root (36/36, 455 ctest, hash coverage OK). Kernel untouched. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01ADabocUiCAh8YsUEsVNspC
luisleo526
added a commit
that referenced
this pull request
Sep 17, 2026
…sidual lot (R4-D L10y) yukozb-gold-ny-orb-v19-close-20-00 #2/#3 and ahtisham-ee-decoded- volatility-expansion-ahtisham #126/#127: with default_qty_value=100 the legacy owner admits the entry and books it as a main lot plus a residual margin-call lot closed again on the entry bar; the switched route refused the whole entry. The adapter now sizes and admits the full-equity entry the way the owner's fill-time affordability and margin-call slice do, and samples the residual lot's open extremes like the owner. Both population reproductions replay identically to the owner. Pinned with embedded bars in tests/test_l10y_full_equity_entry_split.cpp (W21c, qwen3.8-max). 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 26, 2026
…blings its leg had (R5 lane INT26, round 2: the gate sweep's leaver) INT26's gate sweep (exp-r5-int26-20260926 against rebaseline e69aa1f7) refused the merge on one leaver, scrapper:data-BINANCE-BTCUSDT/standard/vasudevshenoy-manoj-betrayed-me (BTCUSDT 15m, process_orders_on_close, pyramiding 0; stop entries 'Buy'/'Sell' with strategy.exit stop/limit brackets, a pending entry cancelled when the next bar does not trigger it): 821 of TradingView's 821 trades on main 0d76a09, 820 on ebe6748 (canonical 100 -> 99.9). Bisect (the probe's own feed, spark, one build per pick boundary): a6e6a00 and 2922ade book 821, e1d7a98 -- R5 lane PAR-ORDERS, H-MEASURE Finding 6b, a process_orders_on_close stop entry its placing close already reached fills at that close -- books 820, and so does every later pick. Mechanism, on 2025-08-03 10:00 UTC. The 09:45 close cancels its pending 'Buy', places it again (stop 113689.29) and re-issues 'Buy Exit' (limit 113771.41); the re-issued legs keep their older book slot, so on the 10:00 bar the pre-script drain settles the limit on its parent's touch bar -- after that bar's receipt read. The 10:00 bar closes at its high 113771.47; its body cancels 'Buy' again and places it at that high with a fresh 'Buy Exit' (limit 114004.53), and since e1d7a98 the close pass fills that re-entry at the close, as TradingView does (#274), so the fresh legs are working when 10:15 opens. The first limit's terminal receipt is read only then, and observe_terminal_receipts' sibling cancel withdrew every working leg of the same exit id, from_entry and origin -- the fresh bracket's included, both legs at origin 0. The re-entry ran on to the 15:15 session close and swallowed TradingView's #275 (11:45). Before e1d7a98 the re-entry rested to 10:15, its legs still staged, so the late read found no sibling to take. The pick is right; the sibling cancel was not. Fix, adapter only: cancel_bracket_siblings takes the ordinal the terminal receipt executed at when the observer reads it, and skips a matching leg of a later strategy.exit call (another command_sequence) that the kernel accepted after that ordinal (its Birth::acceptance_ordinal, read through native_working_requests() only when such a leg is among the matches): that leg is the same id's later order. A leg of the filled leg's own call is its sibling however late it was accepted -- the drain submits a bracket's legs one at a time, executing each, so a stop can be accepted after its limit filled -- and goes as before (a review of the first form, which skipped every leg accepted after the fill, found that case). The close passes that call it synchronously pass nothing and are unchanged. No state is added. The probe (spark, generated.cpp of codegen a4259656, the lane feed 6b54c44a): 821 trades, canonical rubric excellent, countAbsDelta 0 (820 / strong before). TradingView's #273-#275 exact: 10:00 113689.29 -> 10:00 113771.41; 10:00 113771.47 -> 11:00 114004.53; 11:45 114005.84 -> 15:15 113844.70. 819 of 821 trades exact to the minute and cent on both legs (main: 771; the two others, #1 and #3, differ on main too). Witness: tests/test_pooc_stop_reentry_bracket_tapes.cpp replays three `lab tv --no-note` tapes of synthetic scripts of the same shape on NYSE:F 15m (tests/fixtures/pooc_stop_reentry_bracket; tv_trades e0447b53 / b7e89ce1 / efbd25e3): three flat signal closes that each cancel and place again a stop entry with its bracket, 10c beyond the close, then 1c (the next bar fills the entry and then the limit and closes flat), then at that close (filled there), long on bars 39-41 and short on bars 44-46; TradingView books the same four rows on the chart and under the magnifier. Every row is compared field by field, with the end book and its working bracket legs. - int26-reentry-bracket-chart: all four rows, flat; the short is the leaver's shape. - int26-reentry-bracket-mag records a divergence the fix does not touch (the same before and after): there the drain leaves the short's retained limit to the flush below the body, whose book still holds the short, so the re-entry is never placed -- rows 1-3 exact, both books flat. - int26-reentry-noexit-chart, the control: the short's re-entry places no strategy.exit (TradingView's row 4 still open at the end); the short bracket's stop, accepted after its limit filled, must still be withdrawn. Fail-before: against ebe6748 2 of 27 checks fail (the chart tape: 3 rows against 4, short 100 against flat); against the first form of the rule 1 of 28 (the control's stop left working); after, 28 of 28. Floors: one source-bound row. RELEASE_MIN_TESTS 702 -> 703, EXCLUDED_REGISTERED_MIN 683/683/692 -> 684/684/693 (and test_ci_verify's expectation, docs/ci.md). KERNEL_MIN_TESTS unchanged. docs/ci.md's sentence "The PR registration floors at INT26 are 683 for Debug and sanitizers and 692 for native" is rewritten with the new counts. Docs: the design page's PAR-ORDERS paragraph records the rule and the magnified divergence; the CHANGELOG 1.0.0 entry's process_orders_on_close item adds it for a user. Every doc anchor carried onto this tree by the exact line map from ebe6748 (135 moved); the three citations of ci_verify.py's RELEASE_MIN_TESTS line (CONTRIBUTING.md, contributing-llm.md twice) set by hand, 390 -> 393. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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
Hardens `validate_security_timeframes` (`src/engine_security.cpp`) for all `request.security[_lower_tf]` calls.
Invariants now enforced
Pre-existing leak fixed: `request.security("X","5",...)` with `input_tf="15"` was silently going through LTF emulation path. Now correctly gated on `lower_tf_array_requested`.
Test plan