Picks up the deferred work from C5/C7 trim notes. Adds:
cuda/lob_state.cuh — LimitSlot (32 B) + StopSlot (32 B) + Orders
(limits[32] + stops[16] = 1536 B/backtest). u64-aligned with
explicit _pad[6] on both slot structs so the Rust mirrors
(LimitSlotFlat / StopSlotFlat) bytemuck::Pod-derive without
panics about implicit padding.
cuda/resting_orders.cu — three kernels:
resting_orders_step (runs per event after book_update):
1. In-flight → resting promotion at arrival_ts_ns. Queue position
initialised pessimistically (full level depth ahead) per spec §9.
2. Queue decay against same-side trade_signed_vol at level: ahead-
of-us first then us; partial-fill emits OrderEvent + pos update.
3. Marketability check: book moved through our price → cross at
the just-arrived book (slippage-aware fill walks levels).
IOC cancels remainder; FOK does too (partial-fill not allowed).
4. Stop trigger detection: best ask up for buy-stop, best bid
down for sell-stop. StopMarket walks book; StopLimit converts
to a new LimitSlot (overflow → submission_overflow++).
5. OCO mutual exclusion: oco_pair byte (0..31 = limit, 32..47 =
stop) — when one leg fills/triggers, the paired slot is freed.
seed_limit_slot / seed_stop_slot — host-side single-slot seeders
for fixture testing + as a v1 entry point until the decision
kernel learns to emit resting orders. Both set an overflow_flag
if the target slot is already in use (gen_counter bumped on
successful seed for SlotTag freshness).
src/sim.rs — wires three new methods:
seed_limit_order(b, slot, side, kind, active_state, oco_pair,
price, size, queue_position_init, arrival_ts_ns)
seed_stop_order(b, slot, side, kind, active_state, oco_pair,
trigger_price, limit_price, size, arrival_ts_ns)
step_resting_orders(current_ts_ns, trade_signed_vol)
→ launches resting_orders_step kernel + chains step_pnl_track
so any fill that closes a position emits a TradeRecord.
Plus read_limit_slot / read_stop_slot / read_audit_records for
fixture inspection.
Audit-ring buffers (deferred from C2 originally) now allocated:
audit_d (n × 256 × 24 B) + audit_head_d (n × u32). Both
resting_orders.cu and the decision kernel's WriteOrder path
populate it via the inlined pack_slot_tag + emit_audit helpers.
Four new GPU fixtures:
limit_rest_marketable_fill — resting buy at 5500 with book at
ask 5500 fills immediately on step_resting (3 lots, vwap=5500).
stop_trigger — buy 4 lots @ 5500, seed sell-stop at trigger=5495,
book moves so bid=5494.50: stop triggers, walks book, position
closes flat, TradeRecord emitted, stop slot active=0.
oco_one_cancels_other — pair {buy@5495, sell@5505} with oco_pair
cross-linked: book moves so bid=5505.50, sell leg fills (pos=-2),
buy leg auto-cancelled. Both slots end active=0.
submission_overflow — host loop fills all 32 limit slots, then 33rd
seed_limit_order call must return Err (slot 0 already in use).
All 10 GPU fixtures green on RTX 3050 (6 original + 4 new). Ring 2
fuzz at N ∈ {1, 16, 256} still green. 33 lib unit tests still green.
The decision kernel (C7) and submit_market_immediate (C5) paths
are unchanged — they continue to use the "immediate fill" route.
A follow-up commit can teach the decision kernel to emit resting
limits via submit_limit_alloc (already defined in resting_orders.cu).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
56 lines
1.6 KiB
JSON
56 lines
1.6 KiB
JSON
{
|
|
"name": "stop_trigger",
|
|
"n_backtests": 1,
|
|
"events": [
|
|
{
|
|
"type": "snapshot",
|
|
"bid_px": [5499.75, 5499.50, 5499.25, 5499.00, 5498.75, 5498.50, 5498.25, 5498.00, 5497.75, 5497.50],
|
|
"bid_sz": [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
|
|
"ask_px": [5500.00, 5500.25, 5500.50, 5500.75, 5501.00, 5501.25, 5501.50, 5501.75, 5502.00, 5502.25],
|
|
"ask_sz": [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0]
|
|
},
|
|
{
|
|
"type": "submit_market",
|
|
"backtest_idx": 0,
|
|
"side": "buy",
|
|
"size": 4,
|
|
"ts_ns": 1000000000
|
|
},
|
|
{
|
|
"type": "seed_stop",
|
|
"backtest_idx": 0,
|
|
"slot_idx": 0,
|
|
"side": "sell",
|
|
"kind": "StopMarket",
|
|
"active_state": 1,
|
|
"oco_pair": 255,
|
|
"trigger_price": 5495.00,
|
|
"limit_price": 0.0,
|
|
"size": 4.0,
|
|
"arrival_ts_ns": 0
|
|
},
|
|
{
|
|
"type": "snapshot",
|
|
"bid_px": [5494.50, 5494.25, 5494.00, 5493.75, 5493.50, 5493.25, 5493.00, 5492.75, 5492.50, 5492.25],
|
|
"bid_sz": [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
|
|
"ask_px": [5494.75, 5495.00, 5495.25, 5495.50, 5495.75, 5496.00, 5496.25, 5496.50, 5496.75, 5497.00],
|
|
"ask_sz": [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0]
|
|
},
|
|
{
|
|
"type": "step_resting",
|
|
"ts_ns": 2000000000,
|
|
"trade_signed_vol": 0.0
|
|
}
|
|
],
|
|
"expected_pos": [
|
|
{
|
|
"position_lots": 0,
|
|
"realized_pnl_tol": 50.0
|
|
}
|
|
],
|
|
"expected_stop_slot_states": [
|
|
{ "backtest_idx": 0, "slot_idx": 0, "active": 0 }
|
|
],
|
|
"expected_min_trade_records": 1
|
|
}
|