"""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']}")