Files
quant-trading/research/tr9_run.py
Gahow Wang 36cb125719 live: commit the TrendRider V9/V10 registrations the monitor already runs
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>
2026-10-09 22:20:28 -07:00

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()