initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems: - prod/ (BLUE harness, configs, scripts, docs) - nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved) - adaptive_exit/ (AEM engine + models/bucket_assignments.pkl) - Observability/ (EsoF advisor, TUI, dashboards) - external_factors/ (EsoF producer) - mc_forewarning_qlabs_fork/ (MC regime/envelope) Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
This commit is contained in:
279
nautilus_dolphin/dvae/exp9b_liquidation_guard.py
Executable file
279
nautilus_dolphin/dvae/exp9b_liquidation_guard.py
Executable file
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
Exp 9b — Liquidation Guard on Extended Leverage Configs
|
||||
|
||||
Exp9 found a DD plateau after 7x soft cap: each additional leverage unit above 7x
|
||||
costs only +0.12pp DD while adding ~20pp ROI. However, exp9 does NOT model exchange
|
||||
liquidation: the existing stop_pct=1.0 is effectively disabled, and a position at
|
||||
10x leverage would be force-closed by the exchange after a 10% adverse move.
|
||||
|
||||
This experiment adds a per-trade liquidation floor using the _pending_stop_override hook:
|
||||
stop_override = (1.0 / abs_cap) * 0.95 (95% of exchange margin = early warning floor)
|
||||
|
||||
6/7x config: fires if price moves >13.6% against position in the hold window
|
||||
7/8x config: >11.9%
|
||||
8/9x config: >10.6%
|
||||
9/10x config: >9.5%
|
||||
|
||||
All of these thresholds are wide relative to a 10-min BTC hold — the 55-day gold dataset
|
||||
almost certainly contains 0 such events for B/C, possibly 0-1 for D/E.
|
||||
The goal is to CONFIRM that exp9 results are not artefacts of missing liquidation model.
|
||||
|
||||
If results are identical to exp9: liquidation risk is immaterial for this dataset.
|
||||
If results differ: we know exactly which config/day triggered it and the cost.
|
||||
|
||||
Configs run (skip A = GOLD reference, already verified in exp9):
|
||||
B_liq: 6/7x mc_ref=5.0 + liquidation guard at 13.6%
|
||||
C_liq: 7/8x mc_ref=5.0 + liquidation guard at 11.9%
|
||||
D_liq: 8/9x mc_ref=5.0 + liquidation guard at 10.6%
|
||||
E_liq: 9/10x mc_ref=5.0 + liquidation guard at 9.5%
|
||||
Total: 4 runs × ~250s ≈ 17 min
|
||||
|
||||
Results → exp9b_liquidation_guard_results.json
|
||||
"""
|
||||
|
||||
import sys, time, json, math
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
_HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(_HERE.parent))
|
||||
|
||||
from exp_shared import (
|
||||
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
|
||||
load_data, load_forewarner, log_results,
|
||||
)
|
||||
from exp9_leverage_ceiling import ExtendedLeverageEngine
|
||||
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
|
||||
from nautilus_dolphin.nautilus.proxy_boost_engine import DEFAULT_THRESHOLD, DEFAULT_ALPHA
|
||||
|
||||
# Known exp9 results for comparison
|
||||
_EXP9 = {
|
||||
'B': dict(roi=119.34, dd=16.92, trades=2155),
|
||||
'C': dict(roi=141.80, dd=18.32, trades=2155),
|
||||
'D': dict(roi=162.28, dd=18.44, trades=2155),
|
||||
'E': dict(roi=184.00, dd=18.56, trades=2155),
|
||||
}
|
||||
_GOLD_EXP9 = dict(roi=96.55, dd=14.32, trades=2155)
|
||||
|
||||
|
||||
# ── LiquidationGuardEngine ────────────────────────────────────────────────────
|
||||
|
||||
class LiquidationGuardEngine(ExtendedLeverageEngine):
|
||||
"""
|
||||
Adds an exchange-liquidation floor stop to ExtendedLeverageEngine.
|
||||
|
||||
For each entry, sets _pending_stop_override = (1/abs_cap) * margin_buffer
|
||||
BEFORE calling super()._try_entry(). The NDAlphaEngine._try_entry() consumes
|
||||
this and passes it to exit_manager.setup_position() as stop_pct_override.
|
||||
|
||||
The exit_manager then monitors: if pnl_pct < -stop_pct_override → EXIT (liquidation).
|
||||
Using abs_cap (hard ceiling) slightly underestimates the actual liquidation price,
|
||||
i.e. we exit slightly before the exchange would — a conservative model.
|
||||
|
||||
liquidation_stops counter tracks how many times this fires.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, margin_buffer: float = 0.95, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.margin_buffer = margin_buffer
|
||||
self._liq_stop_pct = (1.0 / self._extended_abs_cap) * margin_buffer
|
||||
self.liquidation_stops = 0
|
||||
|
||||
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
|
||||
v50_vel=0.0, v750_vel=0.0):
|
||||
# Arm the liquidation floor before parent entry logic consumes it
|
||||
self._pending_stop_override = self._liq_stop_pct
|
||||
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
|
||||
v50_vel, v750_vel)
|
||||
return result
|
||||
|
||||
def _execute_exit(self, reason, bar_idx, pnl_pct_raw=0.0, bars_held=0):
|
||||
if reason == 'STOP_LOSS':
|
||||
self.liquidation_stops += 1
|
||||
return super()._execute_exit(reason, bar_idx, pnl_pct_raw, bars_held)
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self._liq_stop_pct = (1.0 / self._extended_abs_cap) * self.margin_buffer
|
||||
self.liquidation_stops = 0
|
||||
|
||||
|
||||
# ── Run harness ───────────────────────────────────────────────────────────────
|
||||
|
||||
def _run(engine_factory, name, d, fw):
|
||||
kw = ENGINE_KWARGS.copy()
|
||||
acb = AdaptiveCircuitBreaker()
|
||||
acb.preload_w750(d['date_strings'])
|
||||
|
||||
eng = engine_factory(kw)
|
||||
eng.set_ob_engine(d['ob_eng'])
|
||||
eng.set_acb(acb)
|
||||
if fw is not None:
|
||||
eng.set_mc_forewarner(fw, MC_BASE_CFG)
|
||||
eng.set_esoteric_hazard_multiplier(0.0)
|
||||
|
||||
daily_caps, daily_pnls = [], []
|
||||
for pf in d['parquet_files']:
|
||||
ds = pf.stem
|
||||
df, acols, dvol = d['pq_data'][ds]
|
||||
cap_before = eng.capital
|
||||
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
|
||||
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
|
||||
daily_caps.append(eng.capital)
|
||||
daily_pnls.append(eng.capital - cap_before)
|
||||
|
||||
tr = eng.trade_history
|
||||
n = len(tr)
|
||||
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
|
||||
|
||||
liq_stops = getattr(eng, 'liquidation_stops', 0)
|
||||
|
||||
if n == 0:
|
||||
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
|
||||
trades=0, avg_leverage=0.0, liq_stops=liq_stops,
|
||||
liq_stop_pct=getattr(eng, '_liq_stop_pct', 0.0))
|
||||
|
||||
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
|
||||
wins = [t for t in tr if _abs(t) > 0]
|
||||
losses = [t for t in tr if _abs(t) <= 0]
|
||||
wr = len(wins) / n * 100.0
|
||||
pf_val = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
|
||||
|
||||
peak_cap, max_dd = 25000.0, 0.0
|
||||
for cap in daily_caps:
|
||||
peak_cap = max(peak_cap, cap)
|
||||
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
|
||||
|
||||
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
|
||||
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
|
||||
|
||||
lev_vals = [t.leverage for t in tr if hasattr(t, 'leverage') and t.leverage > 0]
|
||||
avg_lev = float(np.mean(lev_vals)) if lev_vals else 0.0
|
||||
|
||||
return dict(
|
||||
name=name, roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe,
|
||||
trades=n, avg_leverage=avg_lev,
|
||||
liq_stops=liq_stops,
|
||||
liq_stop_pct=getattr(eng, '_liq_stop_pct', 0.0),
|
||||
liq_stop_rate_pct=liq_stops / n * 100.0 if n else 0.0,
|
||||
)
|
||||
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
t_start = time.time()
|
||||
print("=" * 76)
|
||||
print("Exp 9b — Liquidation Guard on Extended Leverage Configs")
|
||||
print("=" * 76)
|
||||
print(" Adding exchange-liquidation floor stop to exp9 B/C/D/E configs.")
|
||||
print(" Stop fires if adverse move exceeds (1/abs_cap)*0.95 of position.")
|
||||
|
||||
ensure_jit()
|
||||
d = load_data()
|
||||
fw = load_forewarner()
|
||||
|
||||
_PROXY = dict(threshold=DEFAULT_THRESHOLD, alpha=DEFAULT_ALPHA,
|
||||
adaptive_beta=True, adaptive_alpha=False, adaptive_thr=False)
|
||||
|
||||
# (label, soft_cap, abs_cap, mc_ref, exp9_key)
|
||||
configs = [
|
||||
("B_liq_6/7x_liqstop13.6%", 6.0, 7.0, 5.0, 'B'),
|
||||
("C_liq_7/8x_liqstop11.9%", 7.0, 8.0, 5.0, 'C'),
|
||||
("D_liq_8/9x_liqstop10.6%", 8.0, 9.0, 5.0, 'D'),
|
||||
("E_liq_9/10x_liqstop9.5%", 9.0, 10.0, 5.0, 'E'),
|
||||
]
|
||||
|
||||
results = []
|
||||
for i, (label, soft, hard, mc_ref, exp9_key) in enumerate(configs):
|
||||
t0 = time.time()
|
||||
liq_pct = (1.0 / hard) * 0.95 * 100
|
||||
print(f"\n[{i+1}/{len(configs)}] {label} (floor={liq_pct:.1f}%) ...")
|
||||
|
||||
def _factory(kw, s=soft, h=hard, r=mc_ref):
|
||||
return LiquidationGuardEngine(
|
||||
extended_soft_cap=s, extended_abs_cap=h, mc_leverage_ref=r,
|
||||
margin_buffer=0.95,
|
||||
**_PROXY, **kw,
|
||||
)
|
||||
|
||||
res = _run(_factory, label, d, fw)
|
||||
elapsed = time.time() - t0
|
||||
|
||||
ref = _EXP9[exp9_key]
|
||||
dROI = res['roi'] - ref['roi']
|
||||
dDD = res['dd'] - ref['dd']
|
||||
print(f" ROI={res['roi']:>8.2f}% (vs exp9: {dROI:+.2f}pp) "
|
||||
f"DD={res['dd']:>6.2f}% (vs exp9: {dDD:+.2f}pp) "
|
||||
f"Trades={res['trades']}")
|
||||
print(f" avg_lev={res['avg_leverage']:.2f}x "
|
||||
f"liquidation_stops={res['liq_stops']} "
|
||||
f"liq_rate={res['liq_stop_rate_pct']:.2f}% ({elapsed:.0f}s)")
|
||||
|
||||
results.append(res)
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────────
|
||||
print(f"\n{'='*76}")
|
||||
print("LIQUIDATION GUARD IMPACT vs EXP9 (unguarded)")
|
||||
print(f"{'Config':<34} {'ROI(9b)':>8} {'ROI(9)':>8} {'ΔROI':>7} "
|
||||
f"{'DD(9b)':>7} {'DD(9)':>7} {'ΔDD':>6} {'LiqStops':>9}")
|
||||
print('-' * 90)
|
||||
for r in results:
|
||||
key = r['name'][0] # B/C/D/E
|
||||
ref = _EXP9[key]
|
||||
dROI = r['roi'] - ref['roi']
|
||||
dDD = r['dd'] - ref['dd']
|
||||
identical = (abs(dROI) < 0.01 and abs(dDD) < 0.01 and r['liq_stops'] == 0)
|
||||
flag = '✓ identical' if identical else '✗ CHANGED'
|
||||
print(f"{r['name']:<34} {r['roi']:>8.2f} {ref['roi']:>8.2f} {dROI:>+7.2f} "
|
||||
f"{r['dd']:>7.2f} {ref['dd']:>7.2f} {dDD:>+6.2f} "
|
||||
f"{r['liq_stops']:>6} {flag}")
|
||||
|
||||
# ── Compounding with liquidation-adjusted numbers ─────────────────────────
|
||||
print(f"\n{'='*76}")
|
||||
print("COMPOUNDING TABLE — liquidation-adjusted (starting $25k, 56-day periods)")
|
||||
all_configs = [
|
||||
("GOLD 5/6x", _GOLD_EXP9['roi'], _GOLD_EXP9['dd']),
|
||||
] + [
|
||||
(r['name'][:10], r['roi'], r['dd']) for r in results
|
||||
]
|
||||
print(f"{'Config':<20} {'ROI%':>7} {'DD%':>6} {'Calmar':>7} "
|
||||
f"{'3per(~5mo)':>12} {'6per(~1yr)':>12} {'12per(~2yr)':>13}")
|
||||
print('-' * 85)
|
||||
for label, roi, dd in all_configs:
|
||||
mult = 1.0 + roi / 100.0
|
||||
calmar = roi / dd if dd > 0 else 0
|
||||
v3 = 25000 * mult**3
|
||||
v6 = 25000 * mult**6
|
||||
v12 = 25000 * mult**12
|
||||
print(f"{label:<20} {roi:>7.2f} {dd:>6.2f} {calmar:>7.2f} "
|
||||
f"${v3:>11,.0f} ${v6:>11,.0f} ${v12:>12,.0f}")
|
||||
|
||||
print(f"\n{'='*76}")
|
||||
any_changed = any(r['liq_stops'] > 0 for r in results)
|
||||
if not any_changed:
|
||||
print("VERDICT: Zero liquidation stops fired across all configs.")
|
||||
print(" Exp9 results are accurate — liquidation risk is immaterial for this 55-day dataset.")
|
||||
print(" The compounding numbers from exp9 stand as-is.")
|
||||
else:
|
||||
changed = [r for r in results if r['liq_stops'] > 0]
|
||||
print(f"VERDICT: {sum(r['liq_stops'] for r in results)} liquidation stop(s) fired.")
|
||||
for r in changed:
|
||||
print(f" {r['name']}: {r['liq_stops']} stops, ΔROI={r['roi']-_EXP9[r['name'][0]]['roi']:+.2f}pp")
|
||||
|
||||
outfile = _HERE / "exp9b_liquidation_guard_results.json"
|
||||
log_results(results, outfile, gold=_GOLD_EXP9, meta={
|
||||
"exp": "exp9b",
|
||||
"question": "Do liquidation stops fire in 55-day dataset? Are exp9 results accurate?",
|
||||
"exp9_reference": _EXP9,
|
||||
"total_elapsed_s": round(time.time() - t_start, 1),
|
||||
})
|
||||
|
||||
total = time.time() - t_start
|
||||
print(f"\nTotal elapsed: {total / 60:.1f} min")
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user