""" TrendRider equity curves from 2010 (the live ETF panel starts in 2016), for the US portfolio research (research/lab/us_final.py). PYTHONPATH=. uv run python -m research.lab.trend_rider_long Downloads the TrendRider ETF basket from 2008 ticker by ticker (batched Yahoo requests fail intermittently) and runs each variant through the live book simulation ($2 per order on $10k, 5 bp, 10% resize band). Writes data/research/tr_long.pkl: {"equity": {name: Series}, "close": DataFrame}. A ticker that still fails is left out; the strategy then picks among the rest of its basket (in the 2026-09-29 run DBC was missing, so the risk-off basket was GLD alone). """ import os import pickle import time import warnings import pandas as pd import yfinance as yf from main import simulate_live_book, strategy_positions from research.lab.fetch_us import _proxy_env from strategies.permanent import ETF_UNIVERSE from trader import RESIZE_BAND, STRATEGY_REGISTRY warnings.filterwarnings("ignore") OUT = os.path.join("data", "research", "tr_long.pkl") VARIANTS = ["trend_rider_v7_vt24", "trend_rider_v7_vt36", "trend_rider_v5_vt30"] BLOCKS = [("2010-03-01", "2013-02-28"), ("2013-03-01", "2016-09-26"), ("2016-09-27", "2021-09-26"), ("2021-09-27", "2026-09-25"), ("2010-03-01", "2026-09-25")] def fetch(tickers: list[str], start: str = "2008-01-01") -> tuple[pd.DataFrame, pd.DataFrame]: close, opens = {}, {} for t in tickers: for attempt in range(5): try: h = yf.Ticker(t).history(start=start, auto_adjust=True) if len(h): close[t], opens[t] = h["Close"], h["Open"] break except Exception as exc: # noqa: BLE001 — retry transient Yahoo errors print(f" {t}: {exc}") time.sleep(3 * (attempt + 1)) c, o = pd.DataFrame(close), pd.DataFrame(opens) c.index = c.index.tz_localize(None).normalize() o.index = c.index return c, o def main() -> None: _proxy_env() tickers = sorted(set(ETF_UNIVERSE) | {"SPY", "SHY"}) close, opens = fetch(tickers) print("missing:", sorted(set(tickers) - set(close.columns))) out = {} for name in VARIANTS: held = strategy_positions(STRATEGY_REGISTRY[name](), close, open_data=opens) eq, _ = simulate_live_book(held, close, 10_000, 0.0005, 2.0, 0, 0, 50.0, RESIZE_BAND) out[name] = eq r = eq.pct_change().dropna() for a, b in BLOCKS: x = r.loc[a:b] e = (1 + x).cumprod() print(f"{name} {a[:4]}-{b[:4]} cagr {(e.iloc[-1] ** (252 / len(x)) - 1) * 100:5.1f}% " f"sh {x.mean() / x.std() * 252 ** 0.5:.2f} mdd {((e / e.cummax()) - 1).min() * 100:5.1f}%") with open(OUT, "wb") as fh: pickle.dump({"equity": out, "close": close}, fh) if __name__ == "__main__": main()