Repository navigation
Fill long stop-limit orders whose limit sits on the price grid - #721
Conversation
…trols A long stop-limit whose limit is a cent price (99.99, 100.07, 1.15 on a 0.01 grid) is refused by the kernel on observed ticks: the fill price is projected to k * tick, and k * 0.01 sits one binary64 ULP above the decimal limit, so the buy fill lands past the immutable limit. test_tick_fractional_stop_limit pins this at tick 0.01: three long rows (red on main, trades=0) and the admitted rows that must stay as main books them (short 1.15 keeps the product 1.1500000000000001, short 100.01, whole prices, 0.07, 0.29, and an off-grid limit 99.995 that the product already admits). Each case checks its own binary64 geometry before any order runs. test_tick_grid_canonical_controls pins ticks 0.05, 0.25 and 0.0004, where every product is admitted: booked prices keep the product, and both files are green on main for those rows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In the stop-limit branch of source_limit_fill, the projected fill (directional_tick of the print) can sit one binary64 ULP past the immutable limit, which the kernel refuses. Reuse the repair the ordinary limit branch already makes: only when the projection is past the limit, and only for a print on the chart grid, book that print's grid spelling. An admitted projection is kept bit for bit, so booked prices elsewhere (including every short row and every product the kernel admits) do not change. directional_tick, nearest_tick, the kernel and every pin table are untouched. Not changed by design: a short 1.15 stop-limit still books the product 1.1500000000000001; the hunk fires only where main's value is refused. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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; formal verification proved 0 of 1 changed function(s) (1 not verified).
Formal verification. PR-changed functions: 0/1 verified (0 proven, 0 may-equivalent, 0 distinguished) · 1 not verified (1 unsupported).
Not verified on this run: resolve\_source\_terms (unsupported).
Graphify review — findings
Fixes stop-limit entries on observed ticks that silently never filled when the tick-grid projection of the print landed one binary64 ULP past the order's immutable limit (e.g. a buy limit at 99.99 on a 0.01 grid projected to 99.990000000000009), which the kernel rejected as invalid terms with no error. In resolve_source_terms, if the projected fill price is outside the limit and the print is on the chart grid, the fill now books that print's grid spelling instead. Projections the kernel already admits are kept bit for bit, so existing fills don't change. Adds test_tick_fractional_stop_limit, which covers the refused rows (99.99, 100.07, 1.15) plus the admitted controls, and registers it and test_tick_grid_canonical_controls in the source-layer build.
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 — 701 functions depend on the 615 functions this change touches.
Health — grade B; 10 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):
consume_matched_request— 2 callers, 43 callees (high)source_trigger_threshold()— 12 callers, 7 callees (high)match_path— 2 callers, 35 callees (high)execute_current— 2 callers, 24 callees (high)invoke_callback— 4 callers, 12 callees (high)pump_batch— 4 callers, 12 callees (high)deliver_confirmed_script— 2 callers, 23 callees (high)directional_tick()— 10 callers, 4 callees (high)- …and 2 more
Verification — 701 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: 653 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify resolve\_source\_terms.
The verifier did not have enough to check resolve\_source\_terms, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: unsupported: member function — receiver/self (`this`) not synthesizable from the simple-type literal pools — abstaining rather than guessing a receiver
The comparison-side fix adds a net 16 lines to src/source/pine_adapter.cpp, which moves every line the published pages cite after it. This commit re-anchors those citations only: the citation digits in docs/adr/0001-kernel-adapter-boundary.md, docs/design/arena-bound.md, docs/design/native-feature-parity.md and docs/native-refactor-progress.md, moved by the repository's own tools (check_doc_anchors.py --fix and reanchor_doc_citations.py against main) and, for the three citations those tools left as ambiguous or comment-only, by the same sixteen lines after checking that the cited source line is byte-identical on main and on this tree. No prose, symbol name or source file changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fill long stop-limit orders whose limit sits on the price grid
What was wrong
A stop-limit order that has been triggered fills at the print, moved to the order's tick grid. On a 0.01 grid the tick product
9999 * 0.01is99.990000000000009, one unit in the last place above the decimal price99.99. A long (buy) stop-limit whose limit is that decimal price therefore got a fill price above its own limit, and the kernel refused the fill: the order produced no trade, with no error. The same happens at100.07and1.15on a 0.01 grid.This fixes the long stop-limit case of #715.
The change
One hunk in the stop-limit branch of
source_limit_fill(src/source/pine_adapter.cpp), using the same repair the ordinary limit branch already makes. Only when the projected fill price is past the order's immutable limit, and only when the print is on the chart's price grid, the fill books the print's grid spelling, which equals the limit. Every fill the kernel already admitted is kept bit for bit. A limit that the price truly did not reach is still refused.directional_tick, the nearest-tick sites, the kernel and every pinned table are untouched.Effects to expect
mainrefused. On a 44-trade ETHUSDT 15-minute strategy with a stop-limit entry (2024-01-01..2024-02-01, spot bars),mainproduces 41 trades and this branch produces 44. All 44 match the reference trade list on entry time, side, exit time, quantity and entry and exit prices at the printed places. The 41 tradesmainalready had are unchanged in every field except the entry counter.main, on every grade field (outcome, tier, match percentage, trade-count delta, entry, exit and PnL error percentiles, trade counts). The trade sets of the four probes whose names mention a stop-limit are byte-identical. The population has no probe on the fixed path, so the evidence for the fix is the 44-trade comparison above and the failing-then-passing rows below, not a population change. The threemainsweeps ran with different codegen heads, and grades are a coarse comparison, so this shows no change in grade, not identical trades on all 7,989 probes.1.15on a 0.01 grid is admitted onmainand books the tick product,1.1500000000000001, one unit in the last place above the print. This branch leaves it as it is, and a control test pins it. The reference exports print prices at the instrument's decimals, so they cannot show whether the reference books that short at the product or at the decimal. If that half is ever changed, this control changes by design.Tests
test_tick_fractional_stop_limit(new): long limits at 99.99, 100.07 and 1.15 on a 0.01 grid, plus admitted rows that must book exactly whatmainbooks: short 1.15, short 100.01, whole prices, 0.07, 0.29, and an off-grid limit at 99.995. Each case checks its own binary64 geometry before any order runs. Onmainthe three long rows fail: 12 failing lines, each a missing trade. On this branch it passes.test_tick_grid_canonical_controls(new): ticks 0.05, 0.25 and 0.0004, where every product is admitted. It passes onmainand on this branch, and pins that booked prices keep the tick product.mainand on this branch (302 passed, 4 skipped, and the same single failure on both).🤖 Generated with Claude Code