feat(skyhook): SKH01-V2-DD — asymmetric %-exits cut standalone DD <30% (2-wave agent research)
Second agent wave (skyhook-improve-v2, 14 DD-reduction families, each adversarially verified by 2 skeptics) beats the prior winner on the only unmet goal (DD<30%). Winner = ASYM_LS -> promoted to engine as SKH01_V2_DD: same signal (ptn_n=45, vola[35,95], vol_lo=0, exit-bars 24/16) but exits switched from ATR to FIXED-PCT ASYMMETRIC — long sl4%/tp10%, short sl2%(tighter)/tp8%. The tight short %-SL caps the per-trade loss that forms the maxDD in vol spikes. Verified (sk.study, independent re-run): standalone maxDD BTC 21.4% / ETH 27.4% (<30%), minFull +0.99, minHold +1.26, causality 0/400 both assets, fee-surviving to 0.40%RT, marginal vs TP01 ADDS (corr 0.09, in-sample edge, robust_oos, multicut, clean-year +0.57), blend 0.75*TP01+0.25*SKH uplift_hold +0.87; blend 50/50 full 1.84/hold 1.59/DD 10.7%. Plateau (not knife-edge); both skeptics holds_up=high, killer=null. Engine: per-direction short exit overrides (exit_mode_short/sl_*_short/tp_*_short), backward-compatible (None -> symmetric, V1/intermediate-winner unchanged). +3 tests (8/8 pass). Lessons: DD is cut by changing the exit MECHANISM (%-SL, L/S asymmetry, ensembles), NOT by entry-only kill-switch / vol-target / cadence. PATTERN_CONF killed as overfit (knife-edge). PCTL_DD unverified (rate-limit) and ENS_PARAM/TPSL_DD recency/hedge-loaded -> forward-monitor. NOT yet wired to live sleeves: re-verify blend@0.25 + causality on execution code before deploy. Includes both waves' research scripts (runs/SKH_* wave 1, runs/SKH2_* wave 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+52
-1
@@ -15,7 +15,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "skyhook"))
|
||||
|
||||
from src.data.downloader import load_data
|
||||
from src.strategies.skyhook import (
|
||||
HTF_MIN, LTF_MIN, SkyhookParams, build_frames, chande01, skyhook_entries)
|
||||
HTF_MIN, LTF_MIN, SKH01_V2_DD, SkyhookParams, build_frames, chande01, skyhook_entries)
|
||||
|
||||
# config V1 (vincente del lever-scout/grid; vedi diario 2026-06-23-skyhook)
|
||||
V1 = dict(ptn_n=55, sl_atr=2.5, tp_atr=6.0, vola_lo=35, vola_hi=95, vol_lo=0.0)
|
||||
@@ -89,3 +89,54 @@ def test_v1_robust_both_assets():
|
||||
assert r["holdout"]["sharpe"] >= 0.2, f"{a} HOLD-OUT Sharpe basso: {r['holdout']['sharpe']}"
|
||||
assert r["full"]["n_trades"] >= 20, f"{a} troppo pochi trade: {r['full']['n_trades']}"
|
||||
assert sk.causality(p, "BTC")["ok"] and sk.causality(p, "ETH")["ok"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exit asimmetrici SHORT (SKH01-V2-DD): l'override cambia SOLO gli short; i default
|
||||
# (None) preservano esattamente il comportamento simmetrico precedente.
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_short_override_backward_compatible():
|
||||
"""Con gli override SHORT a None, gli ingressi sono identici alla versione simmetrica."""
|
||||
ltf, htf = build_frames(load_data("BTC", "5m"))
|
||||
base = SkyhookParams(**V1)
|
||||
# stessi parametri ma con campi override esplicitamente None (= default)
|
||||
same = SkyhookParams(**V1, exit_mode_short=None, sl_pct_short=None, tp_pct_short=None)
|
||||
e0, e1 = skyhook_entries(ltf, htf, base), skyhook_entries(ltf, htf, same)
|
||||
assert e0 == e1, "i campi override a None NON devono cambiare nulla (backward-compat)"
|
||||
|
||||
|
||||
def test_short_override_changes_only_shorts():
|
||||
"""Un SL short piu' stretto (pct) modifica gli stop SHORT ma lascia intatti i LONG."""
|
||||
ltf, htf = build_frames(load_data("ETH", "5m"))
|
||||
sym = SkyhookParams(ptn_n=45, vola_lo=35, vola_hi=95, vol_lo=0.0,
|
||||
exit_mode="pct", sl_pct=0.04, tp_pct=0.10)
|
||||
asym = SkyhookParams(ptn_n=45, vola_lo=35, vola_hi=95, vol_lo=0.0,
|
||||
exit_mode="pct", sl_pct=0.04, tp_pct=0.10,
|
||||
sl_pct_short=0.02, tp_pct_short=0.08)
|
||||
es, ea = skyhook_entries(ltf, htf, sym), skyhook_entries(ltf, htf, asym)
|
||||
longs_same = shorts_diff = 0
|
||||
for a, b in zip(es, ea):
|
||||
if a is None or b is None:
|
||||
assert (a is None) == (b is None)
|
||||
continue
|
||||
assert a["dir"] == b["dir"]
|
||||
if a["dir"] == 1: # LONG invariati
|
||||
assert abs(a["sl"] - b["sl"]) < 1e-6 and abs(a["tp"] - b["tp"]) < 1e-6
|
||||
longs_same += 1
|
||||
else: # SHORT con SL/TP diversi
|
||||
assert abs(a["sl"] - b["sl"]) > 1e-6
|
||||
shorts_diff += 1
|
||||
assert longs_same > 0 and shorts_diff > 0
|
||||
|
||||
|
||||
def test_v2dd_robust_both_assets():
|
||||
"""SKH01-V2-DD: PASS netto fee su BTCÐ, hold-out forte, e maxDD standalone <30%."""
|
||||
import skyhooklib as sk
|
||||
p = SKH01_V2_DD
|
||||
for a in ("BTC", "ETH"):
|
||||
r = sk.run_asset(a, p, sk.FEE_RT)
|
||||
assert r["full"]["sharpe"] >= 0.5, f"{a} FULL Sharpe basso: {r['full']['sharpe']}"
|
||||
assert r["holdout"]["sharpe"] >= 0.5, f"{a} HOLD-OUT Sharpe basso: {r['holdout']['sharpe']}"
|
||||
assert r["full"]["maxdd"] < 0.30, f"{a} maxDD non sotto 30%: {r['full']['maxdd']}"
|
||||
assert r["full"]["n_trades"] >= 20, f"{a} troppo pochi trade: {r['full']['n_trades']}"
|
||||
assert sk.causality(p, "BTC")["ok"] and sk.causality(p, "ETH")["ok"]
|
||||
|
||||
Reference in New Issue
Block a user