Repository navigation
Fill a stop-limit entry at the closing tick when process_orders_on_close is on - #725
Conversation
…sing tick With process_orders_on_close = true, a stop-limit entry whose limit and stop both equal the bar's close does not fill on the closing tick, while a market, a limit-only and a stop-only entry at the same close do (the stop-limit fills on the next bar, or later after a gap). test_pooc_stop_limit_entry_at_close covers the stop-limit entry at the close next to its market, limit-only and stop-only controls. test_native_current_activation covers the kernel operation the fix adds, activate_current_stop_limit. test_native_dormant_book_transcript is adjusted for the same change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ose is on With process_orders_on_close = true, a stop-limit entry whose stop is reached by the closing quote did not fill at that close when its limit is also marketable there, because the stop-limit was only activated at a later evaluation point. The kernel gains activate_current_stop_limit: it activates only the named pending stop-limit when the current quote reaches its stop, returns nothing when it activated or was already live, and on a refusal changes nothing; it does not execute the limit or evaluate any other request. The adapter uses it for the stop-limit entry at the confirmed close, and the matching check treats that activating step as a current-execution step, so the limit is tried at the same close. Market, limit-only and stop-only entries take the same path as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…limit activation The native source guards refused the stop-limit activation added for the closing tick: the native-versions guard requires exactly two code occurrences of activation_grid(*spec) (the trail observer and the single hand-off of a trigger transition to the core), and the head had three (the new stop-limit activation was the third); the C-surface guard wanted a record for the new native member; and the new assignment site had to be in the reviewed site inventory. trigger_step() now holds the single hand-off of a trigger transition to the request core with the run's activation grid, in the current mutation mode. The path matcher and activate_current_stop_limit both call it; the trail observer keeps its own. The matcher passes the same arguments in the same order inside the same try block as before. The current activation used to call the grant-free apply_trigger / prepare_trigger forms, which forward to the grant forms with open_grant false, and now passes open_grant false to the same grant forms. Behaviour is unchanged. native_c_api.h gets a COVERAGE row for activate_current_stop_limit saying why it has no C spelling (its one caller is the Pine host; a C host's stop-limit activates where the matcher reaches its stop; strategy_native_execute_current_v1 takes a market request only). ci/cr150-source-sites.json is regenerated with the repository's own --emit-site-inventory for this tree: it holds the new record plus the order the emitter now produces (the previous order had drifted). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The stop-limit fix moves lines in the sources that the design pages cite by file:line. The repository's own tools (scripts/check_doc_anchors.py --fix and scripts/reanchor_doc_citations.py --ref main) move the citations; nothing is edited by hand and no code changes. Before: 310 bad anchors. After: all 2344 anchors resolve to their symbol. Eight documentation files, 244 lines changed on each side (no line is added or removed).
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; no formal proof of the changed code.
Graphify review — findings
Regenerates the CR150 source-site manifest so its entries follow the current source ordering. This moves the reader, provider, reader_ and IdTableReadCache allocation entries and the fold_cohort_exposure/verify_cohort_exposure assignment entries in pine_adapter.cpp. It also registers a new held = ... assignment site in NativeExecutionConsumer::activate_current_stop_limit.
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 — 4178 functions depend on the 3164 functions this change touches.
Health — this change adds coupling hotspots:
- new:
check_trade()— 7 callers, 3 callees - new:
check_activated_limit_cancelled_before_touch()— 1 callers, 6 callees - new:
check_activated_limit_without_touch()— 1 callers, 6 callees - new:
check_activated_stop_limit_rests_as_limit()— 1 callers, 6 callees - new:
check_close_entries()— 1 callers, 6 callees - new:
activate_current_stop_limit— 0 callers, 20 callees - new:
main()— 0 callers, 9 callees
Verification — 4178 functions in the blast radius were not formally verified this run (proofs are advisory here).
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: 4103 function(s) in the blast radius were not formally verified this run
· 7 grounded finding(s) anchored inline below.
| std::optional<NativeCurrentRefusal> validate_current_execution( | ||
| const BacktestEngine& engine, const NativeCurrentExecution& command) const; | ||
| public: | ||
| std::optional<NativeCurrentRefusal> activate_current_stop_limit( |
There was a problem hiding this comment.
activate_current_stop_limit
fans out to 20 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| require(probe.last_run_status() == 0, "run status must be successful"); | ||
| } | ||
|
|
||
| void check_trade(const Probe& probe, int index, int entry, double price, int exit_submission) { |
There was a problem hiding this comment.
check_trade()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| require(!trade.open_at_end, "expected a closed trade"); | ||
| } | ||
|
|
||
| void check_close_entries(Input mode, Entry kind, bool is_long = true, int slippage = 0) { |
There was a problem hiding this comment.
check_close_entries()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return input; | ||
| } | ||
|
|
||
| void check_activated_stop_limit_rests_as_limit(Input mode, bool is_long) { |
There was a problem hiding this comment.
check_activated_stop_limit_rests_as_limit()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| } | ||
| } | ||
|
|
||
| void check_activated_limit_without_touch(Input mode) { |
There was a problem hiding this comment.
check_activated_limit_without_touch()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| require(touched.pending() == 0, "touched limit must leave no pending entry"); | ||
| } | ||
|
|
||
| void check_activated_limit_cancelled_before_touch(Input mode) { |
There was a problem hiding this comment.
check_activated_limit_cancelled_before_touch()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| } | ||
| } // namespace | ||
|
|
||
| int main() { |
There was a problem hiding this comment.
main()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Native-live on e0c7cc5 (tree 5e5f2f3d), with private fixtures absent: live-tsan 120/120, live-sanitizers 120/120, and native shards 1-4 415 / 377 / 396 / 416 (1604 rows), all passed. Main has 120 / 120 / 414 / 376 / 396 / 416; the two extra rows are the two tests this change adds. |
Fixes #707.
What
With
process_orders_on_close = true, a stop-limit entry whose stop is reached by the closing quote did not fill on that close even when its limit was also marketable there. The stop-limit was only activated at a later evaluation point, so it filled on the next bar (or later after a gap), while a market, a limit-only and a stop-only entry at the same close filled on the close.This change fills such a stop-limit entry on the closing tick. The kernel gains
activate_current_stop_limit: it activates only the named pending stop-limit when the current quote reaches its stop, returns nothing when it activated or was already live, and on a refusal changes nothing. It does not execute the limit and does not evaluate any other request. The adapter calls it for the stop-limit entry at the confirmed close, and the matching check treats that activating step as a current-execution step, so the limit is tried at the same close. Market, limit-only and stop-only entries take the same path as before.Commits
process_orders_on_closeat the closing tick, next to its market, limit-only and stop-only controls; the kernel operation; one existing transcript test adjusted for the same change.src/native_order.cpp,src/source/pine_adapter.cpp, the native consumer).Evidence
PASS (no-improve-no-regression): 1009 of 1009 evaluated cases comparable, no regression, and no probe moves.Classification of the new reviewed site
The one new assignment site on
heldinNativeExecutionConsumer::activate_current_stop_limitis astd::shared_ptr<const LiveRequest>: one request row per call, with no growth in bars, trades or events. It is recorded in the reviewed site inventory (3731 sites, was 3730).TradingView evidence
A TradingView recording of the stop-limit script from the issue (with
process_orders_on_close = true) was joined to native runs, matching trades by entry time and order id.🤖 Generated with Claude Code