trader.py registers trend_rider_v9 (+ vt24/vt32/vt36) and trend_rider_v10 (+ vt32/vt36) from strategies/trend_rider_v9.py, and bridge.py labels them. These were left uncommitted by the 2026-10-07 research session while the monitor ran them from the working tree (state files data/trader_us_trend_rider_v9*/v10*). research/tr9_*.py is the research trail CLAUDE.md cites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
269 lines
15 KiB
Python
269 lines
15 KiB
Python
"""Run TrendRider V9 suites through the official engine path.
|
|
|
|
PYTHONPATH=. uv run python -m research.tr9_run baseline
|
|
PYTHONPATH=. uv run python -m research.tr9_run ablate
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import research.tr9_lab as lab
|
|
|
|
M = lab.TrendRiderV9
|
|
|
|
BASELINES = ["trend_rider_v7", "trend_rider_v7_vt24", "trend_rider_v7_vt32", "trend_rider_v7_vt36",
|
|
"trend_rider_v5_vt30", "trend_rider_v3_vt32"]
|
|
|
|
|
|
def register_all() -> list[str]:
|
|
reg = lab.register
|
|
names = []
|
|
# exact V7 replica in the V9 code path (sanity)
|
|
names.append(reg("v9_base", lambda **kw: M()))
|
|
# risk-on basket extensions
|
|
names.append(reg("v9_soxl", lambda **kw: M(risk_on=("TQQQ", "UPRO", "TECL", "SOXL"))))
|
|
names.append(reg("v9_soxl_tna", lambda **kw: M(risk_on=("TQQQ", "UPRO", "TECL", "SOXL", "TNA"))))
|
|
# risk-off extensions
|
|
names.append(reg("v9_tmf", lambda **kw: M(tmf="TMF")))
|
|
names.append(reg("v9_sqqq", lambda **kw: M(sqqq="SQQQ")))
|
|
names.append(reg("v9_tmf_sqqq", lambda **kw: M(tmf="TMF", sqqq="SQQQ")))
|
|
names.append(reg("v9_soxl_tmf", lambda **kw: M(risk_on=("TQQQ", "UPRO", "TECL", "SOXL"), tmf="TMF")))
|
|
names.append(reg("v9_soxl_tmf_sqqq", lambda **kw: M(
|
|
risk_on=("TQQQ", "UPRO", "TECL", "SOXL"), tmf="TMF", sqqq="SQQQ")))
|
|
# risk-off gating variants (fixed: TLT/QQQ gate refs now included)
|
|
names.append(reg("v9_sqqq05", lambda **kw: M(sqqq="SQQQ", sqqq_trigger=0.05)))
|
|
names.append(reg("v9_sqqq10", lambda **kw: M(sqqq="SQQQ", sqqq_trigger=0.10)))
|
|
names.append(reg("v9_tmf_sqqq05", lambda **kw: M(tmf="TMF", sqqq="SQQQ", sqqq_trigger=0.05)))
|
|
names.append(reg("v9_sqqq_rostop", lambda **kw: M(sqqq="SQQQ", ro_stop_loss=0.15)))
|
|
names.append(reg("v9_tmf_sqqq_rostop", lambda **kw: M(
|
|
tmf="TMF", sqqq="SQQQ", ro_stop_loss=0.15)))
|
|
# profit-take / park variants
|
|
names.append(reg("v9_park_qqq", lambda **kw: M(pt_park_mix={"SHY": 0.5, "QQQ": 0.5})))
|
|
names.append(reg("v9_ladder", lambda **kw: M(
|
|
pt_mode="ladder", pt_ladder=((0.30, 0.5), (0.60, 1.0)))))
|
|
names.append(reg("v9_no_pt", lambda **kw: M(pt_mode="none")))
|
|
# vol target variants
|
|
names.append(reg("v9_assetvt", lambda **kw: M(vt_mode="asset")))
|
|
names.append(reg("v9_ewma", lambda **kw: M(vt_ewma=20)))
|
|
# pick metric
|
|
names.append(reg("v9_momdiv", lambda **kw: M(mom_pick_divisor=True)))
|
|
# trailing-stop on the leveraged risk-on leg
|
|
names.append(reg("v9_trail18", lambda **kw: M(trail_pct=0.18)))
|
|
names.append(reg("v9_trail22", lambda **kw: M(trail_pct=0.22)))
|
|
names.append(reg("v9_trail26", lambda **kw: M(trail_pct=0.26)))
|
|
return names
|
|
|
|
|
|
def main() -> None:
|
|
suite = sys.argv[1] if len(sys.argv) > 1 else "baseline"
|
|
if suite == "baseline":
|
|
curves = lab.run(BASELINES)
|
|
lab.report(curves, "baseline replication")
|
|
elif suite == "ablate":
|
|
names = register_all()
|
|
curves = lab.run(BASELINES + names)
|
|
lab.report(curves, "ablation")
|
|
elif suite == "riskoff":
|
|
names = register_all()
|
|
pick = ["v9_base", "v9_tmf", "v9_sqqq", "v9_sqqq05", "v9_sqqq10",
|
|
"v9_tmf_sqqq", "v9_tmf_sqqq05", "v9_sqqq_rostop", "v9_tmf_sqqq_rostop"]
|
|
curves = lab.run(BASELINES[:4] + pick)
|
|
lab.report(curves, "risk-off extensions")
|
|
lab.yearly_table(curves, ["trend_rider_v7", "v9_tmf", "v9_sqqq", "v9_tmf_sqqq05"])
|
|
elif suite == "trail":
|
|
names = register_all()
|
|
|
|
def vt(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, **kw)
|
|
|
|
extra = [
|
|
lab.register("v9vt36", lambda **kw: vt(0.36, 0.75)),
|
|
lab.register("v9vt36_t18", lambda **kw: vt(0.36, 0.75, trail_pct=0.18)),
|
|
lab.register("v9vt36_t22", lambda **kw: vt(0.36, 0.75, trail_pct=0.22)),
|
|
lab.register("v9vt36_t26", lambda **kw: vt(0.36, 0.75, trail_pct=0.26)),
|
|
lab.register("v9vt32_t22", lambda **kw: vt(0.32, 0.7, trail_pct=0.22)),
|
|
lab.register("v9vt36_b15", lambda **kw: vt(0.36, 0.75, pt_band=0.15)),
|
|
lab.register("v9vt36_thr25", lambda **kw: vt(0.36, 0.75, pt_threshold=0.25)),
|
|
lab.register("v9vt36_thr35", lambda **kw: vt(0.36, 0.75, pt_threshold=0.35)),
|
|
]
|
|
curves = lab.run(BASELINES[:4] + ["v9_trail22"] + extra)
|
|
lab.report(curves, "trail / PT tuning")
|
|
lab.yearly_table(curves, ["trend_rider_v7_vt36", "v9vt36", "v9vt36_t22", "v9vt36_t26"])
|
|
elif suite == "grid":
|
|
def vt(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, **kw)
|
|
|
|
extra = []
|
|
for t in (0.20, 0.21, 0.22, 0.23, 0.24):
|
|
extra.append(lab.register(f"g36_t{int(t*100)}", lambda t=t, **kw: vt(0.36, 0.75, trail_pct=t)))
|
|
for t in (0.20, 0.22, 0.24):
|
|
extra.append(lab.register(f"g32_t{int(t*100)}", lambda t=t, **kw: vt(0.32, 0.7, trail_pct=t)))
|
|
extra.append(lab.register("g36_t22_w20", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_window=20)))
|
|
extra.append(lab.register("g36_t22_w40", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_window=40)))
|
|
extra.append(lab.register("g36_t22_r5", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_reentry_days=5)))
|
|
extra.append(lab.register("g36_t22_r10", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_reentry_days=10)))
|
|
extra.append(lab.register("g34_t22", lambda **kw: vt(0.34, 0.72, trail_pct=0.22)))
|
|
curves = lab.run(BASELINES[:4] + extra)
|
|
lab.report(curves, "trail grid")
|
|
lab.yearly_table(curves, ["trend_rider_v7_vt36", "g36_t20", "g36_t22", "g36_t24", "g32_t22"])
|
|
elif suite == "frontier":
|
|
def vt(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, **kw)
|
|
|
|
extra = []
|
|
for tv, ml in ((0.30, 0.65), (0.32, 0.70), (0.34, 0.72), (0.35, 0.73), (0.36, 0.75), (0.38, 0.78)):
|
|
extra.append(lab.register(f"f{int(tv*100)}_t20", lambda tv=tv, ml=ml, **kw: vt(tv, ml, trail_pct=0.20)))
|
|
extra.append(lab.register("f36_t18", lambda **kw: vt(0.36, 0.75, trail_pct=0.18)))
|
|
extra.append(lab.register("f36_t19", lambda **kw: vt(0.36, 0.75, trail_pct=0.19)))
|
|
extra.append(lab.register("f36_t22_w25", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_window=25)))
|
|
extra.append(lab.register("f36_t22_w35", lambda **kw: vt(0.36, 0.75, trail_pct=0.22, trail_window=35)))
|
|
extra.append(lab.register("f32_t18", lambda **kw: vt(0.32, 0.70, trail_pct=0.18)))
|
|
curves = lab.run(BASELINES[:4] + extra)
|
|
lab.report(curves, "frontier map")
|
|
elif suite == "macro":
|
|
MACRO_UNIVERSE = lab.V9_UNIVERSE + ["^VIX9D", "^VIX3M", "^VIX", "HYG"]
|
|
|
|
def mv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, **kw)
|
|
|
|
def reg_macro(name, **kw):
|
|
return lab.register(name, lambda **kw2: mv(**kw, **kw2), universe=MACRO_UNIVERSE)
|
|
|
|
extra = [
|
|
reg_macro("mc32_m1", tv=0.32, ml=0.70, throttle_vix_inv=True),
|
|
reg_macro("mc32_m2", tv=0.32, ml=0.70, throttle_vix_spike=True),
|
|
reg_macro("mc32_m3", tv=0.32, ml=0.70, throttle_credit=True),
|
|
reg_macro("mc32_m13", tv=0.32, ml=0.70, throttle_vix_inv=True, throttle_credit=True),
|
|
reg_macro("mc32_m123", tv=0.32, ml=0.70, throttle_vix_inv=True, throttle_vix_spike=True,
|
|
throttle_credit=True),
|
|
reg_macro("mc36_m1", tv=0.36, ml=0.75, throttle_vix_inv=True),
|
|
reg_macro("mc36_m3", tv=0.36, ml=0.75, throttle_credit=True),
|
|
reg_macro("mc36_m123", tv=0.36, ml=0.75, throttle_vix_inv=True, throttle_vix_spike=True,
|
|
throttle_credit=True),
|
|
reg_macro("mc34_m3", tv=0.34, ml=0.72, throttle_credit=True),
|
|
reg_macro("mc34_m13", tv=0.34, ml=0.72, throttle_vix_inv=True, throttle_credit=True),
|
|
]
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36"] + extra)
|
|
lab.report(curves, "macro throttle")
|
|
lab.yearly_table(curves, ["trend_rider_v9_vt32", "mc32_m3", "mc32_m13", "mc32_m123"])
|
|
elif suite == "knobs":
|
|
KNOB_UNIVERSE = lab.V9_UNIVERSE + ["RSP"]
|
|
|
|
def kv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, **kw)
|
|
|
|
def reg_knob(name, **kw):
|
|
return lab.register(name, lambda **kw2: kv(**kw, **kw2), universe=KNOB_UNIVERSE)
|
|
|
|
extra = [
|
|
reg_knob("k32_base", tv=0.32, ml=0.70),
|
|
reg_knob("k32_instma50", tv=0.32, ml=0.70, instma_exit=50),
|
|
reg_knob("k32_instma30", tv=0.32, ml=0.70, instma_exit=30),
|
|
reg_knob("k32_fast", tv=0.32, ml=0.70, fast_reentry_days=60),
|
|
reg_knob("k32_instma50_fast", tv=0.32, ml=0.70, instma_exit=50, fast_reentry_days=60),
|
|
reg_knob("k32_breadth100", tv=0.32, ml=0.70, breadth_ma=100),
|
|
reg_knob("k32_dd4", tv=0.32, ml=0.70, dd_stop=0.04),
|
|
reg_knob("k32_ddw30", tv=0.32, ml=0.70, dd_window=30),
|
|
reg_knob("k32_conf2", tv=0.32, ml=0.70, confirm_days=2),
|
|
reg_knob("k32_mom84", tv=0.32, ml=0.70, mom_lookback=84),
|
|
]
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36"] + extra)
|
|
lab.report(curves, "structural knobs")
|
|
import numpy as np
|
|
a = curves.get("trend_rider_v9_vt32")
|
|
b = curves.get("k32_base")
|
|
if a is not None and b is not None:
|
|
print("vt32 fidelity:", np.allclose(a.to_numpy(), b.to_numpy()), "maxdiff",
|
|
float(np.abs(a.to_numpy() - b.to_numpy()).max()))
|
|
elif suite == "pt2":
|
|
def pv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, **kw)
|
|
|
|
extra = [
|
|
lab.register("p32_base", lambda **kw: pv(0.32, 0.70)),
|
|
lab.register("p32_band06", lambda **kw: pv(0.32, 0.70, pt_band=0.06)),
|
|
lab.register("p32_band08", lambda **kw: pv(0.32, 0.70, pt_band=0.08)),
|
|
lab.register("p32_hold10", lambda **kw: pv(0.32, 0.70, pt_min_hold=10)),
|
|
lab.register("p32_hold21", lambda **kw: pv(0.32, 0.70, pt_min_hold=21)),
|
|
lab.register("p32_ononly", lambda **kw: pv(0.32, 0.70, pt_only=("TQQQ", "UPRO"))),
|
|
lab.register("p36_band06", lambda **kw: pv(0.36, 0.75, pt_band=0.06)),
|
|
lab.register("p36_hold10", lambda **kw: pv(0.36, 0.75, pt_min_hold=10)),
|
|
lab.register("p36_ononly", lambda **kw: pv(0.36, 0.75, pt_only=("TQQQ", "UPRO"))),
|
|
lab.register("p34_hold10", lambda **kw: pv(0.34, 0.72, pt_min_hold=10)),
|
|
]
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36"] + extra)
|
|
lab.report(curves, "PT variants 2")
|
|
lab.yearly_table(curves, ["trend_rider_v9_vt32", "p32_band06", "p32_hold10", "p32_ononly"])
|
|
elif suite == "pt3":
|
|
def qv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, **kw)
|
|
|
|
extra = [
|
|
lab.register("q33_h10", lambda **kw: qv(0.33, 0.71, pt_min_hold=10)),
|
|
lab.register("q34_h10", lambda **kw: qv(0.34, 0.72, pt_min_hold=10)),
|
|
lab.register("q34_b08_h10", lambda **kw: qv(0.34, 0.72, pt_min_hold=10, pt_band=0.08)),
|
|
lab.register("q34_b06_h10", lambda **kw: qv(0.34, 0.72, pt_min_hold=10, pt_band=0.06)),
|
|
lab.register("q32_b06_h10", lambda **kw: qv(0.32, 0.70, pt_min_hold=10, pt_band=0.06)),
|
|
lab.register("q36_b06_h10", lambda **kw: qv(0.36, 0.75, pt_min_hold=10, pt_band=0.06)),
|
|
lab.register("q32_b08_h10", lambda **kw: qv(0.32, 0.70, pt_min_hold=10, pt_band=0.08)),
|
|
lab.register("q34_h10_thr28", lambda **kw: qv(0.34, 0.72, pt_min_hold=10, pt_threshold=0.28)),
|
|
]
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36",
|
|
"trend_rider_v9_vt24"] + extra)
|
|
lab.report(curves, "PT hold + band grid")
|
|
lab.yearly_table(curves, ["trend_rider_v9_vt32", "q34_h10", "q34_b06_h10", "q32_b06_h10"])
|
|
elif suite == "pt4":
|
|
def rv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, pt_min_hold=10, **kw)
|
|
|
|
extra = []
|
|
for tv, ml in ((0.33, 0.71), (0.34, 0.72)):
|
|
for thr in (0.26, 0.27, 0.28, 0.29, 0.30):
|
|
extra.append(lab.register(
|
|
f"r{int(tv*100)}_thr{int(thr*100)}",
|
|
lambda tv=tv, ml=ml, thr=thr, **kw: rv(tv, ml, pt_threshold=thr)))
|
|
extra.append(lab.register("r34_thr28_b08", lambda **kw: rv(0.34, 0.72, pt_threshold=0.28,
|
|
pt_band=0.08)))
|
|
extra.append(lab.register("r34_thr28_t22", lambda **kw: M(
|
|
target_vol=0.34, min_lev=0.72, trail_pct=0.22, pt_min_hold=10, pt_threshold=0.28)))
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36"] + extra)
|
|
lab.report(curves, "thr neighborhood")
|
|
lab.yearly_table(curves, ["trend_rider_v9_vt36", "r34_thr28", "r33_thr28", "r34_thr27"])
|
|
elif suite == "final":
|
|
def fv(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, trail_pct=0.20, pt_min_hold=10,
|
|
pt_threshold=0.28, pt_band=0.08, **kw)
|
|
|
|
extra = [
|
|
lab.register("f10_34", lambda **kw: fv(0.34, 0.72)),
|
|
lab.register("f10_32", lambda **kw: fv(0.32, 0.70)),
|
|
lab.register("f10_36", lambda **kw: fv(0.36, 0.75)),
|
|
lab.register("f10_34_t22", lambda **kw: M(target_vol=0.34, min_lev=0.72, trail_pct=0.22,
|
|
pt_min_hold=10, pt_threshold=0.28,
|
|
pt_band=0.08)),
|
|
]
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v9_vt36",
|
|
"trend_rider_v9_vt24"] + extra, end="2026-10-07")
|
|
lab.report(curves, "finalists @ official endpoint 2026-10-07")
|
|
elif suite == "v10robe":
|
|
def w(tv, ml, **kw):
|
|
return M(target_vol=tv, min_lev=ml, pt_min_hold=10, **kw)
|
|
|
|
extra = []
|
|
for t in (0.18, 0.19, 0.20, 0.21, 0.22):
|
|
extra.append(lab.register(f"w34_t{int(t*100)}", lambda t=t, **kw: w(
|
|
0.34, 0.72, trail_pct=t, pt_threshold=0.28, pt_band=0.08)))
|
|
for thr in (0.26, 0.27, 0.28, 0.29, 0.30):
|
|
for band in (0.06, 0.08, 0.10):
|
|
extra.append(lab.register(f"w34_{int(thr*100)}_{int(band*100)}", lambda thr=thr, band=band, **kw: w(
|
|
0.34, 0.72, trail_pct=0.20, pt_threshold=thr, pt_band=band)))
|
|
curves = lab.run(["trend_rider_v9", "trend_rider_v9_vt32", "trend_rider_v10"] + extra,
|
|
end="2026-10-07")
|
|
lab.report(curves, "v10 robustness grids @2026-10-07")
|
|
else:
|
|
raise SystemExit(f"unknown suite {suite}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|