ExchangeFeeConfig in AccountState:
taker_rate, maker_rate, lot_step, tick_size, funding_interval_secs
calibration_ratio: EMA of actual/expected, updated on every fill
Kernel now predicts fees at fill time (PREDICTED_FILL event):
k_capital updated immediately without waiting for WS FILL_SETTLED
When actual fee arrives, prediction is replaced and ratio recalibrated
Reconcile delta: 0.000000 (was ~0.9 USDT in canary without prediction)
Calibration loop on connect():
Fetches recent fill history, validates model vs exchange actuals
deviation < 1pct -> OK; < 5pct -> WARN; >= 5pct -> ERROR (pre-trade gate)
New FFI: dita_kernel_set_exchange_config_json, dita_kernel_calibrate_fee_json
New ExecutionKernel methods: set_exchange_config(), calibrate_fee()
pink_direct.py: loads BingX fee config on connect, calibrates before stream
131/131 offline pass.
The aborted hard cutover crash-looped with "Rust kernel returned null string" from
process_intent on the first live trading step. Root cause (reproduced): a non-finite
(inf/NaN) numeric field reaching the kernel — Python json.dumps emits the Infinity/NaN
token, serde_json rejects it at parse, and the FFI returned null. Magnitude is fine;
only finiteness was the problem.
Defense in depth, kernel catches it:
- Rust FFI (lib.rs): dita_kernel_process_intent_json / _on_venue_event_json now return
a clean INVALID_INTENT KernelResult on parse failure (incl. Infinity/NaN tokens) AND
on serialize failure (a non-finite produced internally) — never a null string.
- Python bridge (rust_backend.py): ExecutionKernel.process_intent validates intent
finiteness/bounds (target_size, reference_price, limit_price, leverage, exit_leg_ratios;
size>=0) BEFORE the FFI and rejects INVALID_INTENT, naming the offending field+value.
- contracts.py: add KernelDiagnosticCode.INVALID_INTENT.
- pink_direct.py: on INVALID_INTENT, log full upstream provenance (snapshot.price,
capital, leverage, sizes) so the numerical SOURCE can be located on the next live run.
- on_venue_event bridge tolerates the fallback's null slot (uses the live slot).
Verified: kernel recompiled; offline 65 + 7 new guard tests green (no regression);
direct-FFI inf payload -> INVALID_INTENT (no null crash). NOTE: this turns the cutover
crash into a clean rejection — the upstream source of the non-finite (the live run's
inf) still needs locating, now aided by the provenance log.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First commit of the previously-untracked PINK-on-DITAv2 migration system
(execution moves to the Rust kernel; policy stays on legacy DITA, so Alpha
Engine algorithmic integrity is preserved). BLUE is untouched.
Sprint 0 (safety snapshot + flaw-fix verification, MARKET single-leg scope):
- Verified Rust FSM fixes (flaws 2,4,10,11,13) by source read of lib.rs.
- Hardened 5 vacuous/guarded assertions in test_flaws.py so each flaw test
genuinely exercises its fix. Most important: Flaw 5 now asserts capital
moves by EXACTLY realized PnL (was entering/exiting at the same price).
- Offline suites: 533 passed, 0 failed (35 flaws + 402 kernel/accounting/
bridge + 96 runtime/persistence/multi-exit/restart/seams).
- GATE PASS: MARKET-path-critical flaws 1,2,5 confirmed fixed + green.
- Added SPRINT0_FLAW_VERIFICATION.md report and _rust_kernel/.gitignore
(excludes Rust target/ build artifacts).
LIMIT/partial-fill remain explicitly out of scope (MARKET-only bring-up).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>