Repository navigation
Fix fill recalculation, declared quantity steps, and intraday request grids - #727
Conversation
…d in a flat fill-recalculation book With calc_on_order_fills on and a flat book, a priced entry is held until the end of the script evaluation. A later entry with the same id is accepted first, and the held older one is then submitted after it and replaces it, so the market entry is lost and a limit the feed never reaches stands in its place. TradingView keeps the later market entry (two short trades, quantity 1 then quantity 2) for the equivalent script, with orders processed at the next open and on close. test_coof_reused_id_reentry runs both process_orders_on_close settings over a constructed feed with expectations derived by hand in the header: the two rows matched by TradingView captures, two hand-derived controls with the old id still open, and three metamorphic controls (market call only, fill recalculation off, different ids). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
submit_or_replace already removes an earlier same-id request from the queued coof requests, bracket legs and delayed market orders when a later one is accepted. The entries a flat calc_on_order_fills evaluation holds until its end were not in that list, so the end-of-evaluation flush submitted the older priced call after the newer one and the core replaced the newer instruction with it. Remove a held row of the same id when the accepted request is a later opening and the row is an opening entry issued before it, with no paired reversal parent and no retained parent topology (those keep their own submission rules). The erase runs only after the core accepted the later request. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…imit, correct the capture wording Header: say which controls are premise checks (market call only, fill recalculation off: they pass with and without the cleanup by construction) and which can fail (different ids, and the two rows with the old id still open). Say that the guard keeping a held entry issued after the accepted one is defensive and unexercised, with the reason: with fill recalculation on, an entry the core accepts while an evaluation holds rows is a later call of that evaluation, and the queues released ahead of the held rows hold no entry. Rows with the old id still open: assert what is pending at bar 25, derived from the engine's cap rule (nothing with next-open fills; with close fills the priced add rests over the cap as the one pending limit at 191). No capture shows TradingView's pending orders for these rows; the header says so. Capture wording: with process_orders_on_close on, the capture lists the second trade on bars 20 and 30, the bars whose closes it fills at. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The cleanup added for a held same-id entry ran for any accepted opening, which includes strategy.order. Narrow it to an accepted entry replacing a held earlier entry of the same id, the shape the change is for. An accepted strategy.order call leaves a held entry alone, as before the cleanup existed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…l open The rows with the old id still open asserted what is pending at bar 25. That value comes from the engine's own cap rule and no TradingView capture shows it, so checking it only restates the engine. Keep printing the count and say in the header that it is engine behaviour, not a TradingView claim. The trade, lot and size checks of those rows stay, and the pending-zero check of the rows with the old id closed stays. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…defect test_adapter_quiet_bar pins values harvested from the base tree. Eight runs of two seeded random scenarios (Random02 and Random20, each with magnifier off and on and with recording off and on) move with the held-entry cleanup, because both scenarios contain the shape the cleanup changes: with calc_on_order_fills on and a flat book, one evaluation issues a priced entry and then a market entry of the same id (Random02 at bar 24: a cancel, a stop entry at 105.50, then a market entry; Random20 at bar 54: a short stop entry at 93.25, then a market short). The base tree filled the held priced entry and lost the later market call; TradingView keeps the later call for this shape. After the change Random02 fills the market entry at the next open (103.5 instead of 105.5) and Random20 fills it at the bar's close (94 instead of 93.25 one bar later). The values come from the repository's own harvest (this test compiled with -DPINEFORGE_QUIET_BAR_HARVEST, run once on this tree); the pinned file was compared with the harvest row by row: only these eight rows differ, each with one changed trade (Random20 also fewer source callbacks: 67 to 65 and 70 to 67), and the other 268 runs of the 276 are byte-equal to the base tree. The rows were replaced by a program from the harvest output, not edited by hand. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
TradingView floors an order quantity to the symbol's quantity step (syminfo.mincontract). The engine floors only when the caller declares the key qty_step and ignores a declared mincontract, so a full-equity order keeps its unrounded quantity and, at one bar of the measured tape, no position opens at all. test_qty_step_mincontract_tapes runs the limit-only control of the #707 reporter (BINANCE:BTCUSDT 60, 2024-01-01 .. 2024-03-01, 29 trades, tape sha256 prefix e0085eab) against the tape's own quantities and exit times: - Q1 declares mincontract = 0.00001 alone: expected to fail until the fix; - Q2 declares qty_step = 0.00001 alone, Q3 and Q4 declare conflicting values in both orders: the explicit qty_step wins. These pass on the current main. Every expected quantity is the tape's decimal text, compared exactly; the step is a declared parameter, never inferred from the symbol or the tape. The tape is a private fixture: registered in the guarded private-fixture form (a SKIP placeholder when absent), listed in tests/private_fixture_rows.txt and in the removed-path inventory, with the count pins and the line pin updated. The public bars file is committed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…eclared set_syminfo_metadata now takes a finite mincontract > 0 as the quantity step while the handle holds no qty_step key. An explicit qty_step (including zero or an invalid value) wins in either setter order. The metadata is per handle, and a handle that declares neither key is unchanged: nothing is inferred from the symbol. The patch is SM-CX's candidate (src/engine_consumer.cpp, 17 lines); the rows of test_qty_step_mincontract_tapes decide it: Q1 turns green, Q2 to Q4 stay green. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
test_syminfo_mincontract_qty_step covers the declared-step contract with literals only: a mincontract alone sets the quantity step on a fresh handle, an explicit qty_step wins in either setter order (including zero and invalid values), an invalid mincontract never sets or clears a grid, a handle without a key keeps the zero-grid quantity bits, the mincontract getter returns the supplied value, and the metadata lives per handle. test_qty_step_qna_rows runs the entry with quantity na and 100 percent of equity sizing for the 59 signal rows whose next bar opens above the signal close, against TradingView's own placed / declined decision and quantity for each. Its rows are a private fixture read at run time (a SKIP placeholder when absent, listed in tests/private_fixture_rows.txt and the removed-path inventory, count and line pins updated); no tape value is embedded in a public file. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…'s limit A declared qty_step, or a finite mincontract > 0 while no qty_step is declared, sets the order quantity grid: stated at strategy_set_syminfo_metadata (pineforge.h) and at the two engine.hpp declarations of the step. The public unit test's comment now calls its rows the documented floor rule and names the limit: the engine floor adds a +1e-6-step epsilon, and quantities near a step boundary beyond the 29 trades and 59 rows of the private tests are not covered. Comments only; no line count of engine.hpp changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…l recalculation With calc_on_order_fills = true, an explicit strategy.close followed by a reversing strategy.entry (pyramiding = 1) lets a repeated entry of the same id add a second lot on the same tick: the first lot's fill is still queued as an applied notification when the repeat is evaluated, so the slot count does not see it. test_coof_close_entry_pyramiding_slot builds a constructed feed (one long, then a close and a reversing short, then a repeated short request of 3, then close-all) and derives the expected result by hand in its header: two closed trades, short quantity 2, at most one short lot, final position 0. Controls: fill recalculation off, and no explicit close, keep their trade lists; an input mutant with pyramiding = 2 must differ from the one-slot result. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ulation accepted_entries_in_cycle skipped an entry's own id and never counted a pending limit entry, so a synchronous fill whose applied notification was still queued did not occupy a slot. With calc_on_order_fills = true, an explicit close followed by a reversing entry then admitted a repeated entry of the same id as a second lot (pyramiding = 1). An opening placement that is already settled, meaning its handle is in its cohort's opened list or the host reports an open lot whose origin is the placement's incarnation, now occupies a slot even for the same id or a limit order; an unfilled replacement has no physical origin and does not. place_entry also no longer skips the cap for a priced entry under process_orders_on_close while fill recalculation is active. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The two rows that reuse a closed id (explicit close, then the same id entered twice on one bar) fail identically on main and with the slot fix, so they do not belong in this test: the fix does not act on them. They stay as an open question outside the registered tests until the engine behaviour for that shape is settled. No other check in the file changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
test_coof_pyramiding_slot_large_book repeats the settled-reversal scenario of test_coof_close_entry_pyramiding_slot with 150 pending same-side sell limits of other ids in the book and pyramiding counts of 1, 2 and 200. The slot count is derived by hand in the header (each pending limit counts 0, the settled short counts 1), and the trade list with the pending limits equals the one without them. It checks results at a large book; it does not assert a bound on the cost of the open-lot visit, which no counter exposes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ing-slot fix kTranscriptDigests[16] (reversals, seed 1780393, magnifier off, margin off, pyramiding 1, process_orders_on_close off, calc_on_order_fills on) moves with the change that counts a settled opening against the pyramiding cap during fill recalculation: after the reversal fill the repeated same-id entry is refused where it was accepted before. The new value comes from the repository's own generator (the harvest build of test_adapter_live_state_equivalence, run once on this tree; the harvested table was compared with the committed table row by row: only row 16 differs, 108 rows both sides). The row's comment changes with it: 176 trades, 668 commands, 627 rows placed became 179 trades, 667 commands, 632 rows placed. The seed-1780393 transcript runs on synthetic bars. The bar-127 side was checked against TradingView on a real-bars source of the same order shape (an explicit close plus a same-id short at pyramiding 1, BTCUSDT 60-minute, 2024-01-01 to 2024-03-01): TradingView refuses the second short lot, as this change does. It is not a TradingView tape of the transcript itself, and it is not claimed class-wide. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The probe counted the book's resting limits by comparing each row's limit price with the level the call gave, exactly. The host stores that sell limit half a tick lower (measured on a CLOUD build: 999.995 for a call at 1000 with a mintick of 0.01; why is not established here), so the helper counted 0 and the two assertions on the working-rest count failed in all 16 cases while every trade, size and lot check passed. The count now identifies a rest by its label and the limit kind and matches the level within one mintick. The expected counts are unchanged: 150 resting limits at the probe and on bar 25 (a build with a dump of the host's rows reported 150 working rows, each labelled rest<k>, before this change). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…er threshold The previous commit matched the resting limits within one mintick because the stored level (999.995 for a call at 1000, mintick 0.01) was measured but not explained. It is the adapter's trigger rule: source_trigger_threshold hands the core a limit trigger at the grid level plus (upward ? -0.5 : 0.5) times the tick, upward for a sell limit (src/source/pine_adapter.cpp, the function that starts at "double source_trigger_threshold"). The helper now expects exactly that level (the call's level minus half a mintick, to rounding) instead of any level within a tick, so it can still see a one-tick error in the stored level. The mintick is a named constant used for the probe's syminfo and the expected level. The comment says the probe places only sells. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…frames Adds the measured TradingView rows that a fix of the requested-bar grid (intraday_bucket_open_ms and the paths that divide the continuous clock) must turn green, and the regression rows that must stay green: - test_requested_bar_grid_tapes: BTCUSDT 60, 2024-01-01 .. 2024-03-01. Requested 300 / 420 / 600 with a bar_index payload, 300 / 420 with an open payload and the raw requested open time and index of the 300 trace tape; the dividing widths 120 / 180 / 240 / 360 / 480 and open 120 as regression rows. Every expected value is read from the TradingView tape (quantity, exit time, raw ro / ri), never from engine output; a row fails unless all 205 entries join and are equal. The tapes are private fixtures: registered in the guarded private-fixture form (a SKIP placeholder when absent), listed in tests/private_fixture_rows.txt, pins updated in scripts/ci_verify.py and its test. The public bars file is committed. - test_requested_bar_grid_private_tapes: the equity-session rows, lab-only (PINEFORGE_LAB_ONLY_TESTS, default OFF). The feed and tapes come from environment variables and an absent input is a failure, never a skip. The rows are expected to fail on the current main for the non-dividing widths and to pass for the dividing ones; they were written before the fix. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Use one session-day bucket key, open and capped end throughout intraday aggregation, timeframe changes and the empty-session close path. Retain the observed first input stamp before relabeling for late-open prefixes. Preserve one completion for the existing pre-open-prefix control. Keep legacy daylight-saving offset resolution. Characterize its known session-less transition edge as an engine expectation, not a TradingView measurement. No instant-exact offset change or new refusal is introduced.
Clamp outside-session bucket ends to the existing open after applying the session-day end cap, preserving the bucket key and open. Pin the valid final session bucket, the Pine time_close consumer and UTC dividing-width behaviour as engine expectations; these rows are not TradingView measurements.
Keep the two explicit-session tape rows and their strict comparisons in the shipping target. Retain the unchanged default-context cases separately for the caller-metadata work. Count actual rows and failures and omit the deferred rows from the runtime summary; no skip or expected-failure path is added.
Reconcile the quantity-step and requested-bar inventories in their reviewed insertion order: 379 rows, 2704 directories, 10235 files and 12939 paths. Locate all evidence citations by their unchanged CMake content. Keep every other key, comment and product line unchanged. Rebuild both scientific arrays with their repository harvest mains on the combined semantic tree. All 108 equivalence and 276 quiet-bar values match the member commits; against main only equivalence row 16 and the eight documented seeded runs differ. No digest was guessed or edited by hand. Re-anchor the documentation to the combined source, including the renamed intraday bucket helper and the manually selected adapter references. The standalone grid-member citation refresh is a separate metadata-only input commit; the final citations here derive from this tree's actual content.
The sized-runner CI change intentionally supersedes three earlier routing descriptions; no test or checker is changed by this empty naming commit. - 482b000 introduced the statement that both native-live sanitizer profiles use fixed bare scale-set names directly in runs-on. docs/ci.md:75-77 now separates the direct bare-scalar jobs from the native profile matrix; docs/ci.md:1636 identifies its exact matrix.runner values. The two live profiles no longer share one literal runs-on value. - c85a40b introduced the statement that live-sanitizers and live-tsan both build on gke-16cpu-32gb-amd64. docs/ci.md:627 now records their measured disk tiers: live-sanitizers uses -256gb and live-tsan uses -128gb, selected by the explicit matrix include rows. The old unsuffixed shared-label statement no longer describes those jobs. - f85ce18 introduced the broad 'GKE preflight and proof legs with fixed labels' summary. docs/ci.md:1632 now states the current disk-qualified Debug and arm64 routing, while the preflight remains tiered by the main classifier (docs/ci.md:9). The old undifferentiated summary is superseded by these explicit workload mappings rather than a routing exemption. This records the real documentation intent of the measured-disk change. The workflow labels, their preflight checks and their assertions are kept.
…er the grid and pyramiding changes The source gate matches each site by file, symbol, lexical scope, class, spelling and occurrence. Three reviewed identities no longer matched because the text of an enclosing scope changed; the statements themselves did not. Each was read by content on this tree: - src/session_time.cpp, entry U0757, symbol "days": the assign is the local `const int64_t days = osec / 86400 - (...)` inside the civil-span branch of compute_tf_close_ms. That function's signature changed (the open stamp parameter is now the bar stamp, and the symbol time zone and session were added). It is a single integer, not a container: nothing grows, the entry's state (unreviewed) and container are unchanged. - src/source/pine_adapter.cpp, entry U0912, symbol "pending": the assign is the counter `std::size_t pending = 0` inside place_entry's pyramiding guard. The guard's condition gained `&& !coof_recalc_active_`. A counter, not the vector of the entry; state (pending-flat-memory) unchanged. - src/source/pine_adapter.cpp, entry U0939, symbol "result": the assign is `const auto result = require_host().cancel(handle)` in the same guard's over-cap branch, under the same changed condition. A local, not the vector of the entry; state (unreviewed) unchanged. The inventory file is the gate's own `--emit-site-inventory` output for this tree; it differs from the committed file in exactly these three scope strings (3 hunks). No entry, state, bound, pin or scan scope changed, and nothing is loosened: the open counts stay 1538 entries, 0 bounded, 184 pending, 1354 unreviewed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
TimeframeAggregator now retains observed_first_ms_. Its one by-value BacktestEngine::script_tf_agg_ member adds eight aligned bytes to the host. The measured Host size is 107952; Adapter remains 89488 and Scratch 672. Refresh only the two Host size pins. PF_STREAM_ENGINE_LAYOUT_VERSION is the live accumulator's exact C++ layout contract, independently of PF_ABI_VERSION. Advance it from 2 to 3 with the matching-module expectation. The runner compares that stamp before reading BacktestEngine directly; older stamp-2 modules remain accepted through strategy_stream_fill_report and avoid the current-layout accumulator. Add a stamped-2 fixture and report control by reusing the existing opaque fixture and authenticated report assertions without changing their source or golden. The old-macro RED and current-macro GREEN control execution and the new-head full verification remain required; source delivery is not a runtime verdict. Existing test deadlines are unchanged.
The new old-layout module shifted the runner CMake declarations. Point the sentence at the add_test(NAME test_native_example_batch declaration at line169, rather than a property-list occurrence or an offset guess. Only this citation changes; sentence wording and document line count stay. Replaced sentence: - Update the three places that pin the paths: `native_market_example` runner/CMakeLists.txt:54, `NATIVE_EXAMPLES` check_native_include_independence.py:49, the tests at `test_native_example_batch` runner/CMakeLists.txt:156. Replacement sentence: - Update the three places that pin the paths: `native_market_example` runner/CMakeLists.txt:54, `NATIVE_EXAMPLES` check_native_include_independence.py:49, the tests at `test_native_example_batch` runner/CMakeLists.txt:169.
The embedded timeframe aggregator gained an eight-byte observation timestamp, which shifts the host's later adapter member from offset 6424 to 6432 on Linux x86_64 with libstdc++. Match the measured offset while retaining the real subobject-address check. The host size pins already include the same growth; offsets within the adapter and retired scratch object remain unchanged.
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
Adds the W15 kernel-calibration rulings to the kernel/adapter boundary ADR and re-pins every pine_adapter.cpp line reference it cites, since the adapter grew by about 49 lines. Narrows the priced-entry carve-out under process_orders_on_close in place_entry, so it no longer applies while a calc_on_order_fills recalculation is active (coof_recalc_active_) and same-direction pyramiding entries are gated normally there. Widens compute_tf_close_ms to take the symbol's timezone and session next to the bar time, so timeframe close follows the session calendar, and the rulings table that records the resulting intraday-grid behavior has to stay one row per ruling.
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 — 4946 functions depend on the 2952 functions this change touches.
Health — this change adds coupling hotspots:
- new:
intraday_bucket()— 5 callers, 5 callees - worse:
compute_tf_close_ms()— 2 callers, 8 callees - new:
check_reentry_trades()— 3 callers, 4 callees - new:
read_rows()— 1 callers, 8 callees - new:
read_tape()— 1 callers, 8 callees - new:
run_row()— 1 callers, 8 callees - new:
read_tape()— 1 callers, 8 callees - new:
check_closed_incarnation()— 1 callers, 7 callees - …and 6 more — each is listed as a finding
Verification — 4946 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: 4664 function(s) in the blast radius were not formally verified this run
· 14 grounded finding(s) anchored inline below.
| } | ||
|
|
||
| static int64_t compute_tf_close_ms(int64_t open_ms, | ||
| static int64_t compute_tf_close_ms(int64_t bar_ms, |
There was a problem hiding this comment.
compute_tf_close_ms()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| /// 13:45/14:45 after the open, under the same key. With the supplied 13:30 | ||
| /// observation, the prefix through 14:44 shares 13:30/14:45 and completes | ||
| /// once. Strict origin anchoring without prefix folding would split it. | ||
| IntradayBucket intraday_bucket(int64_t ms, int64_t bucket_sec, |
There was a problem hiding this comment.
intraday_bucket()
high coupling complexity (Ca·Ce = 25).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
| // Metamorphic controls (a), (b) and (c): row 1's trades, the old short of 1 | ||
| // and then the bar-20 market short of 2 (id "reused", or "B" in (c)). | ||
| void check_reentry_trades(const SlotProbe& p, bool on_close, const char* successor_id) { |
There was a problem hiding this comment.
check_reentry_trades()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| } | ||
|
|
||
| // Rows 1 and 2. | ||
| void check_closed_incarnation(bool on_close, bool close_old) { |
There was a problem hiding this comment.
check_closed_incarnation()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| bool entry_seen = false, exit_seen = false; | ||
| }; | ||
|
|
||
| std::map<Key, TapeTrade> read_tape() { |
There was a problem hiding this comment.
read_tape()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| bool entry_seen = false, exit_seen = false; | ||
| }; | ||
|
|
||
| std::map<Key, TapeTrade> read_tape(const char* slug) { |
There was a problem hiding this comment.
read_tape()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return text.str(); | ||
| } | ||
|
|
||
| void run_row(const Row& row, const std::vector<Bar>& bars) { |
There was a problem hiding this comment.
run_row()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return group.finish(); | ||
| } | ||
|
|
||
| int metadata_getter() { |
There was a problem hiding this comment.
metadata_getter()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return group.finish(); | ||
| } | ||
|
|
||
| int handle_lifetime() { |
There was a problem hiding this comment.
handle_lifetime()
fans out to 7 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 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Fixes #723
Fixes #708
Fixes #722
Fixes #720
Part of #716: the engine now floors order quantities to a declared quantity step (qty_step, or mincontract when no qty_step is declared), which covers items 1 and 2 for runs that declare it; runs that declare neither key are unchanged, and the issue stays open until the callers supply the step.
This change corrects entry handling during fill recalculation, uses an explicitly declared minimum contract as the quantity step, and aligns requested bars with the session grid.
Entry replacement and the pyramiding cap
With fill recalculation enabled and a flat book, a later same-id entry now replaces an earlier held priced entry. Previously the earlier held entry could be submitted afterwards and replace the later market entry. TradingView captures of the reproduction keep the later market entry with orders processed both at the next open and on close.
The engine call logs for two seeded random scenarios show the same priced-entry then market-entry shape on the two divergence bars, with identical calls before and after the fix. The logged position is the script's view; which order wins is shown by the trade comparison. There is no TradingView tape of those random scenarios. Their eight pinned runs move; the other runs retain their recorded values.
A settled opening also occupies its pyramiding slot while its applied notification is still queued. This prevents an explicit close followed by a reversing same-id entry from admitting a second lot past a cap of one. A 150-resting-limit test covers the large-book case. Its helper matches sell limits at the adapter's half-tick matching threshold; the order's own price is unchanged. A real-bars TradingView script of the same order shape refuses the repeated short lot. That capture is not a tape of the seeded transcript and is not a class-wide claim. The reported behavior is present in the released 1.9.1 engine.
Declared quantity step
When a symbol declares a minimum contract and no separate quantity step, the declared minimum contract supplies the step. The default path with neither declaration remains unchanged. The public test names the floor's one-millionth-of-a-step epsilon limit. Stop-limit chronology is outside this quantity-step change.
This reaches library callers that set mincontract alone and preserves explicit qty_step precedence. Nothing is inferred from the symbol name.
The default-metadata path and the other order-quantity cases in #716 remain open.
Requested-bar session grid
TradingView-checked: BINANCE:BTCUSDT 24x7 at 75m over 2025-04-01..04 (a bar opens at 00:00 UTC each day; the last bar of a day is 23:45-00:00). Session symbols, late-open anchoring, other time zones / DST and widths of a day or more are engine expectations, not TradingView-measured.
The reviewed source candidate matched twelve BTC TradingView rows at 205 joined and equal trades each, and both explicit-session equity rows at 40 joined and equal trades each. These are member evidence for those cases; the combined candidate also passed full verification.
The capped final time_close and DST characterization are engine expectations without a TradingView tape. An empty after-close bucket completes immediately and has no feed-level completion row. The default-session case remains open in the caller-metadata work. The open-label-only mutation survives by construction; the prefix-key mutation produces two completions and fails the existing one-completion assertion. The two default-session rows are deferred intact, with no skip or xfail introduced.
Native-live report compatibility
Native-live modules built with an earlier version are still accepted and use the standard report path (no live accumulator) until they are rebuilt.
Documentation sentences removed by this change
Stale references to
intraday_grid_clock_msare replaced withintraday_bucket, the requested-bar grid implementation. Source references are refreshed throughout, including the native example batch test registration.Validation
All eight verification steps passed on Linux x86-64: preflight, Release, Debug, sanitizers, kernel, documentation, corpus parity and subset. The ABI layout probe passed in Release, Debug and sanitizers. On aarch64, the quantity-step tape/API tests passed three of three and the layout/report integration tests passed three of three, with zero skips. The full-population parity sweep completed; the required parity carry verdict follows publication.