Files
PythagorasGoal/scripts/research/skyhook/runs/SKH_P_LOCAL.py
T
Adriano Dal Pastro de72e3ce1f 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>
2026-06-23 16:10:38 +00:00

77 lines
3.8 KiB
Python

"""SKH_P_LOCAL — coordinate/local search around SKH01-V1.
V1: SkyhookParams(ptn_n=55, sl_atr=2.5, tp_atr=6.0, vola_lo=35, vola_hi=95, vol_lo=0.0)
-> minFull +0.69, minHold +0.64 (BTC0.64/ETH0.64), maxDD ~49% (BTC), clean_year(2025)=0.014
robust_oos=False ONLY because clean_year_uplift 0.014 < 0.02.
GOAL: (a) push 2025 clean-year uplift > 0.02 (-> robust_oos True, fully earns slot),
(b) cut DD toward <35%, keeping minHold>=0.5, minFull>=0.5, fee survives 0.30%RT, >=20 trades.
Strategy: V1's 2025 is weak (BTC+2/ETH-2). Cleaner regime gating + tighter SL can both lift the
2025 contribution AND cut the BTC DD. Local coordinate sweep on the high-leverage knobs, each near V1.
"""
from __future__ import annotations
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/skyhook")
import skyhooklib as sk
from src.strategies.skyhook import SkyhookParams
V1 = dict(ptn_n=55, sl_atr=2.5, tp_atr=6.0, vola_lo=35.0, vola_hi=95.0, vol_lo=0.0)
def evalp(**over):
p = SkyhookParams(**{**V1, **over})
out = {}
for a in ("BTC", "ETH"):
out[a] = sk.run_asset(a, p, fee_rt=sk.FEE_RT)
min_full = min(out[a]["full"]["sharpe"] for a in out)
min_hold = min(out[a]["holdout"]["sharpe"] for a in out)
min_tr = min(out[a]["full"]["n_trades"] for a in out)
max_dd = max(out[a]["full"]["maxdd"] for a in out)
return dict(p=p, over=over, min_full=min_full, min_hold=min_hold, min_tr=min_tr, max_dd=max_dd,
btc_dd=out["BTC"]["full"]["maxdd"], eth_dd=out["ETH"]["full"]["maxdd"],
btc_h=out["BTC"]["holdout"]["sharpe"], eth_h=out["ETH"]["holdout"]["sharpe"])
def row(tag, r):
elig = (r["min_full"] >= 0.5 and r["min_tr"] >= 20)
print(f"{tag:<28} minFull={r['min_full']:+.2f} minHold={r['min_hold']:+.2f} "
f"maxDD={r['max_dd']*100:>3.0f}% (btc{r['btc_dd']*100:.0f}/eth{r['eth_dd']*100:.0f}) "
f"minTr={r['min_tr']:>3} {'OK' if elig else 'x'}")
return r
print("=== SKH_P_LOCAL coordinate search around V1 (fee 0.10%RT) ===")
base = row("V1", evalp())
cands = [("V1", base)]
# Axis 1: SL tighter to cut DD (V1 sl=2.5). Lower sl -> lower DD, but may cut hold.
for sl in (1.75, 2.0, 2.25, 2.5):
for tp in (5.0, 6.0, 7.0):
cands.append((f"sl{sl}_tp{tp}", row(f"sl{sl}_tp{tp}", evalp(sl_atr=sl, tp_atr=tp))))
# Axis 2: regime band — tighten vola top (avoid blow-off) & raise vola_lo to skip dead vol.
for vlo, vhi in ((40,90),(45,90),(40,85),(35,90),(45,85),(50,90)):
cands.append((f"vola{vlo}-{vhi}", row(f"vola{vlo}-{vhi}", evalp(vola_lo=float(vlo), vola_hi=float(vhi)))))
# Axis 3: add a volume floor (V1 vol_lo=0 = no vol gate). A floor concentrates into live regimes.
for vol_lo in (30.0, 40.0, 50.0):
cands.append((f"vol_lo{vol_lo}", row(f"vol_lo{vol_lo}", evalp(vol_lo=vol_lo))))
# Axis 4: ptn_n around 55.
for ptn in (45, 50, 60, 65):
cands.append((f"ptn{ptn}", row(f"ptn{ptn}", evalp(ptn_n=ptn))))
# Axis 5: exit bars (asymmetry).
for ul, us in ((24,18),(30,18),(20,14),(28,14)):
cands.append((f"ex{ul}/{us}", row(f"ex{ul}/{us}", evalp(uscitalong=ul, uscitashort=us))))
# Filter eligible (the constraints), rank by min_hold then lower DD.
elig = [(t,r) for (t,r) in cands if r["min_full"] >= 0.5 and r["min_tr"] >= 20 and r["min_hold"] >= 0.5]
print(f"\nEligible (minFull>=0.5, minHold>=0.5, minTr>=20): {len(elig)}")
elig.sort(key=lambda tr: (-round(tr[1]["min_hold"],3), tr[1]["max_dd"]))
for t,r in elig[:10]:
print(f" {t:<22} minHold={r['min_hold']:+.2f} minFull={r['min_full']:+.2f} maxDD={r['max_dd']*100:.0f}% over={r['over']}")
# Low-DD subset
print("\nLowest-DD eligible:")
for t,r in sorted(elig, key=lambda tr:(tr[1]["max_dd"], -tr[1]["min_hold"]))[:8]:
print(f" {t:<22} maxDD={r['max_dd']*100:.0f}% minHold={r['min_hold']:+.2f} minFull={r['min_full']:+.2f} over={r['over']}")