Risk gate (risk/gate.py) — 5 stubs implemented:
1. _kill_switch_active(): operator-controlled emergency stop via set_kill_switch()
2. _cancel_rate_would_exceed(): tracks cancel timestamps per symbol in 60s
sliding window, blocks if >= MAX_CANCELS_PER_SYMBOL_PER_MINUTE
3. _would_self_trade(): checks open orders for same symbol+side at same price
(within tick_size), skipping the cancel_order_id for CANCEL_REPLACE
4. _would_exceed_symbol_notional(): sums current open order notional + new
order notional, blocks if > equity * MAX_SYMBOL_NOTIONAL_FRACTION
5. _violates_venue_minima(): checks tick alignment, lot rounding, min_qty,
and min_notional — all float-robust comparisons
ScenarioFactory — 3 remaining hardcoded scenarios converted:
1. _spread_tightening: spread_mult=0.3, depth_fraction=1.0 (was hardcoded BTC)
2. _cross_venue_arb: spread_mult=0.5, depth_fraction=0.5 (was hardcoded BTC)
3. _cross_exchange_arb_stress: spread_mult=0.8, depth_fraction=0.3 (was hardcoded BTC)
All 30 scenarios now use _behavior_state() — zero hardcoded prices remain.
675 tests pass. Zero regressions.
Operator's live bluff-check caught it: final grep stage without --line-buffered
block-buffers wakes silently. Verified fixed via EARTEST injection (same-second fire).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- evaluator: passes scenario.venue to matrix.record(venue=...)
- PerformanceMatrix.record(): accepts venue parameter (default='bingx')
- Enables cross-exchange learnings: same strategy tested on BingX vs Binance
gets separate performance entries per venue
Adversary ecology analysis:
Counterparties operate at ActionKind level (CROSS_SPREAD/PLACE/CANCEL),
not at order-type level. The CWM infers order type from ActionKind:
CROSS_SPREAD → fills aggressively → equivalent to MARKET
PLACE → passive quote → equivalent to LIMIT
This is correct and venue-independent. Fee calculation already uses
VenueRules (per-exchange fees). No adversary changes needed.
ScenarioFactory + CWM + Engine changes:
1. Scenario.venue field (default='bingx') — each scenario tagged with venue
2. ScenarioFactory.exchange_id parameter — controls which exchange scenarios simulate
3. _make_state + _behavior_state: venue propagated to VenueRules.exchange
4. All 34 scenario builders: venue=self.exchange_id
5. cross_exchange_transfer(): re-tag scenarios for different exchange
(strategy evolved on BingX can be re-evaluated on Binance)
6. CWM core.py: is_maker check updated for three-dimensional order model
(POST_ONLY no longer in OrderType; uses post_only flag instead)
Cross-exchange learning flow:
factory_bingx = ScenarioFactory(exchange_id='bingx')
scenarios_bingx = factory_bingx.build_suite(symbols=[...])
strategy = train(scenarios_bingx) # evolve on BingX
factory_binance = ScenarioFactory(exchange_id='binance')
scenarios_binance = factory_bingx.cross_exchange_transfer(
scenarios_bingx, target_exchange='binance')
score = evaluate(strategy, scenarios_binance) # test on Binance
All tests pass. Strategy PARAMETERS transfer; only venue tag + fees + order mapping change.
Updated README to reflect Fable's corrections:
- OrderType/TimeInForce/Instructions as three orthogonal dimensions
- POST_ONLY/IOC/FOK correctly described as non-types
- BingX trailing_stop -> TRAILING_STOP_MARKET
- Three mapping tables (order type, TIF, instructions)
- Integration status updated
adapter.py now uses normalize_to_exchange(action.order_type, 'bingx')
to translate normalized order types to BingX-native strings.
Falls back to LIMIT/MARKET/POST_ONLY for backward compatibility.
This is the critical integration point: standardized order types flow
from FulfilmentAction → CWM → VenueAdapter → exchange API.
DaatQuery: 8-feature market state representation
DaatVerdict: KNOWN / MARGINAL / OUT_OF_DISTRIBUTION
daat_classify: cosine RETRIEVE → magnitude GATE → local MODEL
- Cosine finds nearest explored state (directional match)
- Magnitude gate detects out-of-distribution states
- Empty explored set → always OUT_OF_DISTRIBUTION
9 tests covering: known state, OOD, empty explored, marginal, result fields.
No Unicode in code. All tests pass.
Item 1 — Mutation-litmus test (spec §1 item 3):
- test_taker_fee_10x_changes_score: fee change MUST affect score
- test_zero_fees_vs_correct_fees: zero vs 5bps must differ
- BOTH PASS — confirms fees ARE wired into reward function
- If fees were ignored, these tests would go RED
Item 3 — Maker fee verification (spec §1 item 5):
- Added '# UNVERIFIED — no maker fills on record as of 2026-07-13'
to Binance and Bybit exchange profiles
- Maker fee sign (positive on BingX, negative rebate on others)
is correct after fee fix but unverified from actual fills.
Items 2,4-10 remain for implementation.
Operator-approved. Apostrophe dropped (ASCII identifier law: import daat, DaatQuery,
dolphin_daat.*, no Unicode in any symbol/path/table). The acronym earns its letters:
D=direction (cosine retrieve), A=anchored (magnitude envelope gate), A=ambiguity
(the state we refuse to collapse), T=triage (KNOWN/MARGINAL/OUT_OF_DISTRIBUTION).
'Triage' is deliberate — the house already triages NOT_ATTEMPTED/REFUSED/INDETERMINATE
at the venue. Same verb, same law, now the names say so. Da'at (knowledge, the hidden
sefirah that sits above Malkhut and feeds it) survives in the etymology, where it costs
nothing. YESOD considered and set aside: it names the conduit, not the knowing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Operator's cosine proposal: ADOPTED as stage 1 of 3, never alone.
- Stage 1 RETRIEVE: cosine on DIRECTION = matmul = the reflex (fast, exact, deterministic)
- Stage 2 GATE: magnitude envelope + support + Mahalanobis -> OUT_OF_DISTRIBUTION
- Stage 3 MODEL: local tangent-space/GP -> prediction + VARIANCE (the real extrapolation)
THE DANGER: crises preserve direction and explode magnitude. Cosine returns
similarity 1.0 for a same-shape-10x-size state — max confidence at the exact moment
it is most wrong. Cosine CANNOT produce the OOD verdict; storing direction+magnitude
separately is the entire crash-safety story.
Same law, third coat: INDETERMINATE (venue) / stale-price (exit) / OOD (manifold).
Unknown is not flat, not 'best guess'.
TOPOLOGY (operator's IFF): cyclic (sin,cos) encoding = free + REQUIRED for the
streak-phase grail study; component detection = cheap; persistent homology = EARN IT
(offline Mode-1 diagnostic that shapes the gate, never a per-tick op).
GENERALIZATION: three-stage discipline (not one metric) is a shared kernel — market
fingerprint (scalar_hash is a hash reaching for this; conflict_level is latent OOD),
trade-path/ADVSL, exit-decision, asset transfer (= how full-universe becomes
affordable), counterparty simplex, ops/incident prefiguration, streak-wave phase.
One guard defends all: confident interpolation into unexplored magnitude is the
universal failure mode. Proposed name: DA'AT.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two modes (operator): EXPLORE (synthetics, ecology-dominant, billions of combos,
emits a MANIFOLD) -> RECOMMEND (live OBF as query, localize + extrapolate, OOD
verdict falls back to doctrinal). Ecology stays: actuals calibrate, ecology plays.
Findings verified live tonight:
- FEE BUG CONFIRMED: trade_execution_quality says 5.016 bps taker; code says 0.5
(asset_classification.py:154/164/174/384). Every CMA-ES number is void until re-baselined.
- BOOK GAP: obf_universe (15.3B rows) is L1+aggregates, NOT a ladder. Three options,
must declare which; book_source provenance tag on every artifact.
- LATENCY: p50 49.9ms / p95 597ms / p99 10.8s / max 450s. Constant-100ms is fiction.
- REGIME MISMATCH: MARAS emits 5 regimes; MALKHUT selector invented 9. RegimeBridge needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A BingX read-timeout/reset/5xx after send means the answer was lost, not that
the order failed. Classify every submit failure by what it PROVES:
NOT_ATTEMPTED / REFUSED -> rollback sound; INDETERMINATE -> point-lookup our
own clientOrderId (read-only, bounded, never a reconcile); unresolved stays
UNKNOWN — no synthetic REJECT, no slot rollback, E-feed FILL settles truth.
- prod/bingx/http.py: BingxHttpError.effect + order_may_exist, 9 raise sites tagged
- adapters/bingx_direct.py: _lookup_own_order_by_client_id (never POSTs)
- dita_v2/venue.py: VenueIndeterminateError(VenuePostAckError) — existing fences catch it
- dita_v2/bingx_venue.py: both submit paths escalate INDETERMINATE receipts
- 14 tests incl. kernel no-rollback invariant + genuine-REFUSED contrast
Suite: 3416 passed, 19 skipped, 3 xfailed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BUG CLASS (doc: BUGCLASS_INDETERMINATE_OUTCOME_20260713.md): an operation with an external
side effect has THREE outcomes — NOT_ATTEMPTED, ATTEMPTED_REFUSED, ATTEMPTED_INDETERMINATE
— and rollback is sound only for the first two. Collapsing the third into 'failed' is what
orphaned 6 live SHORTs: a post-ack TypeError reached rust_backend's 'except Exception ->
synthetic REJECTED -> FSM rollback', which asserted 'no order exists' about an order that
was already filled. Telemetry never had a veto; it hijacked the failure channel.
FIXES (no new seams, no re-architecture):
- venue.py: VenuePostAckError — typed channel meaning THE EFFECT EXISTS. Carries receipt.
- bingx_venue submit/submit_async: point-of-no-return fence. Post-ack bookkeeping failures
raise VenuePostAckError instead of a bare exception.
- rust_backend (BOTH submit paths): catch VenuePostAckError FIRST -> no synthetic REJECT,
no rollback. Slot stays working; E-feed FULL_FILL / reconcile settles the truth.
LOSSLESS TELEMETRY (HJ: 'DITAv2 exists precisely because seams dropped 40% of inputs'):
drop-oldest is data loss and is GONE. Exec path appends O(1) to an unbounded queue and
returns. A SEPARATE spiller thread (which never touches the plane, so a wedged plane cannot
starve it) parks the backlog above HWM into a durable append-only spool; the publisher
replays the spool when the plane recovers.
Proven: wedged-forever plane + 200k records -> 0 dropped, 195903 durable on disk, 4096 in
memory, 7.4 us/call on the exec path. Lossless AND memory-bounded. Healthy plane: 2000/2000.
STILL BROKEN, flagged to codex: the pre-ack branch rolls back on TIMEOUT — but a timeout is
the definition of INDETERMINATE (the order may have filled). Same bug class, older, live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Central finding: TIER-B INVERSION. asex_kernel_executor.py:319 enqueues account/fill
events (execution truth) at priority=4 — the LOWEST tier, BELOW ENTER (P3). Under queue
pressure UV opens a new position before applying the fill that tells it what it already
holds. Tonight's 6 orphans are the proof: tier-G telemetry destroyed tier-B truth, UV
believed it was flat while holding 6 live SHORTs, and no SL/TP/ADVSL can protect a
position the kernel does not know exists. That is why B outranks C and D.
Also catalogued: A-tier sensors (heartbeat/disconnect/seq-gap/halt/stale-book) are not
ranked work at all — only the kill switch is. Missing C (liquidation distance, daily/
session loss, symbol loss, leverage trigger), D (trailing, giveback, stale-exit reprice),
E (self-cross, amend), F (spread/depth gate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>