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:
Adriano Dal Pastro
2026-06-23 16:10:38 +00:00
parent 8e46a62e67
commit de72e3ce1f
31 changed files with 5768 additions and 13 deletions
+37 -4
View File
@@ -135,13 +135,21 @@ class SkyhookParams:
# uscite — time-based asimmetrico (barre LTF)
uscitalong: int = 24
uscitashort: int = 18
# uscite — hard stop/profit
# uscite — hard stop/profit (LONG, e SHORT se gli override sotto sono None)
exit_mode: str = "atr" # 'atr' = multipli di ATR LTF ; 'pct' = percentuale fissa
sl_atr: float = 2.0
tp_atr: float = 5.0
sl_pct: float = 0.03
tp_pct: float = 0.075
ltf_atr_win: int = 14
# uscite — OVERRIDE asimmetrico SHORT (None = usa i valori simmetrici sopra).
# In crypto lo short si fa steamrollare da uno spike vola: stop short piu' stretti
# tagliano il draw-down standalone senza toccare il segnale (vedi SKH01-V2-DD, diario).
exit_mode_short: str | None = None
sl_atr_short: float | None = None
tp_atr_short: float | None = None
sl_pct_short: float | None = None
tp_pct_short: float | None = None
# ---------------------------------------------------------------------------
@@ -204,14 +212,21 @@ def skyhook_entries(ltf: pd.DataFrame, htf: pd.DataFrame, p: SkyhookParams | Non
continue
if comp_long[i]:
direction, mb = 1, p.uscitalong
mode, sl_a, tp_a, sl_p, tp_p = p.exit_mode, p.sl_atr, p.tp_atr, p.sl_pct, p.tp_pct
elif comp_short[i]:
direction, mb = -1, p.uscitashort
# SHORT: usa l'override asimmetrico dove presente, altrimenti i valori simmetrici.
mode = p.exit_mode_short if p.exit_mode_short is not None else p.exit_mode
sl_a = p.sl_atr_short if p.sl_atr_short is not None else p.sl_atr
tp_a = p.tp_atr_short if p.tp_atr_short is not None else p.tp_atr
sl_p = p.sl_pct_short if p.sl_pct_short is not None else p.sl_pct
tp_p = p.tp_pct_short if p.tp_pct_short is not None else p.tp_pct
else:
continue
if p.exit_mode == "atr":
sl_off, tp_off = p.sl_atr * a[i], p.tp_atr * a[i]
if mode == "atr":
sl_off, tp_off = sl_a * a[i], tp_a * a[i]
else:
sl_off, tp_off = p.sl_pct * c[i], p.tp_pct * c[i]
sl_off, tp_off = sl_p * c[i], tp_p * c[i]
if direction == 1:
sl, tp = c[i] - sl_off, c[i] + tp_off
else:
@@ -221,6 +236,24 @@ def skyhook_entries(ltf: pd.DataFrame, htf: pd.DataFrame, p: SkyhookParams | Non
return entries
# ---------------------------------------------------------------------------
# Config canoniche (vedi docs/diary/2026-06-23-skyhook.md)
# ---------------------------------------------------------------------------
# SKH01-V1: vincente del primo lever-scout/grid (regime gate + breakout lento + stop larghi).
SKH01_V1 = SkyhookParams(ptn_n=55, sl_atr=2.5, tp_atr=6.0, vola_lo=35.0, vola_hi=95.0, vol_lo=0.0)
# SKH01-V2-DD: vincente dell'onda DD-reduction (famiglia ASYM_LS). Stesso SEGNALE del winner
# intermedio (ptn_n=45, banda vola larga) ma EXIT a percentuale fissa ASIMMETRICA: short con SL
# piu' stretto (2% vs 4% long) -> taglia il draw-down standalone (maxDD BTC 21% / ETH 27% <30%)
# alzando hold-out e uplift di portafoglio. Verificato leak-free + 2 scettici avversariali.
SKH01_V2_DD = SkyhookParams(
ptn_n=45, vola_lo=35.0, vola_hi=95.0, vol_lo=0.0,
uscitalong=24, uscitashort=16,
exit_mode="pct", sl_pct=0.04, tp_pct=0.10, # LONG
exit_mode_short="pct", sl_pct_short=0.02, tp_pct_short=0.08, # SHORT (SL piu' stretto)
)
def signal_counts(ltf: pd.DataFrame, htf: pd.DataFrame, p: SkyhookParams | None = None) -> dict:
"""Diagnostica: quante barre passano regime/pattern/composer (prima del cap giornaliero)."""
p = p or SkyhookParams()