de72e3ce1f
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>
108 lines
4.8 KiB
Python
108 lines
4.8 KiB
Python
"""SKH_P_REGIME — regime-band sweep on the V1 base.
|
|
|
|
Search bands: vola_lo in {20,30,35,45}, vola_hi in {88,95,100},
|
|
vol_lo in {0,30,45,55}, vol_hi in {80,100}.
|
|
Find the combo that lifts min HOLD-OUT and is a PLATEAU (neighbors also good).
|
|
Compare to 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.
|
|
"""
|
|
from __future__ import annotations
|
|
import sys, itertools
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/skyhook")
|
|
import skyhooklib as sk
|
|
from src.strategies.skyhook import SkyhookParams
|
|
|
|
ASSETS = ("BTC", "ETH")
|
|
|
|
# V1 base (everything except the regime bands stays fixed)
|
|
BASE = dict(ptn_n=55, sl_atr=2.5, tp_atr=6.0)
|
|
|
|
VOLA_LO = [20, 30, 35, 45]
|
|
VOLA_HI = [88, 95, 100]
|
|
VOL_LO = [0, 30, 45, 55]
|
|
VOL_HI = [80, 100]
|
|
|
|
|
|
def mk(vlo, vhi, vol_lo, vol_hi):
|
|
return SkyhookParams(vola_lo=vlo, vola_hi=vhi, vol_lo=vol_lo, vol_hi=vol_hi, **BASE)
|
|
|
|
|
|
def eval_combo(vlo, vhi, vol_lo, vol_hi):
|
|
p = mk(vlo, vhi, vol_lo, vol_hi)
|
|
res = {}
|
|
for a in ASSETS:
|
|
r = sk.run_asset(a, p, sk.FEE_RT)
|
|
res[a] = r
|
|
min_full = min(res[a]["full"]["sharpe"] for a in ASSETS)
|
|
min_hold = min(res[a]["holdout"]["sharpe"] for a in ASSETS)
|
|
min_tr = min(res[a]["full"]["n_trades"] for a in ASSETS)
|
|
max_dd = max(res[a]["full"]["maxdd"] for a in ASSETS)
|
|
return dict(vlo=vlo, vhi=vhi, vol_lo=vol_lo, vol_hi=vol_hi,
|
|
min_full=min_full, min_hold=min_hold, min_tr=min_tr, max_dd=max_dd,
|
|
btc_hold=res["BTC"]["holdout"]["sharpe"], eth_hold=res["ETH"]["holdout"]["sharpe"],
|
|
btc_full=res["BTC"]["full"]["sharpe"], eth_full=res["ETH"]["full"]["sharpe"])
|
|
|
|
|
|
def main():
|
|
rows = []
|
|
combos = list(itertools.product(VOLA_LO, VOLA_HI, VOL_LO, VOL_HI))
|
|
print(f"Sweeping {len(combos)} band combos x 2 assets = {len(combos)*2} run_asset calls")
|
|
for vlo, vhi, vol_lo, vol_hi in combos:
|
|
rows.append(eval_combo(vlo, vhi, vol_lo, vol_hi))
|
|
|
|
# rank by min HOLD-OUT (subject to minFull>=0.5 and >=20 trades both assets)
|
|
valid = [r for r in rows if r["min_full"] >= 0.5 and r["min_tr"] >= 20]
|
|
valid.sort(key=lambda r: r["min_hold"], reverse=True)
|
|
|
|
print("\n=== TOP 15 by min HOLD-OUT (minFull>=0.5, minTrades>=20) ===")
|
|
print(f"{'vlo':>4}{'vhi':>5}{'vol_lo':>7}{'vol_hi':>7} {'minF':>6}{'minH':>6}{'minTr':>6}{'maxDD':>7} {'btcH':>6}{'ethH':>6}")
|
|
for r in valid[:15]:
|
|
print(f"{r['vlo']:>4}{r['vhi']:>5}{r['vol_lo']:>7}{r['vol_hi']:>7} "
|
|
f"{r['min_full']:>6.2f}{r['min_hold']:>6.2f}{r['min_tr']:>6}{r['max_dd']*100:>6.0f}% "
|
|
f"{r['btc_hold']:>6.2f}{r['eth_hold']:>6.2f}")
|
|
|
|
# full table sorted for plateau inspection (group by lo/hi neighbors)
|
|
rows_sorted = sorted(rows, key=lambda r: r["min_hold"], reverse=True)
|
|
print("\n=== ALL combos by min HOLD-OUT ===")
|
|
for r in rows_sorted:
|
|
flag = "" if (r["min_full"] >= 0.5 and r["min_tr"] >= 20) else " (low-full/trades)"
|
|
print(f" vlo={r['vlo']:>3} vhi={r['vhi']:>3} vol_lo={r['vol_lo']:>3} vol_hi={r['vol_hi']:>3} | "
|
|
f"minF={r['min_full']:+.2f} minH={r['min_hold']:+.2f} tr={r['min_tr']:>3} DD={r['max_dd']*100:.0f}%{flag}")
|
|
|
|
if not valid:
|
|
print("\nNo valid combo (minFull>=0.5 & >=20 trades). Best raw:")
|
|
print(rows_sorted[0])
|
|
return
|
|
|
|
# plateau check: for the top combo, look at neighbors in the grid
|
|
top = valid[0]
|
|
print(f"\n=== WINNER: vlo={top['vlo']} vhi={top['vhi']} vol_lo={top['vol_lo']} vol_hi={top['vol_hi']} ===")
|
|
print(f" minFull={top['min_full']:+.2f} minHold={top['min_hold']:+.2f} minTr={top['min_tr']} maxDD={top['max_dd']*100:.0f}%")
|
|
|
|
# neighbor plateau: same vol_lo/vol_hi, vary vola_lo/vola_hi to adjacent grid values
|
|
def find(vlo, vhi, vol_lo, vol_hi):
|
|
for r in rows:
|
|
if r["vlo"]==vlo and r["vhi"]==vhi and r["vol_lo"]==vol_lo and r["vol_hi"]==vol_hi:
|
|
return r
|
|
return None
|
|
print("\n Plateau neighbors (min HOLD-OUT):")
|
|
for vlo in VOLA_LO:
|
|
for vhi in VOLA_HI:
|
|
r = find(vlo, vhi, top['vol_lo'], top['vol_hi'])
|
|
if r:
|
|
mark = " <-- WIN" if (vlo==top['vlo'] and vhi==top['vhi']) else ""
|
|
print(f" vola_lo={vlo:>3} vola_hi={vhi:>3}: minH={r['min_hold']:+.2f} minF={r['min_full']:+.2f}{mark}")
|
|
|
|
# final study + causality + marginal on the winner
|
|
p = mk(top['vlo'], top['vhi'], top['vol_lo'], top['vol_hi'])
|
|
print("\n=== STUDY (winner) ===")
|
|
rep = sk.study(f"SKH_P_REGIME_vlo{top['vlo']}_vhi{top['vhi']}_vollo{top['vol_lo']}_volhi{top['vol_hi']}", p)
|
|
print(sk.fmt(rep))
|
|
print("\ncausality:", sk.causality(p))
|
|
print("\nmarginal:", sk.marginal(p))
|
|
print("\nas_json:", sk.as_json(rep))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|