PINK DITAv2 L3: fix live LIMIT cancel (kernel order-id propagation + truth-based cancel)
L3 live validation surfaced a live-only defect: a working LIMIT order could not
be cancelled (MARKET never exercised cancel — synchronous fills).
Two coupled fixes:
- Rust FSM (lib.rs): propagate the venue's order id onto the active order for
ALL order types and event kinds (ACK/partial/full fill) whenever the exchange
provides one — orders are created at submit with an empty venue_order_id, so a
later cancel had no real id to reference. Only fills empty ids, never overwrites.
Requires recompiling libdita_v2_kernel.so.
- Backend (bingx_direct.py): add cancel(order) — a properly-signed DELETE by
orderId (clientOrderId fallback) with TRUTH-BASED confirmation: BingX can return
transient errors ("order not exist", dup-within-1s from an internal retry) even
when the order was removed, so the cancel succeeds iff the order is no longer
open on the venue. The venue adapter prefers this backend cancel over its raw
signed_delete fallback (which failed signature with an empty id).
Validated:
- Offline: 63 + new cancel-truth unit tests green (no regression post-recompile).
- Live VST: resting SHORT LIMIT (+5%) rests as ENTRY_WORKING, confirmed as a LIMIT
open order, cancel -> CANCEL_ACK -> IDLE, exchange flat (test_pink_limit_live.py).
- Live VST MARKET run-through re-validated post-recompile: PASS, exact capital
reconciliation, two-phase rows visible (ORDER_REQUESTED + ENTRY_FILLED/EXIT).
LIMIT remains execution-infra only; PINK policy stays MARKET. BLUE untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1111,6 +1111,29 @@ impl KernelCore {
|
||||
let mut accepted = true;
|
||||
let mut diagnostic_code = KernelDiagnosticCode::OK;
|
||||
|
||||
// Propagate the venue's order id onto the working order whenever the
|
||||
// exchange provides one — for ALL order types and event kinds (ACK,
|
||||
// partial/full fill). Orders are created at submit time with an empty
|
||||
// venue_order_id; recording the assigned id lets a later cancel
|
||||
// reference the real exchange order (essential for resting LIMIT cancel;
|
||||
// harmless for MARKET, which fills synchronously). Only fills empty ids
|
||||
// (never overwrites) and targets the currently-active order.
|
||||
if !event.venue_order_id.is_empty() {
|
||||
let target = if slot.active_entry_order.is_some() {
|
||||
slot.active_entry_order.as_mut()
|
||||
} else {
|
||||
slot.active_exit_order.as_mut()
|
||||
};
|
||||
if let Some(order) = target {
|
||||
if order.venue_order_id.is_empty() {
|
||||
order.venue_order_id = event.venue_order_id.clone();
|
||||
}
|
||||
if !event.venue_client_id.is_empty() && order.venue_client_id.is_empty() {
|
||||
order.venue_client_id = event.venue_client_id.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match event.kind {
|
||||
KernelEventKind::ORDER_ACK => {
|
||||
if slot.active_entry_order.is_some()
|
||||
|
||||
Reference in New Issue
Block a user