"""SKH2_CHANDE_WIN — DD-reduction wave: re-tune indicator WINDOWS (Chande/ATR) for DD. Family task: smoother indicators -> more stable regime -> potentially lower standalone maxDD. We hold the VERIFIED V2 winner's pattern/exits/bands FIXED and sweep ONLY the windows: n_vola, n_volume in {7,13,21,34} atr_win in {10,14,21} ltf_atr_win in {10,14,21} Everything is expressible via SkyhookParams -> the SHARED honest harness sk.study() applies the exact leak-free FULL+HOLDOUT+fee-sweep+per-year machinery, and sk.causality / sk.marginal give the same comparable numbers as every other agent. WINNER (baseline to beat): SkyhookParams(ptn_n=45, sl_atr=2.5, tp_atr=7.0, uscitalong=24, uscitashort=16, vola_lo=35.0, vola_hi=95.0, vol_lo=0.0) minFull +0.83 minHold +0.81 ; standalone DD BTC 34% / ETH 31% (>30% = the problem). GOAL: max_dd < 0.30 while keeping minHold >= ~0.70 and earns_slot True, blend w25 uplift_hold >= 0.55. """ from __future__ import annotations import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/skyhook") import itertools import skyhooklib as sk from src.strategies.skyhook import SkyhookParams # ---- fixed winner spine (pattern / exits / bands) -------------------------- FIXED = dict(ptn_n=45, sl_atr=2.5, tp_atr=7.0, uscitalong=24, uscitashort=16, vola_lo=35.0, vola_hi=95.0, vol_lo=0.0) def mk(n_vola, n_volume, atr_win, ltf_atr_win): return SkyhookParams(n_vola=n_vola, n_volume=n_volume, atr_win=atr_win, ltf_atr_win=ltf_atr_win, **FIXED) def cheap_eval(p): """Fast standalone screen: FULL+HOLD on BTCÐ only (no fee-sweep/marginal).""" rb = sk.run_asset("BTC", p) re = sk.run_asset("ETH", p) min_full = min(rb["full"]["sharpe"], re["full"]["sharpe"]) min_hold = min(rb["holdout"]["sharpe"], re["holdout"]["sharpe"]) max_dd = max(rb["full"]["maxdd"], re["full"]["maxdd"]) min_tr = min(rb["full"]["n_trades"], re["full"]["n_trades"]) return dict(min_full=min_full, min_hold=min_hold, max_dd=max_dd, min_tr=min_tr, btc_dd=rb["full"]["maxdd"], eth_dd=re["full"]["maxdd"], btc_hold=rb["holdout"]["sharpe"], eth_hold=re["holdout"]["sharpe"]) def earns_slot(rep, marg): return (rep["verdict"]["grade"] != "FAIL" and marg.get("marginal_verdict") == "ADDS" and bool(marg.get("robust_oos")) and not bool(marg.get("is_hedge"))) def beats_winner(rep, marg, ev): es = earns_slot(rep, marg) w25 = marg.get("blends", {}).get("w25", {}).get("uplift_hold") mh = rep["verdict"]["min_asset_holdout_sharpe"] return bool(es and ev["max_dd"] < 0.30 and (w25 is not None and w25 >= 0.55) and mh >= 0.65) # ---- WINNER reference (so DD comparison is apples-to-apples in THIS harness) ---- def winner_params(): return SkyhookParams(**FIXED) if __name__ == "__main__": print("########## STAGE 1: cheap window screen (FULL+HOLD+DD, BTCÐ) ##########") # winner reference in this harness wev = cheap_eval(winner_params()) print(f"[WINNER ref] minFull={wev['min_full']:+.2f} minHold={wev['min_hold']:+.2f} " f"maxDD={wev['max_dd']*100:.0f}% (BTC {wev['btc_dd']*100:.0f}% / ETH {wev['eth_dd']*100:.0f}%) " f"minTr={wev['min_tr']}") n_vola_grid = [7, 13, 21, 34] n_volume_grid = [7, 13, 21, 34] atr_grid = [10, 14, 21] ltf_grid = [10, 14, 21] rows = [] for nva, nvo, aw, law in itertools.product(n_vola_grid, n_volume_grid, atr_grid, ltf_grid): p = mk(nva, nvo, aw, law) ev = cheap_eval(p) rows.append((nva, nvo, aw, law, ev)) # Sort by lowest DD among those that keep some hold-out edge & enough trades def keyf(r): ev = r[4] return ev["max_dd"] viable = [r for r in rows if r[4]["min_tr"] >= 20] viable.sort(key=keyf) print(f"\n--- {len(rows)} configs screened. Top 15 by LOWEST standalone maxDD " f"(min_tr>=20) ---") print(f"{'nva':>4}{'nvo':>4}{'aw':>4}{'law':>5} {'maxDD':>7} {'btcDD':>6} {'ethDD':>6} " f"{'minFull':>8} {'minHold':>8} {'minTr':>6}") for nva, nvo, aw, law, ev in viable[:15]: print(f"{nva:>4}{nvo:>4}{aw:>4}{law:>5} {ev['max_dd']*100:>6.1f}% " f"{ev['btc_dd']*100:>5.1f}% {ev['eth_dd']*100:>5.1f}% " f"{ev['min_full']:>+8.2f} {ev['min_hold']:>+8.2f} {ev['min_tr']:>6}") # STAGE-1 LEARNING (from broad probes): no window combo gets BOTH BTCÐ sub-30% # (BTC & ETH DD move in OPPOSITE directions vs n_vola/atr_win). The best DD-CUT that # also keeps hold-out is the SLOWER-INDICATOR corner. Study the lowest-DD configs that # still keep minHold>=0.50 (the real DD/hold tradeoff frontier), plus a couple extras # found by the broad probe (n_vola=13, slower atr_win/ltf_atr_win). extra = [(13, 13, 18, 18), (13, 13, 14, 18), (13, 13, 21, 18)] # (nva,nvo,aw,law) # candidates that cut DD while keeping hold-out cands = [r for r in viable if r[4]["min_hold"] >= 0.50 and r[4]["min_full"] >= 0.50] cands.sort(key=lambda r: r[4]["max_dd"]) # lowest DD first study_keys = set() study_list = [] for r in cands[:5]: k = (r[0], r[1], r[2], r[3]) if k not in study_keys: study_keys.add(k); study_list.append(r) # ensure the broad-probe extras are studied (they may not be on the coarse grid) for nva, nvo, aw, law in extra: k = (nva, nvo, aw, law) if k not in study_keys: ev = cheap_eval(mk(nva, nvo, aw, law)) study_keys.add(k); study_list.append((nva, nvo, aw, law, ev)) if not study_list: study_list = viable[:4] print(f"\n########## STAGE 2: FULL study + causality + marginal on " f"{len(study_list)} candidate(s) ##########") results = [] for nva, nvo, aw, law, ev in study_list: p = mk(nva, nvo, aw, law) name = f"CW_nva{nva}_nvo{nvo}_aw{aw}_law{law}" rep = sk.study(name, p) caus_b = sk.causality(p, "BTC") caus_e = sk.causality(p, "ETH") marg = sk.marginal(p) caus_ok = bool(caus_b["ok"] and caus_e["ok"]) es = earns_slot(rep, marg) bw = beats_winner(rep, marg, ev) and caus_ok w25 = marg.get("blends", {}).get("w25", {}).get("uplift_hold") w50 = marg.get("blends", {}).get("w50", {}) results.append(dict(name=name, p=p, rep=rep, marg=marg, ev=ev, caus_ok=caus_ok, es=es, bw=bw, w25=w25, w50=w50, cfg=dict(n_vola=nva, n_volume=nvo, atr_win=aw, ltf_atr_win=law))) print("\n" + sk.fmt(rep)) print(f" causality BTC={caus_b} ETH={caus_e}") print(f" marginal: verdict={marg.get('marginal_verdict')} corr_full={marg.get('corr_full')} " f"has_insample_edge={marg.get('has_insample_edge')} is_hedge={marg.get('is_hedge')} " f"robust_oos={marg.get('robust_oos')} multicut_persistent={marg.get('multicut_persistent')}") print(f" clean_year_uplift={marg.get('clean_year_uplift')} " f"blend_w25_uplift_hold={w25} w50={w50}") print(f" >> earns_slot={es} beats_winner={bw} standaloneDD={ev['max_dd']*100:.1f}%") # ---- pick best: prefer beats_winner; else lowest DD among earns_slot; else lowest DD ---- def rank(r): # higher is better. Priority: (1) beats_winner, (2) earns_slot, then PORTFOLIO VALUE # (blend w25 uplift_hold + min-asset hold-out) which the wave objectives (2)&(3) reward, # with DD as a final tiebreak. NOTE: no window combo reaches max_dd<0.30 (DD wall is # structural: BTC & ETH DD move in OPPOSITE directions vs the vola window) so we report # the strongest earns_slot config rather than chasing an unreachable DD gate. w25 = r["w25"] if r["w25"] is not None else -9 return (1 if r["bw"] else 0, 1 if r["es"] else 0, round(w25, 3), r["rep"]["verdict"]["min_asset_holdout_sharpe"], -r["ev"]["max_dd"]) if results: best = sorted(results, key=rank, reverse=True)[0] b = best rep = b["rep"]; marg = b["marg"]; ev = b["ev"] v = rep["verdict"] print("\n" + "=" * 78) print("FINAL BEST CONFIG (CHANDE_WIN family)") print("=" * 78) print(f" config = {b['cfg']} (+ fixed winner spine {FIXED})") print(f" name = {b['name']}") print(f" minFull = {v['min_asset_full_sharpe']:+.3f}") print(f" minHold = {v['min_asset_holdout_sharpe']:+.3f} " f"(BTC {rep['per_asset']['BTC']['holdout']['sharpe']:+.2f} / " f"ETH {rep['per_asset']['ETH']['holdout']['sharpe']:+.2f})") print(f" standalone max_dd = {ev['max_dd']:.4f} " f"(BTC {ev['btc_dd']:.4f} / ETH {ev['eth_dd']:.4f})") print(f" n_trades_min = {v['min_trades']}") print(f" fee_survives 0.30%= {v['fee_survives']}") print(f" causality_ok = {b['caus_ok']}") print(f" grade = {v['grade']}") print(f" --- marginal vs TP01 ---") print(f" corr_full = {marg.get('corr_full')}") print(f" marginal_verdict = {marg.get('marginal_verdict')}") print(f" has_insample_edge = {marg.get('has_insample_edge')}") print(f" is_hedge = {marg.get('is_hedge')}") print(f" robust_oos = {marg.get('robust_oos')}") print(f" multicut_persist = {marg.get('multicut_persistent')}") print(f" clean_year_uplift = {marg.get('clean_year_uplift')}") print(f" blend w25 uplift_hold = {b['w25']}") print(f" blend w50 = {b['w50']}") print(f" earns_slot = {b['es']}") print(f" BEATS_WINNER = {b['bw']}") print("=" * 78) # machine-readable line for the harness operator import json out = dict( family="CHANDE_WIN", best_config=b["cfg"], fixed=FIXED, name=b["name"], grade=v["grade"], min_full=v["min_asset_full_sharpe"], min_hold=v["min_asset_holdout_sharpe"], max_dd=ev["max_dd"], btc_dd=ev["btc_dd"], eth_dd=ev["eth_dd"], n_trades_min=v["min_trades"], fee_survives=v["fee_survives"], causality_ok=b["caus_ok"], corr_full=marg.get("corr_full"), marginal_verdict=marg.get("marginal_verdict"), has_insample_edge=marg.get("has_insample_edge"), is_hedge=marg.get("is_hedge"), robust_oos=marg.get("robust_oos"), multicut_persistent=marg.get("multicut_persistent"), clean_year_uplift=marg.get("clean_year_uplift"), blend_w25_uplift_hold=b["w25"], earns_slot=b["es"], beats_winner=b["bw"], ) print("RESULT_JSON " + json.dumps(out, default=str))