uv(parity): bit-parity sweep unroll-vs-UV — 7/9 parity; ADVSL functionally dead in UV (worst)
This commit is contained in:
122
prod/docs/uv_cert/exit_crawl/BIT_PARITY_UNROLL_VS_UV_20260713.md
Normal file
122
prod/docs/uv_cert/exit_crawl/BIT_PARITY_UNROLL_VS_UV_20260713.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# BIT-LEVEL PARITY — BLUE exit stack vs UV port (2026-07-13, redo)
|
||||
|
||||
READ-ONLY crawl. Sources:
|
||||
|
||||
- **A (BLUE)**: `/mnt/dolphinng5_predict/unrolled/blue_unrolled.py` (unroll), with gap-verification against
|
||||
`nautilus_dolphin/nautilus_dolphin/nautilus/alpha_exit_manager.py` (AEM), `prod/clean_arch/tp_curve.py`,
|
||||
`adaptive_exit/advanced_sl.py` (ASL), `prod/supervisor/dolphin-supervisord.conf` (live env).
|
||||
- **B (UV)**: `/root/uv-wt/refactor/prod/clean_arch/violet/uv/` — `brain_tpsl.py`, `tpsl_ticker.py`,
|
||||
`max_hold_modulation.py`, `tp_exit_modulation.py`, `sl_exit_modulation.py`, `advanced_sl_port.py`,
|
||||
`blue_prime/runner.py`.
|
||||
|
||||
**UNROLL GAP (material):** the unroll's `AlphaExitManager` (blue_unrolled.py:1877–1922) is a simplified
|
||||
backtest stub — no TP_FLOOR, no OB modulation, no `max_favorable`. The canonical live class is
|
||||
`nautilus_dolphin/nautilus_dolphin/nautilus/alpha_exit_manager.py` (339 lines); all AEM line refs below are
|
||||
to that file. Everything else cited from the unroll matched targeted checks against the real sources.
|
||||
|
||||
---
|
||||
|
||||
## Verdict table
|
||||
|
||||
| # | Item | BLUE (file:line) | UV (file:line) | Verdict |
|
||||
|---|------|------------------|----------------|---------|
|
||||
| 1 | TP base 0.0020; UV source of base | `blue_unrolled.py:3051` `_tp_base_pct = ENGINE_KWARGS.get("fixed_tp_pct", 0.0020)`; ENGINE_KWARGS `blue_unrolled.py:3246` `'fixed_tp_pct': 0.0020` | `max_hold_modulation.py:139` base from `engine._tp_base_pct` (or 0.0020); `MaxHoldIngress.base_tp_pct=0.0020` (`max_hold_modulation.py:34`); runner passes engine base (`blue_prime/runner.py:322-329`); no-ingress fallback = `UV_TP_PCT` env (`tpsl_ticker.py:390-396`); `blue_tp_yield` hard default 0.0020 (`tp_exit_modulation.py:86`) | **BIT-PARITY** (source = same engine anchor; `UV_TP_PCT` unpinned in repo — see F10) |
|
||||
| 2 | Cubic curve: base − 0.00013·clamp(lev/2,0,1)³, floor 0.00187, cap base, lev=notional/capital | `prod/clean_arch/tp_curve.py:13-16,33-64` (constants 2.0/3.0/0.00013/0.00187; `compute_our_leverage` = \|notional\|/capital); unroll copy identical `blue_unrolled.py:1109-1135` | UV **imports the same file**: `tp_exit_modulation.py:14` `from prod.clean_arch.tp_curve import compute_our_leverage, compute_soft_tp_pct`; called `tp_exit_modulation.py:91-92`; notional = qty·entry (`tpsl_ticker.py:399`) ≡ BLUE `blue_unrolled.py:3406-3412` | **BIT-PARITY** (shared source module) |
|
||||
| 3 | OB TP modulation: cascade ×1.40 live; withdrawal ×0.60 elif; per-asset ×0.75 | AEM:174-177 `if macro.cascade_count > 0: dynamic_tp_pct *= 1.40`; AEM:181-187 `elif macro.regime_signal == 1:` … `if pnl_pct > 0.0: dynamic_tp_pct *= 0.60`; AEM:198-202 `if cascade==0 and regime!=1 and withdrawal_velocity < -0.20: if pnl_pct>0: *= 0.75`; macro source `blue_unrolled.py:1464-1475` | `tp_exit_modulation.py:100-102` `if ingress.cascade_count > 0: dynamic *= 1.40`; `:103-107` `elif ingress.regime_signal == 1: … if pnl > 0.0: dynamic *= 0.60`; `:111-118` identical per-asset predicate `*= 0.75`; ingress from same `ob_engine.get_macro()/get_signal()` (`max_hold_modulation.py:172-177`) | **BIT-PARITY** — identical elif structure, factors, and pnl>0 gates. Shadowing note: ×0.60 is preempted by cascade via `elif` on both sides; it is not *strictly* unreachable in either (get_macro can yield regime=1 with cascade_count=0 when every per-asset vel ∈ (−0.3, −0.2] and avg < −0.2, `blue_unrolled.py:1465-1475`) — reachability envelope identical because both consume the same macro |
|
||||
| 4 | TP_FLOOR: armed at max_favorable≥base, exits at pnl≤base, checked FIRST | Arm AEM:208-210 (`max_favorable >= base_tp_pct`); exit AEM:232-234 `if tp_floor_enabled and _floor_armed and pnl_pct <= base_tp_pct` — first exit check; enabled `DOLPHIN_TP_FLOOR` default True `blue_unrolled.py:3208`; base = curve via `set_live_tp_pct` scan stamp `blue_unrolled.py:3502-3522` + AEM:151 | Arm `tp_exit_modulation.py:122` (`favorable >= curve`); exit `brain_tpsl.py:371-377` `if tp_floor_enabled and tp_floor_armed and pct_move <= tp_floor_pct` — first check in `_evaluate`; floor anchor = curve (`tp_exit_modulation.py:124,128`) | **BIT-PARITY** (diag nuance: BLUE's `tp_floor_armed` diag ignores the enabled flag, UV folds enabled into armed — exit path identical; see F9) |
|
||||
| 5 | Check order TP_FLOOR→FIXED_TP→STOP_LOSS→MAX_HOLD | AEM:232 (floor) → 242 vd (`vd_enabled=False` default AEM:44) → 280 tf (`tf_enabled=False`) → 315 FIXED_TP → 320 STOP_LOSS → 325 MAX_HOLD | Tick lane `brain_tpsl.py:371→381→389` (floor→TP→SL; trailing disabled, `tpsl_ticker.py:123-127`); MAX_HOLD on scan lane `tpsl_ticker.py:272-297`, after ADVSL `:248-271` | **BIT-PARITY** (note: BLUE ADVSL is out-of-band async enqueue `blue_unrolled.py:7416-7516`, so ADVSL-vs-MAX_HOLD order is nondeterministic in BLUE; UV serializes ADVSL before MAX_HOLD — a scheduling choice, not a threshold change) |
|
||||
| 6a | SL catastrophic 0.0120 re-armed/scan tighten-only | Env `DOLPHIN_CATASTROPHIC_FLOOR_PCT=0.0120` (supervisord.conf:73; default `blue_unrolled.py:3056-3059`); tighten-only re-arm `blue_unrolled.py:5052-5090` (`if current <= 0.0 or current > floor_pct`), called per scan `:6788,6871` + at setup `:4618,4660,4883,4931` | `sl_exit_modulation.py:18` `BLUE_CATASTROPHIC_FLOOR_PCT = 0.0120`; `blue_sl_yield:59-61` `effective = min(configured, floor)` recomputed every evaluation (tighten-only by construction) | **BIT-PARITY** |
|
||||
| 6b | Withdrawal SL 0.10 WIDENS | AEM:181-183 `elif macro.regime_signal == 1: dynamic_sl_pct = 0.10` (scan-scoped; stored override untouched) | `sl_exit_modulation.py:19,63-65` `if ingress.regime_signal == 1: effective = 0.10` (evaluation-scoped) | **BIT-PARITY** |
|
||||
| 6c | Overlay SL 0.0050 + $500 | LIVE in BLUE for `overlay_flip` positions: `blue_unrolled.py:5092-5117` `min(0.0120, 0.0050, 500/notional)`; env supervisord.conf:73 (`0.0050`, `500`) | No overlay concept anywhere in `/root/uv-wt/refactor/prod/clean_arch/violet/uv/` (grep `overlay` = 0 hits in port modules) | **MISSING-IN-UV** (scope-consistent: UV has no post-win LONG overlay / EFSM; becomes a gap only if UV ever carries overlay positions) |
|
||||
| 7 | ADVSL gates/score/actuation | Constants `adaptive_exit/advanced_sl.py:332-338` (20/1.85/0.42/0.52/0.56/0.70); gates `:732-739`; live env `DOLPHIN_ENABLE_ADVANCED_SL_LIVE="1"` (supervisord.conf:73); full-fraction RETRACT `blue_unrolled.py:7490` `"fraction": 1.0`; reason `blue_unrolled.py:7481-7484` = `ADVSL_ASL_PRESSURE_CONT_STRESS`; catastrophic sub-mode dormant (`catastrophic_mode_enabled=False`, ASL:358); **recovery_cut LIVE** ASL:740-751 (no enable flag) | Constants `advanced_sl_port.py:43-50` identical; gates `:128-135` + port re-derive `:221-247`; full-fraction `tpsl_ticker.py:264` `exit_fraction=1.0`, `brain_tpsl.py:565` `target_size = pos.quantity`; reason `tpsl_ticker.py:257` = `ASL_PRESSURE_CONT_STRESS` (no `ADVSL_` prefix); catastrophic omitted (dormant-parity); **recovery_cut absent**; math is a stdlib transcription, not the BLUE runtime | **DIVERGENT** — gate constants and actuation parity; scoring math approximated (F3–F5, F8); recovery_cut MISSING (F1); and as wired UV ADVSL **can never fire** (F2) |
|
||||
| 8 | MAX_HOLD branches (spot-check) | base 250 `blue_unrolled.py:3247`; cascade ×0.5→125 AEM:178; withdrawal+eff_imb<−0.10 ×0.4→100 AEM:189-190; favorable ext ×1.5→375 AEM:193-195; per-asset `min(dyn, 0.40·base)`→100 AEM:198-200; int() truncation | `max_hold_modulation.py:15` base 250 (+ hard assert `:154-157`); `:90-92` ×0.5; `:93-97` ×0.4; `:98-100` ×1.5; `:103-109` `min(dynamic, int(base*0.40))`; int() truncation identical | **BIT-PARITY** (confirms prior cert) |
|
||||
| 9 | PnL formula + fee-exclusive semantics | AEM:146 `pnl_pct = d * (current - entry) / entry` — raw price move; fees applied only in accounting legs (e.g. `blue_unrolled.py:2182-2197`), never in exit gates | `brain_tpsl.py:321-324` `pct_move = (entry−px)/entry` SHORT / `(px−entry)/entry` LONG; no fee term anywhere in gate math | **BIT-PARITY** (formula + fee-exclusive gating). Note: same formula, different price feed — BLUE scan price + OBF mid-injection (`blue_unrolled.py:3524-3540`), UV BingX mark-price REST (`tpsl_ticker.py:65-79`) |
|
||||
|
||||
**Counts: 7 BIT-PARITY / 1 DIVERGENT (ADVSL) / 1 MISSING-IN-UV (overlay SL). 0 UV-ONLY live-path additions** (UV trailing-TP exists but is hard-disabled off BLUE doctrine, `tpsl_ticker.py:123-127`).
|
||||
|
||||
---
|
||||
|
||||
## FAILURES (exact values)
|
||||
|
||||
### F1 — ADVSL `recovery_cut` MISSING-IN-UV (BLUE-live path)
|
||||
BLUE `adaptive_exit/advanced_sl.py:740-751`: exits (reason `ASL_RECOVERY_CUT`) when
|
||||
`bars_held ≥ eff_min_hold AND pressure ≥ 1.85 AND continuation ≤ 0.42 AND stress ≥ 0.52 AND favorable ≥ 0
|
||||
AND drawdown_from_peak ≥ max(tp_pct·0.10, 0.0005) AND recovery_ratio ≥ 0.95 AND score ≥ 0.70` — **no enable
|
||||
flag; live whenever ADVSL is live**. It bypasses the snapback gate. UV `advanced_sl_port.py` has no
|
||||
equivalent → UV holds where BLUE cuts.
|
||||
|
||||
### F2 — UV ADVSL is functionally inert as wired (worst divergence)
|
||||
`tpsl_ticker.py:343-348` calls `advsl.evaluate(...)` with **no `market_state` and no `exf_snapshot`**.
|
||||
With empty inputs the UV stress composite (`advanced_sl_port.py:100-101`, weights 0.34/0.22/0.18/0.12/0.14
|
||||
— identical to BLUE ASL:580-588) evaluates to:
|
||||
`0.34·0 + 0.22·0 + 0.18·(1−0) + 0.12·(1−0) + 0.14·exf_stress(∅)=0.14·0.20 = 0.328`
|
||||
(empty exf → taker=0.5 → taker_score=1.0 → exf_stress=0.20). Since `gate_stress` requires `stress ≥ 0.52`
|
||||
and 0.328 < 0.52 **unconditionally**, `would_exit` can never be True. BLUE passes live
|
||||
`market_state`/`market_bundle`/`exf_snapshot`/`ae_shadow`/`meta_performance` (`blue_unrolled.py:7433-7456`).
|
||||
Net: ADVSL is live-armed in BLUE (`DOLPHIN_ENABLE_ADVANCED_SL_LIVE=1`) and dead-on-arrival in UV.
|
||||
Fail direction is safe (HOLD), but it is a missing live exit layer, compounding F1.
|
||||
|
||||
### F3 — continuation blend divergence
|
||||
BLUE ASL:640-642,669-672: `p_cont` defaults to 0.5 and the blend
|
||||
`continuation = 0.55·raw + 0.45·p_cont` is applied **always** (p_cont sourced from `ae_shadow`).
|
||||
UV `advanced_sl_port.py:108-112`: blend applied **only if** `p_continuation`/`p_cont` present in
|
||||
`v7_decision` (different source). With absent keys: BLUE gate `continuation ≤ 0.42` ⇔ raw ≤ 0.3545;
|
||||
UV gate ⇔ raw ≤ 0.42. Different effective cut.
|
||||
|
||||
### F4 — snapback composite weights differ
|
||||
BLUE ASL:648-654 uses the **nested** `_mean_reversion_snapback_score` (ASL:136-154) as the 0.60 term →
|
||||
effective `snapback = 0.36·recovery + 0.40·max(0,momentum₄) + 0.24·mrs`.
|
||||
UV `advanced_sl_port.py:102-103`: `0.60·recovery + 0.25·max(0,momentum₃) + 0.15·mrs`.
|
||||
Also momentum lookback: BLUE 4-back (ASL:152) vs UV 3-back (`advanced_sl_port.py:78`).
|
||||
|
||||
### F5 — stall metric divergence (feeds pressure fallback and score)
|
||||
BLUE `_stall_fraction` ASL:129-133: `mean(|diff(log(prices))| < 0.00015)` over full window, **empty → 0.0**.
|
||||
UV `advanced_sl_port.py:107,124`: fraction of *favorable-series* diffs `< 0.00015`, **empty → 1.0**, and the
|
||||
score term windows to last-5 only. Pressure fallback also swaps BLUE's `mean_reversion_score` for raw
|
||||
`recovery_from_adverse` (BLUE ASL:639 vs UV :107). Forecast/support terms likewise simplified: UV pins
|
||||
market_support to 0.5 (`+0.12·0.5`, `advanced_sl_port.py:126`) and drops BLUE's tanh(vel_div/ob_imbalance),
|
||||
bounce, rv_comp terms (ASL:255-296); decay_bars = consecutive-from-end (ASL:182-187) vs count-in-last-5/5
|
||||
(UV:121); `effective_min_hold` fixed 20 (UV:129) vs `max(20, 0.25·market_hold_bars)` (ASL:655-658);
|
||||
tp_scale hardcoded 0.002 (UV:116-120) vs `market_state_tp_pct` (ASL:576). Same-valued only at defaults.
|
||||
|
||||
### F6 — ADVSL exit-reason prefix
|
||||
BLUE enqueues `reason = f"ADVSL_{_adv.reason}"` → `ADVSL_ASL_PRESSURE_CONT_STRESS`
|
||||
(`blue_unrolled.py:7481-7484`). UV emits `ASL_PRESSURE_CONT_STRESS` with `trigger_type="advsl"`
|
||||
(`tpsl_ticker.py:253-257`). Journal/audit string mismatch; semantics identical.
|
||||
|
||||
### F7 — overlay catastrophic floor missing
|
||||
BLUE overlay positions get `min(0.0120, 0.0050, 500/notional)` (`blue_unrolled.py:5104-5114`) plus the
|
||||
overlay-ADVSL fast exit (`_overlay_advsl_should_exit`, `blue_unrolled.py:5119-5156`: min_bars 6,
|
||||
mfe_max 0.0020, pressure≥1.85, mae_risk≥0.50, live env `DOLPHIN_OVERLAY_ADVSL_LIVE=1`). UV: absent.
|
||||
Consistent with UV having no LONG overlay; must be revisited if overlay ever ports.
|
||||
|
||||
### F8 — market-state key precedence swapped (latent)
|
||||
BLUE reads `market_fingerprint_*` first, `market_state_*` fallback (ASL:568-575); UV reads
|
||||
`market_state_*` first (`advanced_sl_port.py:85-88`). Moot while UV passes no market_state (F2), but a
|
||||
bit-divergence the day inputs are wired.
|
||||
|
||||
### F9 — journal-only nuances (no exit-path impact)
|
||||
(a) Scan-clock TP re-stamp calls `_resolve_tp(pos, 0.0, …)` with `pnl_pct=0.0` (`tpsl_ticker.py:231-237`)
|
||||
→ journaled branch can never be `*_PROFIT` and journaled `tp_effective_pct` omits ×0.60/×0.75 even when the
|
||||
tick lane applies them; BLUE journals per decision with real pnl (AEM:204-223).
|
||||
(b) `tp_floor_armed` diag: BLUE reports armed even when floor disabled (AEM:208-217); UV folds the enabled
|
||||
flag into armed (`tp_exit_modulation.py:122`).
|
||||
|
||||
### F10 — env-conditional parity
|
||||
`UV_TP_PCT`/`UV_SL_PCT` are mandatory-at-arm but pinned nowhere in the repo (only tests set them).
|
||||
Parity requires deployment to set `UV_SL_PCT ≥ 0.0120` (effective SL then = 0.0120 floor, matching BLUE's
|
||||
stop_pct=1.00 + floor) and `UV_TP_PCT = 0.0020` (or unset, falling back to the engine anchor via
|
||||
`maybe_build_ticker(tp_base_pct=…)`, `tpsl_ticker.py:698-708`, `blue_prime/runner.py:322-329`).
|
||||
BLUE pins all of these in `prod/supervisor/dolphin-supervisord.conf:73`.
|
||||
|
||||
---
|
||||
|
||||
## Bottom line
|
||||
|
||||
The mechanical exit spine — TP curve, OB TP modulation (incl. the ×0.60 elif structure), TP_FLOOR,
|
||||
check order, catastrophic/withdrawal SL, MAX_HOLD — is **bit-faithful** (rows 1-6b, 8, 9), largely because
|
||||
UV imports the same `tp_curve.py` and transcribed AEM's branch order literally. The **ADVSL layer is not
|
||||
bit-parity**: constants and actuation match, but the scoring math is an approximation, `recovery_cut` is
|
||||
missing, and — decisively — the UV wiring starves it of market_state/exf so `gate_stress` (≥0.52 vs a
|
||||
0.328 ceiling) can never pass: **BLUE's live ADVSL protection does not exist functionally in UV.**
|
||||
The overlay SL (0.0050 + $500) is out of UV's scope today but must ride along if the LONG overlay ever ports.
|
||||
Reference in New Issue
Block a user