Built by OA agent per VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md; reviewed for
compliance/flaws. VERIFIED: wraps real prod/bingx/leverage.py (untouched), constants
imported from it, NO arbitrary upper caps, exact == bit-identity (1e6 gate 0 mismatches),
ROUND_HALF_EVEN explicitly tested (1.5->2 AND 2.5->2), clamping+non-default caps+frozen
model. 38 tests pass on independent rerun.
REVIEW FIX: to_exchange clamped negative internal_conviction to 0 in the trace field
(reused ConvictionLeverage ge=0); changed trace field to plain float (poison guard only)
so it records the ACTUAL input faithfully; dropped the clamp + unused import. Relocated
8 off-spec leverage-spike scratch files off repo root -> prod/VIOLET_dev/l3_spike/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Additive (non-breaking): decide(factors=None) keeps the V3a base-only path (existing
11 tests unchanged); decide(factors=SizingFactors(...)) produces BLUE-complete
conviction via VioletSizer (base_max=8 + dc/regime(ACB)/ob/esof, capped@9) with the
full factor breakdown on ShadowDecision (base_leverage/dc_lev_mult/regime_size_mult/
market_ob_mult/esof_size_mult, None on the base path). SizingFactors value object =
the live-plane inputs the launcher will source (V3.4b). 6 new tests incl. consistency
vs VioletSizer, STALKER cap, EsoF-stale haircut. 17 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VIOLET_DEV_SPEC_AND_PLAN.md: authoritative consolidated plan (mission, doctrine,
V0->V6 ladder w/ status, code map, full sizing composition, next steps, vision,
TODOs, operational notes). Supersedes scattered plan files.
VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md: self-contained parallel-developable unit
(V3.5) for an independent agent — wrap prod/bingx/leverage.py conviction->exchange
mapping, V-TYPES + bit-identity gate, full file paths/tests/gates/acceptance. Zero
overlap with V3.4 (DecisionEngine<->Sizing integration, lead-owned).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverted a redundant widening of the main MC gate (typical ranges) after confirming
test_gate_mc_extreme_multipliers already bit-identity-tests boost in [1,5], beta in
{0,0.2,0.8,1}, mc in {0,0.5,1}, and the OB agreement boundary (N=200k, exact !=).
Added a cross-reference note. All 6 gates green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Repo cwd /mnt/dolphinng5_predict (git root), no remote (local-only) -> agent must
run on this host in this dir; needs host-local eigenvalues data, live ClickHouse,
and BLUE runtime for bit-identity. Adds CH creds + python path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
§8b: authoritative orchestrator composition (5 multipliers: base x dc_lev_mult x
regime_size_mult[ACB x meta x MC] x market_ob_mult x esof; dynamic cap, STALKER 2.0)
+ operator factor-recall map (DC boost + OB-consensus = the two aside ACBv6).
§8c: vision roadmap — five-lanes=separation-of-concerns reframe, LONG-alpha grail,
FPGA-pure VIBRISS banditry, pure-dataflow-DAG->compile nirvana w/ bit-identity as
bridge, culminating in DISTRACK (memory-constant streaming distributions, sequenced
AFTER live testnet->mainnet).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logs the open question (noise/denominator artifact vs real depeg micro-edge
deliberately self-limited) + how to settle it. VIOLET keeps BLUE's exclusion
regardless (follow BLUE).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Soak surfaced USDCUSDT being shorted. BLUE has a hardcoded exclusion gate around
its (muted-IRP) picking: _STABLECOIN_SYMBOLS removed from prices_dict pre-select
(nautilus_event_trader.py:24/3906). Replicate exactly: VioletDecisionEngine skips
the same 10 symbols in observe() so IRP never sees them. Following BLUE in all
regards (picking unchanged; this is BLUE's separate gate). Set-equality drift
guard vs BLUE source + never-selected test. 11 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
launch_dolphin_violet.py: _build_shadow() behind DOLPHIN_VIOLET_DECISION_SHADOW=1
(default OFF -> _build_shadow returns None, zero behavior change, verified).
Divergence driver optionally feeds VioletDecisionEngine + journals to
dolphin_violet.violet_decisions (NO orders). Self-disables if the table is
absent (no CH doom-loop, the 2026-06-11 spool lesson). 22_violet_decisions.sql
applied to CH (table present, empty). vel_div sourced from scan_payload['vel_div']
(matches pink_direct). Restart for the DARK soak held for operator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22_violet_decisions.sql (dolphin_violet.violet_decisions, DDL-first) +
shadow_journal.py: VioletDecisionJournal validates each actuated ShadowDecision
via DecisionRow (V-TYPES, allow_inf_nan=False) before the CH sink -- malformed
dies at source, never at the spool head. NEVER an order. DecisionRow field set ==
DDL columns (asserted). 4 tests pass. Launcher wiring + DARK soak = operator step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parity_harness.py: median-curve parity of V3a VioletBetSizer vs recorded
dolphin.trade_events (vel_div->leverage), restricted to short-signal domain.
GATE PASSES on prod host: pearson 0.9998, max_abs_err 0.238 (budget 1.0) over
23 bins -> base conviction sizer reproduces BLUE's central tendency. Per-trade
scatter is the deferred SC/ACB/OB/gold modulation layer (separate finding doc).
3 unit + 1 gate green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90s BTCUSDT-PERP capture on the prod host, public data, dummy creds
(factory demands key env vars even for the data client; public endpoints
ignore the header). NT internal dispatch p50 0.23ms / p99 3.3ms — inside
VIOLET reactor budgets; 77 ticks/s sustained, zero drops. Qualifications:
NT owns its (uvloop) event loop -> recommended separate feed process
bridging via Zinc/HZ; exchange->cb p50 135ms is network+NTP+throttle, a
relative baseline only; PRODGREEN-era launcher API calls no longer exist
in 1.219 (FUTURES->USDT_FUTURE) — pin version + API smoke on adoption.
Exec-client spike remains keys-blocked; executor decision at the V4 gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
exec_driver.py lifts PINK's TTL-resolution logic (_exec_after_submit /
_handle_expired_working, live-proven) into a standalone driver with
injected ports (router/submit/pump/slot_view/venue_flat/ref-price) and
replaces the 1s polling sweep with one DeadlineScheduler deadline per
working order. The driver is the TIMING authority (router clamps TTLs
>=0.5s — its internal deadline is vestigial here); the router stays the
POLICY authority. R1 preserved verbatim: exit TTL -> MARKET escalation on
the SAME trade_id; post-only reject -> schedule_in(0) through the one
shared resolution path; venue-truth requote gate fails safe.
scripted_venue.py subclasses MockVenueAdapter (zero shared edits) with
per-trade directives: IMMEDIATE_FILL / REST_THEN_FILL / REST_THEN_EXPIRE /
POST_ONLY_REJECT / CANCEL_REJECT / FILL_RACES_CANCEL; deferred fills
release through reconcile() (the production pump seam), never the 50ms
subscribe() poll.
17 new tests incl. full-kernel CANCEL->venue mapping, fill-races-cancel,
bounded retry chains, on_fill deadline cancellation, fail-safe probe.
Router 77 green untouched; shared files clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
domain.py: refined scalar aliases (BarsHeld kills the bars_held=-106
UInt16 poison class by construction), DivergenceRow (DDL-shaped, frozen,
extra=forbid), ExecDriverSettings (env boundary for the V2 driver; ttl
override exists because the shared router clamps TTLs >= 0.5s),
ExecGateReport schema, beartype 'typed' decorator with
DOLPHIN_VIOLET_BEARTYPE=0 kill-switch.
divergence.py: rows now parse through DivergenceRow before the sink —
malformed rows die at the source with a rate-limited WARNING + counter,
never at the head of the CH spool.
Properties (hypothesis, derandomized): ExecutionRouter state machine
(fill/retry mutual exclusion via pop-semantics, R1 exit escalation same
trade_id, bounded retry chains, <=1 working ENTER), LatencyHistogram
percentile laws (member-of-samples, monotone, extremes), DivergenceRow
parse laws. 34 new tests; violet suite 64 green; router 77 green; zero
shared-file edits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The driver owned a freshly built HazelcastDataFeed that nothing ever
connected; every 1s poll hit features_map=None ('NoneType' .get) at
ERROR level and no scans reached the divergence monitor. Connect with
retry before sampling. Verified live: WS bookTicker up for 50 symbols,
divergence rows landing in real time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
launch_dolphin_violet.py: own namespaces hard-set (CH dolphin_violet, HZ
DOLPHIN_STATE_VIOLET/PNL, Zinc prefix violet, DOLPHIN-VIOLET-001); own
credentials (BINGX_VIOLET_API_KEY/SECRET) — DARK idle with periodic WARNING
until provisioned; CH preflight SELECT-probes the required tables and NEVER
creates (DDL-before-code); kernel snapshot path repointed away from PINK's
fixed /tmp/.pink_kernel_state.json; mainnet hard-disabled; observe loop
never calls runtime.step(). ObserveOnlyVenue: submit/cancel raise
ObserveOnlyViolation with full attribute delegation — the kernel's
venue-submit-failure rollback converts a refusal into a synthetic REJECT
(slot back to IDLE), proven against the real kernel. FeedDivergenceMonitor:
per-asset scan-vs-venue divergence rows (bookTicker WS via
prod/bingx/market_stream, REST fallback) with stale-mid suppression and
plane seq propagation — the FET 0.2176-vs-0.1878 detector; runs even DARK
(public data). Supervisord [program:dolphin_violet] autostart=false, no
keys in conf by design. Violet package: 42 tests green + V0 gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ch_writer: _writer_violet (db=dolphin_violet) + ch_put_violet, the existing
per-strategy singleton pattern. bingx/journal: explicit violet entries in
_STRATEGY_DB_MAP/_SINK_MAP/_SINK_NAME_MAP — closes the hazard where the
unknown-strategy fallback routes venue journals into BLUE's dolphin DB
(regression-pinned in the new test, which documents the footgun for any
future strategy color). test_violet_namespace_isolation: violet sink
targets dolphin_violet exclusively; journal resolves violet explicitly;
Zinc region names for prefix violet disjoint from pink; persistence wired
with the violet sink routes only to dolphin_violet; HZ map-name contract
pinned for the C4 launcher. Shared-lib regression: 57/57 across PINK
kernel/runtime + BLUE tp_floor/malformed-open + bingx http safety.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 tables consolidated from the LIVE post-ALTER dolphin_pink schema
(maras_tp + provenance folded into base CREATEs — a fresh DB never replays
ALTER chains) + violet_feed_divergence (scan-vs-venue divergence metric,
session_id + plane seqs + mono_ns). apply_violet_ddl.py posts ONE statement
per HTTP request (multi-statement posts fail — proven on pink 08), is
idempotent (all IF NOT EXISTS, double-apply tested live), and verifies the
expected table set. Applied to live CH: verify all 14 present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
67 production .py modules that the running PINK service imports but which
were never committed: prod/bingx/ (HTTP client, market/user streams,
journal, config), prod/clean_arch/ adapters/persistence/runtime/dita/dita_v2
production modules and their co-located tests. Rule going forward: every
module imported by launch_dolphin_pink.py / pink_direct.py must appear in
git ls-files. Excludes _backup dirs, __pycache__, and non-code files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PinkAssetPicker/PinkAlphaSizer (BLUE-parity IRP picker + sizer, 2026-06-10)
plus the 2026-06-11 fix: price_of falls back to the dehyphenated symbol —
reconcile/fill paths write venue format (FET-USDT) into the slot while the
scan universe keys are Binance-style (FETUSDT); without the fallback an
adopted slot had no price and TP/SL/MAX_HOLD never evaluated (the unmanaged
FET position incident).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TP_FLOOR (LINK 5e05eeeb, -$1,248.71): once the BASE 0.20% TP is crossed,
regression to base exits — caps the left tail of the OB cascade x1.40
TP-widening (which is logged per decision now: dynamic_tp_pct,
tp_mod_factor, cascade_count, ob_regime_signal, tp_floor_armed on
v7_decision_events). Class default OFF (champion parity); live ON via
DOLPHIN_TP_FLOOR.
Malformed-OPEN Option A (causal fix): POSITION_DUST_NOTIONAL_USD shared by
the full-close decision and the single _ps_write_open lifecycle gate (OPEN
rows can never round to zero size on disk); retract terminal leg writes its
trade_exit_legs + trade_reconstruction rows; restore reject-exhaustion halts
for unknown-corruption classes and flat-continues only for the documented
zero-size tombstone class; chain-token mismatch emits a CHAIN_TOKEN_MISMATCH
journal event; restored entry_bar preserves bars_held continuity (negative
entry_bar allowed, Int32) in both CH and HZ restore paths.
Tests: test_tp_floor.py 16/16 incl. LINK golden replay;
test_malformed_open_distal.py 11/11. Suites before/after identical except
one PRE-EXISTING failure fixed (full-close zero-size-row test).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review of PINK_ACCOUNTING_EXEC_FIX execution found the Phase 3.2 repair
path triply broken: (1) !closed guard blocked repair on terminal fills —
the common price-less case; (2) wrapper on_account_event was a raw FFI
passthrough so repairs never settled into published capital; (3) live
FILL_SETTLED carried no slot_id and realized_pnl=0 (pre-folded) — repair
was dead code. Fixes: repair allowed on closed slots (flag+dedup keep it
idempotent); wrapper settles the baseline diff on FILL_SETTLED-with-slot_id;
dedicated repair_realized_pnl field avoids double-folding the K-ledger;
_FakeKernelAccount fixture mirrors the Phase 1 anchor_to_exchange contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two related accounting fixes:
1. _calibrate_fee_model startup guard: before calling calibrate_fee,
compute raw deviation from the published taker/maker rate (ignoring
any stale calibration_ratio). If >15%, skip and log WARNING rather
than letting a bad REST fill set calibration_ratio to ~0.8 and cause
ESTIMATED fees to understate actuals by 20% for the entire session.
2. fee_settled_events trade_id: BingX WS does not echo back our
clientOrderId in fill events (field "c" is empty). Was falling back
to BingX's internal orderId (p-e-mq5.../p-x-mq5...) which can't
be joined to trade_events. Now reads trade_id from kernel slot 0
(which retains the trade_id until the next ENTER) so
fee_settled_events.trade_id = BTCUSDT-T-N. Added venue_order_id
field to persist_fee_settled for bidirectional reconciliation.
128/128 tests green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
trade_seq was missing from the kernel account snapshot dict, so the
intent engine always computed trade_id = "BTCUSDT-T-000000000001".
The Rust FSM SLOT_BUSY guard only fires on *different* trade_ids; with
the same ID it resets the slot and submits a new exchange order on each
ENTER signal tick (~86 duplicate orders observed in one session).
Fix:
- Add _slot_was_closed dict to ExecutionKernel; set False on ENTER
accepted (both sync/async), True on on_venue_event when slot.closed
- Increment account.snapshot.trade_seq on the IDLE→CLOSED transition
- Expose trade_seq in snapshot()["account"] so DecisionContext carries
the correct counter → intent engine generates unique IDs per trade
451/451 tests green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds dolphin-supervisord.service (installed + enabled) and dolphin_startup_check.sh:
- ExecStartPre waits for HZ (CRITICAL/blocks), CH+Prefect (WARN/degraded-ok)
- Logs to /tmp/dolphin_logs/startup.log + run_logs/dolphin_startup_<date>.log
- Writes machine-readable /tmp/dolphin_logs/startup_status.json on every start
- nautilus_trader remains autostart=false — BLUE must be started manually
SYSTEM BIBLE bumped to v7.1; §16.10 updated, §16.14 added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add §16.10 corrected daemon start sequence (supervisord NOT auto-started on boot),
§16.12 critical supervisord.conf rules (no /tmp paths, OBF starvation → BLUE freeze,
pre-restart position check), §16.13 OOM recovery runbook with exact commands.
Incident context (2026-06-08):
- Previous agent set nautilus_trader to /tmp/blue_runtime_mirror/ — broken after OOM reboot
- OBF died during BLUE run, degraded gate for 285+ bars, BLUE stuck in RETRACT on LTCUSDT
- Fix: revert supervisord.conf to /mnt canonical paths, restart supervisord
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A previous agent changed nautilus_trader to run from /tmp/blue_runtime_mirror/prod/
and updated PYTHONPATH + DOLPHIN_LOCAL_RUNTIME_ROOT to match. /tmp/blue_runtime_mirror
no longer exists after the OOM reboot, so supervisord could not start BLUE.
Fix: restore canonical paths for nautilus_trader:
command: /mnt/dolphinng5_predict/prod/nautilus_event_trader.py
directory: /mnt/dolphinng5_predict/prod
PYTHONPATH: /mnt/dolphinng5_predict:/mnt/dolphinng5_predict/nautilus_dolphin:/mnt/dolphinng5_predict/prod
DOLPHIN_LOCAL_RUNTIME_ROOT: /mnt/dolphinng5_predict
Rule: NEVER point nautilus_trader at /tmp. /tmp dirs are volatile;
canonical trader binaries must always be referenced via /mnt paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defect A (fee sign): bingx_user_stream._normalise_order flipped to
fee = -raw_fee so BingX negative-n costs arrive as positive kernel
costs. k_maker_rebates no longer accumulates phantom rebates.
Defect B (opening fee dropped): fill_qty now falls back to "z"
(cumFilledQty) when "l" (lastFilledQty) is zero/absent, so
apply_predicted_fill computes a non-zero opening-leg fee.
Architectural fix (WARN unfreezes): lib.rs reconcile() now unfreezes
capital_frozen on WARN as well as OK. WARN (0.01-20 USDT delta) is
normal in-flight settlement — only ERROR (≥20, unexplained) should
halt ENTERs. The old keep-state logic trapped the kernel permanently
frozen after the first trade's ENTER predicted-fee phase pushed delta
briefly into ERROR.
Acceptance criterion: |k_capital - bingx_balance| < 1 USDT, frozen=False
after every round-trip trade — verified numerically against T-1/T-2
ground truth from the CRITICAL doc.
Docs: CRITICAL_AGENT-TODO_ACCOUNTING_BUGFIX.md §12-13 (fix record),
CAPITAL_BOOKKEEPING_DESIGN.md §8 (kernel spec), SYSTEM_BIBLE §11.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: open_positions()/open_orders() called _backend_snapshot() ->
_call_backend() -> _run() -> pool.submit(asyncio.run, coro) which spawned a
temporary event loop in a worker thread. httpx AsyncClient created inside that
temp loop, loop closed immediately. All subsequent HTTP calls raised Event loop
is closed or asyncio.locks.Event bound to different loop. Crash triggered WS
stream reconnects; each reconnect re-ran reconcile with N>1 BingX positions and
orphaned all but the largest.
Fix: open_positions()/open_orders() now read backend._state (populated by
await backend.connect() in the main loop). Fallback to _backend_snapshot()
for callers without a connected backend.
Fixes test_bingx_bugs::TestConnectNoDoubleRefresh: connect() is now async.
New test_orphan_prevention.py: 23 tests covering all 5 orphan mechanisms:
A. open_positions/open_orders use backend._state, never hit thread pool
B. connect() awaitable, backend.connect() runs in main event loop
C. Reconcile guard: >1 position logs ERROR and takes only largest
D. clientOrderId p-action-base36-rand4 on every order
E. EXIT sizing capped to kernel slot_size
391 passed, 2 skipped, 0 failed across all 14 test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_reconcile_position_slot passed all N BingX positions (all slot_id=0) to
reconcile_from_slots; with N>1 the kernel silently took one and forgot the
rest. Now: sort by size desc, take only the largest, log ERROR naming every
ignored orphan symbol. Caller must flatten exchange to 0 before restarting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- migrate_pink_sink_schema.sql: ALTER TABLE adds fee/fee_source/is_maker/
slippage_bps/mark_at_submit/exchange_ts to trade_events and trade_exit_legs;
CREATE TABLE fee_settled_events (was missing entirely). DDL already applied.
- monitor_pink.py: wire real PinkClickHousePersistence so monitor roundtrips
write to dolphin_pink CH tables. Adds _make_decision_intent() helper; calls
persist_step() after ENTER and EXIT. Persistence failure is non-fatal (warns
and continues). 42 persistence tests green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause 1 (http.py): duplicate signature= in POST body — canonical_query
included signature key after build_signed_params injected it, then body
appended &signature= again. Fix: exclude 'signature' from canonical.
Root cause 2 (bingx_direct + http.py): HTTP retry sent same MARKET order to
backup URL (bingx.pro), which hits the same VST account. Without clientOrderId,
each retry opened a new SHORT position; EXIT BUY 10 only closed one. Fix:
restore clientOrderId in hyphen format p-{e/x}-{base36_ts}-{rand4} (pure
alphanumeric rejected by VST; hyphen format accepted). Adds max_retries_override
+ urls_to_try to _request_json for non-idempotent override path.
Root cause 3 (flat_and_start_pink): k.venue.connect() ran backend.connect()
inside asyncio.run() in a thread-pool. httpx session created there references
a dead event loop; order POSTs raise RuntimeError("Event loop is closed").
Fix: await adapter.connect() directly from main event loop.
Also: enter_wall_ms + tight _is_our_position createTime filter to separate
PINK's position from concurrent strategies on shared VST account. 1.5s
settle sleep before flat check.
New test suite test_bingx_http_safety.py: 20 tests covering idempotency,
retry correctness, backup-URL dedup, event-loop hygiene, signing correctness.
Live result: ENTER 290ms, EXIT 260ms — both sub-second. Position flat.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>