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>
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""Full-window audit: every registered US strategy vs TrendRider V9.
|
|
|
|
Runs the official engine path (run_market, same params as precompute) over
|
|
the current 10y common window and writes the comparison table plus the
|
|
head-to-head verdict (does V9 beat every other strategy on BOTH CAGR and
|
|
Sharpe?) to data/research/results/tr9_full_comparison.json.
|
|
|
|
PYTHONPATH=. uv run python -m research.tr9_audit
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
import pandas as pd
|
|
|
|
from trader import (AGENT_REVIEW_STRATEGIES, STRATEGY_REGISTRY,
|
|
strategy_supports_market)
|
|
|
|
import research.tr9_lab as lab
|
|
|
|
OUT = os.path.join("data", "research", "results", "tr9_full_comparison.json")
|
|
V9_NAMES = ["trend_rider_v9", "trend_rider_v9_vt24", "trend_rider_v9_vt32", "trend_rider_v9_vt36",
|
|
"trend_rider_v10", "trend_rider_v10_vt32", "trend_rider_v10_vt36"]
|
|
NEW_NAMES = [n for n in V9_NAMES if "v10" in n]
|
|
|
|
|
|
def main() -> None:
|
|
us_names = [n for n in STRATEGY_REGISTRY
|
|
if strategy_supports_market(n, "us") and n not in AGENT_REVIEW_STRATEGIES
|
|
and not n.startswith("sim_")]
|
|
end = "2026-10-07"
|
|
print(f"auditing {len(us_names)} US strategies (window ...{end})")
|
|
curves = lab.run(us_names, start="2017-12-26", end=end)
|
|
rows = []
|
|
for name, eq in curves.items():
|
|
m = lab.metrics(eq)
|
|
rows.append({"name": name, **m})
|
|
df = pd.DataFrame(rows).sort_values("cagr", ascending=False)
|
|
pd.set_option("display.width", 200)
|
|
print(f"{'name':44s} {'CAGR':>8s} {'vol':>7s} {'Sharpe':>7s} {'MDD':>7s}")
|
|
for _, r in df.iterrows():
|
|
print(f"{r['name']:44s} {r['cagr']*100:7.1f}% {r['vol']*100:6.1f}% "
|
|
f"{r['sharpe']:7.2f} {r['mdd']*100:6.1f}%")
|
|
|
|
others = df[~df["name"].isin(NEW_NAMES)]
|
|
best_cagr = others.loc[others["cagr"].idxmax()]
|
|
best_sharpe = others.loc[others["sharpe"].idxmax()]
|
|
print(f"\nexisting best CAGR : {best_cagr['name']} {best_cagr['cagr']*100:.1f}%")
|
|
print(f"existing best Sharpe : {best_sharpe['name']} {best_sharpe['sharpe']:.2f}")
|
|
verdict = {}
|
|
for v9 in NEW_NAMES:
|
|
row = df[df["name"] == v9].iloc[0]
|
|
beat_cagr = bool((others["cagr"] < row["cagr"]).all())
|
|
beat_sharpe = bool((others["sharpe"] < row["sharpe"]).all())
|
|
verdict[v9] = {"cagr": float(row["cagr"]), "sharpe": float(row["sharpe"]),
|
|
"mdd": float(row["mdd"]),
|
|
"beats_all_cagr": beat_cagr, "beats_all_sharpe": beat_sharpe,
|
|
"beats_all_both": beat_cagr and beat_sharpe}
|
|
print(f"{v9}: CAGR {row['cagr']*100:.1f}% (beats all: {beat_cagr}), "
|
|
f"Sharpe {row['sharpe']:.2f} (beats all: {beat_sharpe})")
|
|
with open(OUT, "w") as fh:
|
|
json.dump({"window": {"start": "2017-12-26", "end": end},
|
|
"rows": rows, "verdict": verdict}, fh, indent=1)
|
|
print("wrote", OUT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|