"""TP profit-floor (TP_FLOOR) + TP-threshold diagnostics — regression suite. Incident: LINKUSDT 5e05eeeb (2026-06-11). The OB tail-avoidance layer silently widened the "fixed" 0.20% TP by x1.40 during a cascade (alpha_exit_manager.evaluate, cascade branch). The trade peaked at +0.265% (between base 0.19998% and widened 0.27998%), held four consecutive scans, reversed, and died at STOP_LOSS -$1,248.71. This suite pins: 1. Default-OFF parity: with tp_floor_enabled=False (the class default), behavior is bit-identical to the pre-change engine, INCLUDING the cascade-widened HOLD that caused the incident. 2. The golden LINK replay: with the floor ON, the trade exits TP_FLOOR on the first regression scan below base TP (+0.1617%), not STOP_LOSS. 3. Arming and edge rules, modulation interactions, LONG symmetry, STOP_LOSS / MAX_HOLD untouched, diagnostics present on every decision. """ import sys from pathlib import Path sys.path.insert(0, "/mnt/dolphinng5_predict/nautilus_dolphin") import pytest from nautilus_dolphin.nautilus.alpha_exit_manager import AlphaExitManager # ── OB engine mock — exactly the surface evaluate() consumes ───────────────── class _Sig: def __init__(self, imbalance_ma5=0.0, withdrawal_velocity=0.0): self.imbalance_ma5 = imbalance_ma5 self.withdrawal_velocity = withdrawal_velocity class _Macro: def __init__(self, cascade_count=0, regime_signal=0): self.cascade_count = cascade_count self.regime_signal = regime_signal class MockOBEngine: def __init__(self, cascade_count=0, regime_signal=0, imbalance_ma5=0.0, withdrawal_velocity=0.0): self._sig = _Sig(imbalance_ma5, withdrawal_velocity) self._macro = _Macro(cascade_count, regime_signal) def get_signal(self, asset, ts): return self._sig def get_macro(self): return self._macro # ── LINK 5e05eeeb constants (from the live tape) ───────────────────────────── LINK_ENTRY = 7.729 LINK_TP = 0.0019998464 # tp_effective_pct as recorded LINK_DIR = -1 # SHORT # (price, expected pnl_pct fraction) sequence from dolphin.v7_decision_events LINK_TAPE = [ (7.7225, 0.00084), # bars 0-1 (7.7185, 0.00136), # bars 4-7 (7.7175, 0.00149), # bars 5-9 (7.7085, 0.00265), # bars 10-12 — ABOVE base TP, below widened TP (7.7125, 0.00213), # bar 12-13 — still above base TP (7.7165, 0.00162), # bar 13-14 — REGRESSED below base TP ] def _mgr(floor=False, ob=None, tp=LINK_TP, stop=1.0, max_hold=250): m = AlphaExitManager(fixed_tp_pct=tp, stop_pct=stop, max_hold_bars=max_hold, tp_floor_enabled=floor) if ob is not None: m.ob_engine = ob return m def _short(m, trade_id="t", entry=LINK_ENTRY, bar=0): m.setup_position(trade_id, entry, LINK_DIR, bar) return trade_id # ── 1. Default-off parity (incident behavior preserved bit-exact) ─────────── def test_default_is_off(): assert AlphaExitManager().tp_floor_enabled is False def test_cascade_widened_hold_unchanged_when_floor_off(): """The incident, replayed: floor OFF + cascade ON -> HOLD through the whole profitable window and no TP_FLOOR ever — pre-change behavior.""" m = _mgr(floor=False, ob=MockOBEngine(cascade_count=3)) t = _short(m) bar = 0 for price, _pnl in LINK_TAPE: bar += 1 r = m.evaluate(t, price, bar, asset="LINKUSDT") assert r["action"] == "HOLD", (price, r) # diagnostics still present even when floor is off assert r["dynamic_tp_pct"] == pytest.approx(LINK_TP * 1.40, rel=1e-9) assert r["tp_mod_factor"] == pytest.approx(1.40, rel=1e-9) assert r["cascade_count"] == 3 assert r["tp_floor_armed"] is True def test_no_ob_engine_fixed_tp_fires_at_base(): """Without an OB engine there is no modulation: first scan at +0.265% fires plain FIXED_TP (sanity that base behavior is intact).""" m = _mgr(floor=False) t = _short(m) r = m.evaluate(t, 7.7085, 10, asset="LINKUSDT") assert (r["action"], r["reason"]) == ("EXIT", "FIXED_TP") assert r["dynamic_tp_pct"] == pytest.approx(LINK_TP) assert r["tp_mod_factor"] == pytest.approx(1.0) # ── 2. Golden LINK replay with the floor ON ───────────────────────────────── def test_link_golden_replay_floor_exits_on_regression(): """THE fix: cascade widens TP to 0.27998%; the tape peaks at 0.265% (HOLD, matching live); on the first scan back below base TP the floor fires TP_FLOOR at +0.1617% — instead of riding to -1.26% STOP_LOSS.""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=3)) t = _short(m) actions = [] bar = 0 result = None for price, _pnl in LINK_TAPE: bar += 1 result = m.evaluate(t, price, bar, asset="LINKUSDT") actions.append(result["action"]) if result["action"] == "EXIT": break assert result["action"] == "EXIT" assert result["reason"] == "TP_FLOOR" # exits on the LAST tape row (the regression scan), not earlier assert actions == ["HOLD"] * (len(LINK_TAPE) - 1) + ["EXIT"] assert result["pnl_pct"] == pytest.approx( LINK_DIR * (7.7165 - LINK_ENTRY) / LINK_ENTRY) # +0.16173% assert result["pnl_pct"] > 0.0 # banked a WIN assert result["tp_floor_armed"] is True def test_floor_does_not_fire_while_above_base(): """pnl at 0.2135% (above base 0.19998%) must NOT trigger the floor — the widened FIXED_TP logic stays in charge of capturing more.""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=1)) t = _short(m) m.evaluate(t, 7.7085, 1, asset="LINKUSDT") # arm (0.265%) r = m.evaluate(t, 7.7125, 2, asset="LINKUSDT") # 0.2135% > base assert r["action"] == "HOLD" # ── 3. Arming rules ────────────────────────────────────────────────────────── def test_floor_unarmed_below_base_never_fires(): """Excursion never reached base TP -> dips can not trigger TP_FLOOR.""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2)) t = _short(m) r1 = m.evaluate(t, 7.7185, 1, asset="LINKUSDT") # +0.136% < base assert (r1["action"], r1["tp_floor_armed"]) == ("HOLD", False) r2 = m.evaluate(t, 7.7350, 2, asset="LINKUSDT") # negative excursion assert r2["action"] == "HOLD" r3 = m.evaluate(t, 7.760, 3, asset="LINKUSDT") # -0.40% — still HOLD assert r3["action"] == "HOLD" def test_marginal_cross_then_reverse_exits_near_base(): """Cross base TP by a hair (1.0001x), reverse: floor exits ~at base — economically a 0.20% TP (the operator's stated intent). An EXACT-ulp touch is allowed not to arm (float round-trip); crossing must arm.""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2)) t = _short(m) cross = LINK_ENTRY * (1.0 - LINK_TP * 1.0001) # just through base r1 = m.evaluate(t, cross, 1, asset="LINKUSDT") # armed on the crossing bar; pnl marginally ABOVE base -> no fire yet # (pnl <= base is false by the 1.0001 margin) assert r1["action"] == "HOLD" and r1["tp_floor_armed"] is True back = LINK_ENTRY * (1.0 - LINK_TP * 0.95) # regression below base r2 = m.evaluate(t, back, 2, asset="LINKUSDT") assert (r2["action"], r2["reason"]) == ("EXIT", "TP_FLOOR") assert r2["pnl_pct"] == pytest.approx(LINK_TP * 0.95, rel=1e-6) def test_set_live_tp_pct_rebases_floor(): """The soft-leverage sync re-bases the floor each scan.""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2), tp=0.0020) t = _short(m) m.evaluate(t, 7.7085, 1, asset="LINKUSDT") # armed vs 0.20% m.set_live_tp_pct(0.0030) # TP widened to 0.30% # 0.265% max_favorable is now BELOW the new base -> floor disarmed r = m.evaluate(t, 7.7125, 2, asset="LINKUSDT") # 0.2135% assert r["action"] == "HOLD" assert r["tp_floor_armed"] is False # ── 4. Other exits untouched ───────────────────────────────────────────────── def test_stop_loss_unaffected(): m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2), stop=0.012) t = _short(m) r = m.evaluate(t, LINK_ENTRY * 1.013, 1, asset="LINKUSDT") # -1.3% (short) assert (r["action"], r["reason"]) == ("EXIT", "STOP_LOSS") def test_max_hold_unaffected(): m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2), max_hold=5) t = _short(m) r = None for bar in range(1, 7): r = m.evaluate(t, LINK_ENTRY * 1.0005, bar, asset="LINKUSDT") # small loss if r["action"] == "EXIT": break assert (r["action"], r["reason"]) == ("EXIT", "MAX_HOLD") def test_widened_fixed_tp_still_fires_above_widened(): """Cascade ON, pnl ABOVE the widened threshold -> FIXED_TP (continuation capture preserved; the floor must not pre-empt it).""" m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2)) t = _short(m) deep = LINK_ENTRY * (1.0 - LINK_TP * 1.40 * 1.1) # 10% past widened r = m.evaluate(t, deep, 1, asset="LINKUSDT") assert (r["action"], r["reason"]) == ("EXIT", "FIXED_TP") assert r["pnl_pct"] > LINK_TP * 1.40 def test_withdrawal_tightening_fires_fixed_tp_not_floor(): """regime_signal=1 with profit tightens TP x0.60 -> FIXED_TP fires below base; the floor never engages (pnl above dynamic but below base is impossible here because dynamic < base).""" m = _mgr(floor=True, ob=MockOBEngine(regime_signal=1)) t = _short(m) px = LINK_ENTRY * (1.0 - LINK_TP * 0.8) # pnl = 0.8x base r = m.evaluate(t, px, 1, asset="LINKUSDT") assert (r["action"], r["reason"]) == ("EXIT", "FIXED_TP") assert r["tp_mod_factor"] == pytest.approx(0.60, rel=1e-9) # ── 5. LONG symmetry ───────────────────────────────────────────────────────── def test_long_floor_symmetry(): m = _mgr(floor=True, ob=MockOBEngine(cascade_count=2), tp=0.0020) m.setup_position("L", 100.0, +1, 0) r1 = m.evaluate("L", 100.25, 1, asset="X") # +0.25% in band -> HOLD assert r1["action"] == "HOLD" and r1["tp_floor_armed"] is True r2 = m.evaluate("L", 100.15, 2, asset="X") # regression below base assert (r2["action"], r2["reason"]) == ("EXIT", "TP_FLOOR") assert r2["pnl_pct"] == pytest.approx(0.0015, rel=1e-6) # ── 6. Diagnostics contract ────────────────────────────────────────────────── DIAG_KEYS = ("tp_base_pct", "dynamic_tp_pct", "tp_mod_factor", "cascade_count", "ob_regime_signal", "tp_floor_armed") def test_diagnostics_on_every_decision_and_last_eval(): m = _mgr(floor=True, ob=MockOBEngine(cascade_count=4, regime_signal=0)) t = _short(m) r = m.evaluate(t, 7.7225, 1, asset="LINKUSDT") for k in DIAG_KEYS: assert k in r, k assert r["cascade_count"] == 4 le = m.last_eval assert le["trade_id"] == t and le["bar"] == 1 for k in DIAG_KEYS: assert k in le, k def test_diagnostics_defaults_without_ob_engine(): m = _mgr(floor=True) t = _short(m) r = m.evaluate(t, 7.7225, 1, asset="LINKUSDT") assert r["cascade_count"] == 0 assert r["ob_regime_signal"] == 0 assert r["tp_mod_factor"] == pytest.approx(1.0) def test_no_state_return_unchanged(): m = _mgr(floor=True) r = m.evaluate("ghost", 1.0, 1) assert (r["action"], r["reason"]) == ("HOLD", "NO_STATE") if __name__ == "__main__": sys.exit(pytest.main([__file__, "-v"]))