docs(dumb-review): correct E1 — single-position engine self-clears; real phantom bug found+fixed
E1 as submitted (release phantom on promotion-SUPPRESSED entries) does not survive review: the orchestrator is single-position and self-clears exit_manager+self.position on its own exit each cycle (esf_alpha_orchestrator 491-492), so shadow mode is a faithful paper mirror and pi's fix would regress shadow parity. The investigation instead found the real bug — phantom_release left engine.position set → single-position engine bricked on a proven reject — fixed in f11.1/blue-sizing-parity 98941373. Preserves pi's original E1 text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
439
prod/docs/DUMB_QUESTION_REVIEW_20260723.md
Normal file
439
prod/docs/DUMB_QUESTION_REVIEW_20260723.md
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
# The "Dumb-Question" Review — self-defeating logic bugs in the live UV/VIOLET stack
|
||||||
|
|
||||||
|
**2026-07-23 · pi (READ-ONLY sweep; nothing edited or run).**
|
||||||
|
Scope tree: `/root/uv-wt/f10b-acbv6` (branch `f11/fill-driven-exit`, HEAD `b655e32`).
|
||||||
|
Priority paths swept: `prod/clean_arch/violet/uv/` (exec, tpsl/exit, sizing, promotion,
|
||||||
|
hooks, blue_prime), `prod/clean_arch/dita_v2/` (capital/account/fill), `prod/bingx/`.
|
||||||
|
|
||||||
|
## What this review hunts (the bar)
|
||||||
|
|
||||||
|
ONE class only: **egregious, self-defeating logic** — a mechanism that obviously
|
||||||
|
defeats its own stated purpose, such that a competent engineer shown it says *"that's
|
||||||
|
dumb."* Calibrated against the two already-caught bugs:
|
||||||
|
|
||||||
|
1. *Mechanism not wired to its target* — fill-driven EXIT arming registered ENTRY
|
||||||
|
orders but not the ticker's EXIT orders → exit fills came back UNMATCHED, the watch
|
||||||
|
never disarmed. (Now fixed on this branch — verified: `blue_prime/runner.py:635-658`
|
||||||
|
registers ticker EXIT intents via `set_on_exit_submit`.)
|
||||||
|
2. *Safeguard guarding the wrong signal* — an E-capital staleness guard halted on
|
||||||
|
balance AGE while the venue only pushes on CHANGE. (Now fixed — verified:
|
||||||
|
`e_feed.py:711-745` re-stamps off a fresh signed venue read and fail-soft keeps the
|
||||||
|
feed alive; it guards liveness, not age.)
|
||||||
|
|
||||||
|
NOT reported here: perf, style, exotic-timing races, "could be cleaner." Anything that
|
||||||
|
did not clearly clear the bar is in the BORDERLINE section or omitted.
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**5 egregious findings as submitted — but E1 was corrected on review** (it does not
|
||||||
|
survive; the investigation it prompted found + fixed a *different* real phantom bug —
|
||||||
|
see the correction block under E1). Net: **4 egregious as-stated + 1 corrected-to-real-fix.**
|
||||||
|
Original submitted count below. (2 directly capital-affecting on the live armed path; the other
|
||||||
|
3 are correctness/safety of the capital-bookkeeping + venue layers). Plus **11 borderline**
|
||||||
|
items (self-contradicting comments, unconsumed reconcile verdicts, observability
|
||||||
|
divergences, latent/dead-path sign bugs, one wrong-object hook coupling) recorded for
|
||||||
|
completeness. Every egregious
|
||||||
|
finding below was re-verified by hand against the source — file:line confirmed.
|
||||||
|
|
||||||
|
Ranked by egregiousness × blast-radius (capital first):
|
||||||
|
|
||||||
|
| # | Finding | Bucket | Blast |
|
||||||
|
|---|---------|--------|-------|
|
||||||
|
| E1 | ~~Phantom-slot release unreachable on promotion-suppressed entries~~ **DOES NOT SURVIVE — see correction; real bug found + fixed** | mechanism-not-wired | **corrected** |
|
||||||
|
| E2 | E→K account mirror wire dropped by the factory | declared-but-dead | capital-bookkeeping correctness |
|
||||||
|
| E3 | Funding sign inverted on the K-capital side | right-idea-wrong-coupling | capital math (reconcile; latent for sizing) |
|
||||||
|
| E4 | Tripwire safety verdict dropped by the account contract serializer | self-contradicting safeguard | safety / correctness (cross-process; latent) |
|
||||||
|
| E5 | `prod/bingx/execution.py` is import-dead (whole VST client) | declared-but-dead | correctness (dead module; not live path) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EGREGIOUS FINDINGS
|
||||||
|
|
||||||
|
> **CORRECTION (2026-07-23, claude — source-verified against the readable kernel).**
|
||||||
|
> E1 as stated **does not survive review, and its suggested fix would be a parity
|
||||||
|
> regression** — but the investigation it prompted found a *different, real* bug of the
|
||||||
|
> original phantom class, now **fixed** (commit `98941373`, `f11.1/blue-sizing-parity`).
|
||||||
|
>
|
||||||
|
> Why E1-as-stated is wrong: the orchestrator (`esf_alpha_orchestrator.process_bar`) is
|
||||||
|
> **single-position**. Its entry gate is `self.position is None`; it self-registers on
|
||||||
|
> entry (627/652) and **self-clears on its own TP/SL/max-hold exit every cycle**
|
||||||
|
> (`_execute_exit` 491-492), evaluating the exit each bar. There is no multi-phantom
|
||||||
|
> "accumulation" (one position by construction) and no permanent "freeze" (self-clears).
|
||||||
|
> In shadow mode it runs a faithful single-position paper trade identical to BLUE.
|
||||||
|
> Releasing the phantom on every promotion-SUPPRESSED entry (pi's fix) would, in shadow
|
||||||
|
> mode (gate always inactive), clear `self.position` every scan → the engine re-enters
|
||||||
|
> every scan instead of holding → its own exit alpha never runs → **shadow parity
|
||||||
|
> regression**. So the suggested fix was **not** implemented.
|
||||||
|
>
|
||||||
|
> The REAL bug (found while verifying E1): the shipped `phantom_release` clears
|
||||||
|
> `exit_manager.reset_position(trade_id)` but **not** `engine.position`. Since the entry
|
||||||
|
> gate is `self.position`, and `evaluate()` returns `NO_STATE` (never EXIT) for the popped
|
||||||
|
> trade_id (`alpha_exit_manager.py:124`), a proven venue/kernel reject left the
|
||||||
|
> single-position engine **wedged** — never re-enters, never exits the ghost. Real-kernel
|
||||||
|
> repro confirmed `BRICKED=True`. Fix: clear BOTH (mirroring `_execute_exit` 491-492),
|
||||||
|
> guarded to the exact phantom trade_id. This is the genuine finding; pi's specific
|
||||||
|
> mechanism (not-wired-to-suppressions) was the wrong diagnosis of a real problem.
|
||||||
|
>
|
||||||
|
> The original (as-submitted) E1 text is preserved below for the record.
|
||||||
|
|
||||||
|
### E1 — Phantom-slot release is unreachable on promotion-SUPPRESSED entries (the common non-fill path)
|
||||||
|
|
||||||
|
**Egregiousness.** `phantom_release.py` was written to kill F10 bug #2: the engine
|
||||||
|
self-registers a position at DECISION time, before any order is placed, so a non-fill
|
||||||
|
must be unwound or "the engine wrongly believes it holds the asset and will not re-enter
|
||||||
|
it." The release is wired ONLY to the promoted-then-rejected path — the exact case it
|
||||||
|
most needs to cover (promotion *suppressed* the order, so it was never placed) never
|
||||||
|
reaches it. A safety net that skips the most common way the thing it protects against
|
||||||
|
happens.
|
||||||
|
|
||||||
|
**Mechanism + intended purpose.** `_release_phantom_on_reject(engine, intent, receipt)`
|
||||||
|
(`phantom_release.py:30-58`) resets `engine.exit_manager`'s decision-time position on a
|
||||||
|
PROVEN reject. The engine registers the phantom inside `harness.step(...)`
|
||||||
|
(`blue_prime/runner.py:880`), which runs BEFORE `bridge.try_promote_result(...)`
|
||||||
|
(`runner.py:950`).
|
||||||
|
|
||||||
|
**Exact failure.** The ONLY call site of `_release_phantom_on_reject` is inside the
|
||||||
|
closure `_register_entry_from_receipt` (`runner.py:1124`), and that closure is invoked
|
||||||
|
only at `runner.py:1190` (`if _promoted and _receipt_future is not None`) and
|
||||||
|
`runner.py:1197-1198` (`elif _promoted and _receipt is not None`). Both require
|
||||||
|
`_promoted=True`. When promotion SUPPRESSES an organic ENTER — book-health reject,
|
||||||
|
governor defer, asset-not-on-venue, stale-capital error, or `gate_inactive` in shadow
|
||||||
|
mode (`promotion.py:528,558,588,605,670`) — `_promoted=False` and the closure never
|
||||||
|
runs, so the decision-time registration already made by the engine is never unwound. The
|
||||||
|
not-promoted branch that DOES exist (`runner.py:964-971`) releases only the FORCE_ENGAGE
|
||||||
|
concurrency slot, not the engine's phantom position. Verified: `grep` finds exactly one
|
||||||
|
`_release_phantom_on_reject` call in the runner; there is no organic not-promoted release.
|
||||||
|
|
||||||
|
- File:line: `phantom_release.py:30-58`; sole caller `runner.py:1124` inside a
|
||||||
|
`_promoted`-gated closure (`runner.py:1190`, `1197-1198`); registration-before-promotion
|
||||||
|
ordering `runner.py:880` vs `:950`; the not-promoted branch that omits it
|
||||||
|
`runner.py:964-971`.
|
||||||
|
|
||||||
|
**Blast-radius — capital / correctness.** Armed mode: every book-health / governor /
|
||||||
|
venue-listing / stale-capital suppression leaks a phantom into `exit_manager`; the engine
|
||||||
|
then believes it holds that asset and stops re-entering it, silently diverging blue_prime's
|
||||||
|
decisions from real BLUE (the whole fidelity premise). Shadow mode: the gate is always
|
||||||
|
inactive, so EVERY organic entry decision leaks a phantom. Accumulating phantoms
|
||||||
|
progressively freeze the decision surface.
|
||||||
|
|
||||||
|
**The fix would be…** call the phantom release on the organic not-promoted branch too
|
||||||
|
(where a suppressed/synchronously-rejected ENTER is known non-live), not only inside the
|
||||||
|
`_promoted`-gated receipt closure — i.e. unwind the decision-time registration whenever
|
||||||
|
the order provably was not placed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E2 — The E→K account-truth mirror is built, handed in, then dropped on the floor by its factory
|
||||||
|
|
||||||
|
**Egregiousness.** The whole point of `EKBridge` is "every venue-accepted account event
|
||||||
|
is mirrored into the kernel's K-account; E stays authority, K mirrors." The armed runner
|
||||||
|
constructs the bridge and passes it into the E-feed factory — which accepts the parameter
|
||||||
|
and then never forwards it to the object that would use it. The mirror can never run in
|
||||||
|
the live process, and its stats read zero, hiding that it never ran.
|
||||||
|
|
||||||
|
**Mechanism + intended purpose.** `LiveEFeed` stores `self._kernel_bridge`
|
||||||
|
(`e_feed.py:640`) and, on every published account observation, calls
|
||||||
|
`self._kernel_bridge.mirror_future(converted)` under an `if self._kernel_bridge is not
|
||||||
|
None:` guard (`e_feed.py:686-689`). The live caller supplies it correctly:
|
||||||
|
`_e_feed = maybe_build_e_feed(..., kernel_bridge=EKBridge(_exec_executor))`
|
||||||
|
(`blue_prime/runner.py:479-481`).
|
||||||
|
|
||||||
|
**Exact failure.** `maybe_build_e_feed(plane, *, io_loop=None, kernel_bridge=None)`
|
||||||
|
(`e_feed.py:853`) accepts `kernel_bridge` but the `LiveEFeed(...)` construction it returns
|
||||||
|
(`e_feed.py:873-879`) passes only `EFeed(plane)`, `subscribe`, `account_snapshot`,
|
||||||
|
`close_source`, `refresh_interval_s`, `io_loop` — **`kernel_bridge` is never forwarded**.
|
||||||
|
So `LiveEFeed._kernel_bridge` stays `None`, the mirror guard is always false, and
|
||||||
|
`mirror_future` is never called. Unlike the fill observer (which has
|
||||||
|
`attach_fill_observer`, wired post-construction at `runner.py:633`), there is **no
|
||||||
|
`attach_kernel_bridge`** — construction is the only channel, and the factory eats it.
|
||||||
|
Verified: `grep` confirms the caller supplies `kernel_bridge=`, the factory signature
|
||||||
|
accepts it, and no attach method exists.
|
||||||
|
|
||||||
|
- File:line: accepts `e_feed.py:853`; drops `e_feed.py:873-879`; dead consumer
|
||||||
|
`e_feed.py:686-689`; live caller that supplies it `blue_prime/runner.py:479-481`.
|
||||||
|
|
||||||
|
**Blast-radius — capital-bookkeeping correctness + observability.** The DITAv2 kernel's
|
||||||
|
K-account projection never receives venue truth via this path in the live armed process,
|
||||||
|
so anything reading K-account state runs on an unfed projection; `EKBridge.stats` shows 0
|
||||||
|
bridged, masking the gap. E remains authoritative (the capital provider still works),
|
||||||
|
which is exactly what keeps this quiet.
|
||||||
|
|
||||||
|
**The fix would be…** forward `kernel_bridge` into the `LiveEFeed(...)` constructor in
|
||||||
|
`maybe_build_e_feed` (or add an `attach_kernel_bridge` and wire it like the fill observer).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3 — Funding sign inverted on the K-capital side vs the E-side sibling and the documented convention
|
||||||
|
|
||||||
|
**Egregiousness.** For a single funding event the E-wallet moves UP and K-capital moves
|
||||||
|
DOWN — the two account projections disagree in direction on the same event, and the K
|
||||||
|
side treats received funding as a cost. One of them is provably wrong by the field's own
|
||||||
|
docstring.
|
||||||
|
|
||||||
|
**Mechanism + intended purpose.** `AccountProjectionV2` folds funding into K-capital as
|
||||||
|
`k_capital = seed + Σrealized − Σfees − Σfunding` (`account.py:416`).
|
||||||
|
`ExchangeEvent.funding_amount` is documented "positive = received, negative = paid"
|
||||||
|
(`exchange_event.py:87`).
|
||||||
|
|
||||||
|
**Exact failure.** `apply_funding` does `self._k_funding += amount` (`account.py:371`)
|
||||||
|
and `k_capital` **subtracts** `_k_funding` — so a positive (received) funding *reduces*
|
||||||
|
K-capital. The E-side sibling does the opposite and correct thing:
|
||||||
|
`asex_account_publisher.apply_funding` → `self._wallet_balance += amount`
|
||||||
|
(`asex_account_publisher.py:51`), so positive funding *increases* the E-wallet. The
|
||||||
|
adapter passes `event.funding_amount` straight through with no flip. Verified by reading
|
||||||
|
both sites: same input `amount`, opposite sign into capital.
|
||||||
|
|
||||||
|
- File:line: `account.py:369-372` + `:416` (K subtracts received funding);
|
||||||
|
`asex_account_publisher.py:49-54` (E adds it); convention `exchange_event.py:87`.
|
||||||
|
|
||||||
|
**Blast-radius — capital math (latent).** In the observed wiring, published capital comes
|
||||||
|
from E (`wallet_balance`), so sizing is not directly corrupted today; the damage is a
|
||||||
|
guaranteed 2×funding K-vs-E divergence on reconcile R1 every funding tick, and direct
|
||||||
|
capital corruption on any path that trusts `k_capital`. (Compounded by E2: K may be
|
||||||
|
under-fed in the live armed process anyway.)
|
||||||
|
|
||||||
|
**The fix would be…** align the K side with the convention and the E sibling — treat a
|
||||||
|
positive `funding_amount` as an increase to capital (add received funding), not a
|
||||||
|
subtracted cost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E4 — The account-snapshot serializer drops the tripwire safety verdict it says consumers MUST read
|
||||||
|
|
||||||
|
**Egregiousness.** `AccountStateSnapshot`'s docstring is explicit: "`reconcile_ok` kept
|
||||||
|
for backward compat — consumers MUST read `tripwire_ok`." Its own `as_dict()` serializes
|
||||||
|
the deprecated `reconcile_ok` and OMITS `tripwire_ok`. The one field the contract says is
|
||||||
|
the go/no-go safety carrier cannot cross the transport; the field it says not to read is
|
||||||
|
the only one that does.
|
||||||
|
|
||||||
|
**Mechanism + intended purpose.** Rev-4-FINAL replaced K-derived `reconcile_ok` with a
|
||||||
|
stateless per-event `tripwire_ok` (`contracts.py:413-431`) as the account-health carrier.
|
||||||
|
The real shared-memory plane transports the account region via `as_dict()`
|
||||||
|
(`real_zinc_plane.py` `publish_account`) and reconstructs from the payload.
|
||||||
|
|
||||||
|
**Exact failure.** `AccountStateSnapshot.as_dict()` (`contracts.py:433-442`) returns
|
||||||
|
`wallet_balance, available_margin, used_margin, event_seq, mono_ns, e_live, reconcile_ok`
|
||||||
|
— **no `tripwire_ok`, no `tripwire_last_event_id`**. Any cross-process reader that
|
||||||
|
reconstructs from the dict defaults `tripwire_ok=True` (the frozen default,
|
||||||
|
`contracts.py:430`), so a tripped tripwire (`False`) is silently lost and every reader
|
||||||
|
sees "healthy." The authors already know this — `e_feed.py:205-208` notes "as_dict
|
||||||
|
currently omits the tripwire fields" and hand-rolls a separate encoder to dodge it — but
|
||||||
|
that workaround does not cover the plane's `as_dict` path. Verified by reading `as_dict`.
|
||||||
|
|
||||||
|
- File:line: `contracts.py:433-442` (omits the mandatory field, emits the deprecated one);
|
||||||
|
docstring contradiction `contracts.py:417-418`.
|
||||||
|
|
||||||
|
**Blast-radius — safety / correctness (latent).** Any cross-process consumer of the real
|
||||||
|
account plane can never observe a safety trip. Latent today because the live capital
|
||||||
|
consumer (`e_capital_provider.make_e_capital_provider`, `e_capital_provider.py:31-44`)
|
||||||
|
gates only on `e_live`/`wallet_balance`/freshness and never reads `tripwire_ok` at all —
|
||||||
|
so even in-process the verdict is unconsumed. The in-memory test plane passes the object
|
||||||
|
by reference, so the field survives there, which is why this hides in tests and would
|
||||||
|
only bite on the real shared-memory transport.
|
||||||
|
|
||||||
|
**The fix would be…** serialize `tripwire_ok` + `tripwire_last_event_id` in `as_dict()`
|
||||||
|
and reconstruct them on the plane; and have the capital provider actually consult the
|
||||||
|
tripwire before returning `wallet_balance`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E5 — `prod/bingx/execution.py` (the whole VST Nautilus execution client) cannot be imported
|
||||||
|
|
||||||
|
**Egregiousness.** The task named this file as primary live scope; it is dead-on-arrival.
|
||||||
|
Two unconditional top-level imports reference modules that do not exist in the package, so
|
||||||
|
`import prod.bingx.execution` raises `ModuleNotFoundError` before any of its listenKey /
|
||||||
|
PostOnly / reduceOnly / drift logic is reachable. An "execution client" that cannot load.
|
||||||
|
|
||||||
|
**Mechanism + intended purpose.** `BingxExecutionClient` is the Nautilus VST execution
|
||||||
|
client that `prod/bingx/factories.py:13` wires and `prod/clean_arch/violet/exec/vst_adapter.py`
|
||||||
|
documents itself as wrapping.
|
||||||
|
|
||||||
|
**Exact failure.** `execution.py:68` `from .sizing_mode import build_split_sizing_payload`
|
||||||
|
and `execution.py:89` `from .websocket import BingxUserStream` — neither
|
||||||
|
`prod/bingx/sizing_mode.py` nor `prod/bingx/websocket.py` exists (the dir has
|
||||||
|
`market_stream.py`, not `websocket.py`). Verified: `ls` confirms both files absent; AST
|
||||||
|
scan confirms both imports are top-level module scope. Consequently the whole client, its
|
||||||
|
`LiveExecClientFactory`, and `vst_adapter.py` are non-loadable. The live BingX path does
|
||||||
|
NOT use this file — it runs through `prod/clean_arch/adapters/bingx_direct.py` +
|
||||||
|
`dita_v2/bingx_user_stream.py` (which the live E-feed imports at `e_feed.py:859`).
|
||||||
|
|
||||||
|
- File:line: `execution.py:68`, `:89`; would-be consumers `prod/bingx/factories.py:13,25,29`,
|
||||||
|
`prod/clean_arch/violet/exec/vst_adapter.py`.
|
||||||
|
|
||||||
|
**Blast-radius — correctness (dead module, NOT live capital).** No live-path impact
|
||||||
|
because live trading goes through `bingx_direct.py`. The hazard is that a whole VST
|
||||||
|
execution client + its factory + adapter present as usable code but cannot load; anything
|
||||||
|
that ever tries to revive that path fails immediately, and any audit that trusts this file
|
||||||
|
as "the live exec logic" is auditing a corpse. (Everything the task flagged inside this
|
||||||
|
file — listenKey keepalive, PostOnly, `use_reduce_only` gate, drift check — is
|
||||||
|
unreachable.)
|
||||||
|
|
||||||
|
**The fix would be…** either restore/rename the missing `sizing_mode`/`websocket` modules
|
||||||
|
(or fix the imports to the real ones, e.g. `market_stream`), or delete the dead client +
|
||||||
|
its factory + adapter so nothing presents it as live.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BORDERLINE (real, but did not clearly clear the egregiousness bar / low or latent blast)
|
||||||
|
|
||||||
|
Grouped by taxonomy.
|
||||||
|
|
||||||
|
### safeguard-guarding-the-wrong-thing
|
||||||
|
|
||||||
|
- **B1 — BookHealthGate "exit-side liquidity" is hardcoded to the ASK side.**
|
||||||
|
Check 6 (`exec/book_health_gate.py:392-395`) rejects on `h.n_ask_levels <
|
||||||
|
self._min_exit_levels` with the comment "a short must buy back into the ask." For a LONG
|
||||||
|
the exit SELLs into the BID, so this measures the wrong side. Compounding: `is_tradeable`
|
||||||
|
receives `side` (`:324`) but passes only `(asset, notional_usd)` to `_evaluate` (`:352`),
|
||||||
|
so `side` never steers the decision — it only stamps telemetry. BORDERLINE because it is
|
||||||
|
a SOFT check, BLUE is short-dominated, and the health snapshot carries only `n_ask_levels`
|
||||||
|
(no bid data), so it is partly a data limitation, not just a branch. *Fix: thread `side`
|
||||||
|
into the gate and check bid-side depth for longs (requires bid-side data in the snapshot).*
|
||||||
|
|
||||||
|
- **B2 — Reconcile R2 compares cumulative realized against a single fill's realized.**
|
||||||
|
`delta_r2 = abs(k.realized_pnl − e.last_fill_realized_pnl)` (`account.py:529-531`):
|
||||||
|
`k.realized_pnl` is cumulative `Σrealized`, `e.last_fill_realized_pnl` is only the latest
|
||||||
|
fill. After the 2nd fill R2 trips on essentially every multi-fill trade. BORDERLINE only
|
||||||
|
because `reconcile.status` has no located prod consumer (effectively computed-but-unused
|
||||||
|
today). *Fix: compare like-for-like (cumulative vs cumulative).*
|
||||||
|
|
||||||
|
### right-idea-wrong-coupling
|
||||||
|
|
||||||
|
- **B3 — Reconcile R4 compares open_notional against used_margin.** Comment says "open_notional
|
||||||
|
vs exchange notional" but the code does `abs(k.open_notional − e.used_margin)`
|
||||||
|
(`account.py:552-556`) with a 0.3%-of-notional band; `used_margin` = notional/leverage.
|
||||||
|
For any leverage > 1 this diverges by ≈(1 − 1/lev)×notional → permanent ERROR. BORDERLINE:
|
||||||
|
same unconsumed-`reconcile.status` caveat as B2. *Fix: compare notional-to-notional (or
|
||||||
|
margin-to-margin consistently).*
|
||||||
|
|
||||||
|
- **B11 — Hook 05 `tp_threshold` syncs the leverage-adjusted soft-TP onto the wrong object;
|
||||||
|
the UV live exit never reads it.** BLUE's authoritative reference sets the live TP on the
|
||||||
|
ENGINE — `self.eng.set_live_tp_pct(tp_pct)` (`/opt/dolphin/prod/nautilus_event_trader.py:752`).
|
||||||
|
Hook 05 instead computes the leverage-conditioned soft-TP (`compute_our_leverage` /
|
||||||
|
`compute_soft_tp_pct`) and pushes it into `engine.exit_manager`:
|
||||||
|
`exit_manager.set_live_tp_pct(tp_pct)` else `setattr(exit_manager, "live_tp_pct", tp_pct)`
|
||||||
|
(`hooks/05_tp_threshold.py:48,73-77`) — an extra `.exit_manager` hop BLUE does not take.
|
||||||
|
Verified: across the UV tree `live_tp_pct` is ONLY ever written (all four refs are inside
|
||||||
|
hook 05); nothing reads it back. The UV live exit path (`brain_tpsl.py`, `tpsl_ticker.py`)
|
||||||
|
consumes `tp_base_pct` (from `engine._tp_base_pct`/`exit_manager.fixed_tp_pct`,
|
||||||
|
`runner.py:600-607`) and never calls `compute_soft_tp_pct`/`compute_our_leverage` at all —
|
||||||
|
so the hook's leverage tightening is dropped and UV exits on the base fixed TP. Contrast the
|
||||||
|
sibling hook 04, which correctly targets the engine (`engine.set_esof_advisory_score`,
|
||||||
|
`04_esof_size_gate.py:50`), and hook 11, which correctly targets `engine.exit_manager`
|
||||||
|
(`11_catastrophic_floor.py:124-142`) matching BLUE. BORDERLINE (not egregious) for two
|
||||||
|
honest reasons: (1) the adjustment is a "very small leverage-conditioned tightening" per
|
||||||
|
`prod/clean_arch/tp_curve.py:50-57`, so the PnL magnitude is small; (2) it could not be
|
||||||
|
statically disproven that the *compiled* D_LIQ `exit_manager` exposes a working
|
||||||
|
`set_live_tp_pct` (BLUE calls the engine-level setter, strong evidence the canonical setter
|
||||||
|
is engine-level — but the compiled surface has no `.py` on disk). Right-idea-wrong-coupling.
|
||||||
|
*Fix: set the live TP on the engine (`engine.set_live_tp_pct`) as BLUE does, and/or have the
|
||||||
|
UV exit path consume the leverage-adjusted TP; confirm against the live engine's method
|
||||||
|
surface.*
|
||||||
|
|
||||||
|
- **B4 — Commission sign passed through unflipped in the friction path.** `friction.py:150-156`
|
||||||
|
returns BingX's raw `commission` (negative = cost) unflipped; consumer `execution.py:1065/1526`
|
||||||
|
would book a normal cost fill as a negative commission (a rebate). The live path flips it
|
||||||
|
correctly (`bingx_user_stream.py:361-362`, `fee = -raw_fee`). BORDERLINE: the only
|
||||||
|
consumers are the dead `execution.py` (E5) and `characterization.py` (a bench), so it is
|
||||||
|
latent, not live. *Fix: negate `n` at the friction seam to match the live convention.*
|
||||||
|
|
||||||
|
### self-contradicting-logic (comment vs behavior)
|
||||||
|
|
||||||
|
- **B5 — "SL evaluated before TP" docstring over TP-first code.** `TpSlHandler.on_tick`
|
||||||
|
docstring says "D7: SL evaluated before TP" (`brain_tpsl.py:319`), but `_evaluate` runs
|
||||||
|
TP_FLOOR → FIXED_TP → SL (`:455-479`, with an explicit "BLUE fixed TP precedes SL" comment
|
||||||
|
at `:463-464`). Behavior is correct (matches BLUE doctrine); the docstring lies. Runtime
|
||||||
|
blast none; review-hazard — an editor trusting it could flip to loss-first. *Fix: correct
|
||||||
|
the docstring.*
|
||||||
|
|
||||||
|
- **B6 — Trailing-TP comment claims "before fixed TP", code runs it last.** `brain_tpsl.py:481`
|
||||||
|
"Trailing TP (before fixed TP…)" but the `_trail_hit` return sits AFTER FIXED_TP and SL.
|
||||||
|
Effectively dead (trail arms only under `UV_ENABLE_NONPARITY_TRAIL=1`, which the runner
|
||||||
|
never sets). *Fix: correct the comment or remove the dead branch.*
|
||||||
|
|
||||||
|
### computed-but-unused / fired-into-void
|
||||||
|
|
||||||
|
- **B7 — `on_bar` scan-path `state=="closed"` cleanup + MAX_HOLD logs are dead in the default
|
||||||
|
dispatch mode.** With `UV_EXIT_DISPATCH=1` (default), `submit_decision` returns
|
||||||
|
`"dispatched"`, never `"closed"`/`"rejected"` synchronously (`brain_tpsl.py:391`), so the
|
||||||
|
inline `_bars.pop` cleanup and both MAX_HOLD "EXIT"/"EXIT REJECTED" log branches
|
||||||
|
(`tpsl_ticker.py:316-346`) never fire in prod — the intended MAX_HOLD exit signal is lost
|
||||||
|
(reconcile handles the real close). Benign for correctness. *Fix: emit the MAX_HOLD log
|
||||||
|
from the dispatch completion path.*
|
||||||
|
|
||||||
|
- **B8 — Per-scan TP re-stamp feeds `pnl_pct=0.0`, so the journaled TP curve omits the
|
||||||
|
profit-gated modulation the live exit uses.** `on_bar` calls `_resolve_tp(pos, 0.0,
|
||||||
|
max_favorable, …, persist=True)` (`tpsl_ticker.py:277-283`); the profit branches in
|
||||||
|
`blue_tp_yield` (WITHDRAWAL_STRESS_PROFIT ×0.60, PER_ASSET_WITHDRAWAL_PROFIT ×0.75) only
|
||||||
|
fire when `pnl > 0`, so the persisted `tp_effective_pct`/`branch` systematically omit them,
|
||||||
|
while the live exit path passes the true `pct_move` (`brain_tpsl.py:418`). Observability
|
||||||
|
only — the TUI/journal TP diverges from the enforced TP exactly in the profit cases you'd
|
||||||
|
audit. *Fix: pass the true `pct_move` to the diagnostic re-stamp.*
|
||||||
|
|
||||||
|
### act-at-the-wrong-time / fallback-that-masks
|
||||||
|
|
||||||
|
- **B9 — ACB day-roll can size on inert `beta=0.0` at the UTC boundary before the sink
|
||||||
|
hydrates.** `on_day_roll` runs `begin_day` (sets `beta=0.0` with no ACB injected) then
|
||||||
|
`_feed`s the provider (`acb_beta_wiring.py:224-232`); `SinkBoostProvider` returns `None`
|
||||||
|
on a missing key and `_feed` "keeps prior value" (`:282-283`) — but the prior value at a
|
||||||
|
fresh day is the just-reset `0.0`. So at each UTC roll / cold start there is a window of
|
||||||
|
scans that size on neutral beta (the exact inertness this module exists to remove), and it
|
||||||
|
is silent. BORDERLINE — self-heals within the sink refresh cadence; small window. *Fix:
|
||||||
|
block/flag entries until a real beta has hydrated for the new date rather than sizing on
|
||||||
|
the reset default.*
|
||||||
|
|
||||||
|
### computed-not-wired-to-this-target
|
||||||
|
|
||||||
|
- **B10 — The `RateGovernor` is not on the live BingX transport, and `http.py` doesn't
|
||||||
|
recognize BingX's own rate-limit code.** `prod/bingx/rate_governor.py` is built around the
|
||||||
|
`100410` rejection, but nothing in `prod/bingx/http.py` (the client every order flows
|
||||||
|
through) calls `acquire()`/`note_100410()`; the only callers are the tempo lane +
|
||||||
|
blue_prime runner. And `http.py:826-833` recognizes only `109400` (+ loose "rate limit"
|
||||||
|
text), not `100410`. BORDERLINE — the governor IS used by the tempo lane; whether live
|
||||||
|
orders route through that lane vs straight through `bingx_direct` was not confirmed. *Fix:
|
||||||
|
route the order path through the governor, and teach `http.py` the `100410` code.*
|
||||||
|
|
||||||
|
### Minor (noted, not the target class)
|
||||||
|
|
||||||
|
- `tpsl_ticker.py:455` — `LOGGER.warning("TP capital provider failed: %s: %s", exc)` has two
|
||||||
|
format specifiers but one argument (malformed log on the capital-provider failure path).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cleared on inspection (checked, NOT bugs — recorded so they aren't re-hunted)
|
||||||
|
|
||||||
|
- **Calibration #1 (fill-driven EXIT wiring):** closed — ticker EXIT intents register with
|
||||||
|
the fill-router at submit (`blue_prime/runner.py:635-658`); exit fills route to
|
||||||
|
`_apply_exit` → net-qty reduce → disarm.
|
||||||
|
- **Calibration #2 (E-capital staleness):** the live E-feed re-stamps off a fresh signed
|
||||||
|
venue read and fail-soft keeps the feed alive (`e_feed.py:711-745`) — guards liveness, not
|
||||||
|
age.
|
||||||
|
- **TP mechanics:** TP binds to `entry_price` reconciled to fill price; TP_FLOOR ratchets
|
||||||
|
one-way on monotonic `max_favorable` and exits on regression to the anchor (correct
|
||||||
|
direction); SL sign/tighten-widen matches doctrine.
|
||||||
|
- **Sizing:** `size_trade` binds quantity to `effective_notional/mark_price` with consistent
|
||||||
|
exchange leverage; capital → sizing binds to the E-anchored provider fresh per promote,
|
||||||
|
fail-closed when armed.
|
||||||
|
- **ACB leg-A / leg-B:** `feed_proxy_b` IS called before `step_bar` (`blue_prime/runner.py:865`);
|
||||||
|
the None-on-read-miss doctrine correctly never resets a good beta mid-day.
|
||||||
|
- **Live BingX path (`bingx_direct.py` + `bingx_user_stream.py`):** reduceOnly set on exits;
|
||||||
|
one-way `positionSide=BOTH`; PostOnly via timeInForce; listenKey keepalive renews at 1800s
|
||||||
|
and forces reconnect on persistent failure; signing clock offset + reactive re-sync;
|
||||||
|
commission sign flipped correctly.
|
||||||
|
- **`use_reduce_only`** defaults `True` (`config.py:51`).
|
||||||
|
- **governor_gate / FORCE_ENGAGE Poisson clock / exec_control IOC budget:** consistent with
|
||||||
|
their stated invariants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Method: six parallel read-only subagent hunts across exec / tpsl-exit-sizing /
|
||||||
|
dita_v2-capital / bingx-venue / blue_prime-promotion / hooks-advisors, then per-finding
|
||||||
|
hand-verification of every egregious item against source (file:line confirmed). The hooks
|
||||||
|
advisor chain (`uv/hooks/01-11`) is wired (`build_hooks`/`HookRunner`,
|
||||||
|
`blue_prime/runner.py:438-445`); most hooks are correctly coupled to real consumers (01/10
|
||||||
|
read back via `resolve_effect_bool`/`_resolve_step_prices` at `runner.py:847,854`;
|
||||||
|
02/03/08/09 mutate real engine state; 04/11 target the engine and the exit_manager
|
||||||
|
respectively, matching BLUE; 06/07 are shadow-advisory by explicit design). The one coupling
|
||||||
|
defect found is B11 (hook 05's leverage-adjusted TP written to `exit_manager` — an object the
|
||||||
|
UV live exit never reads), kept in BORDERLINE for the reasons stated there. No egregious
|
||||||
|
computed-but-unused ACTUATION bug was confirmed in the hook chain within this pass.*
|
||||||
Reference in New Issue
Block a user