Files
quant-trading/research/lab/us_target50.py
Gahow Wang ad569f1cea feat: us_momentum_qqq_trend; alpha-vs-QQQ research across new data sources
Register us_momentum_qqq_trend: half overnight_recovery_top10, half a QQQ
trend sleeve (strategies/qqq_trend.py: 200-day switch times a 30% vol target,
held as TQQQ + SHY). Mixed stock+ETF, US only (extra tickers QQQ, SHY, TQQQ).

Research (research/lab/): alpha against QQQ with a 2006-16 design / 2017-26
holdout split (alpha.py, qqq_scan.py, beat_qqq.py); leverage and drawdown
frontier (levered_book.py, us_target50.py); synthetic 3x ETFs from 1990 and
TrendRider/QQQ-trend stress tests (fetch_long_history.py, letf_history.py,
vol_trend.py); short-horizon sleeves, diversifiers, industry momentum,
earnings-announcement returns; new point-in-time data sources: SEC Form 4
insider trades (fetch_insider.py, insider.py), 10-K/10-Q text similarity,
8-K earnings dates and SIC codes (fetch_lazy_prices.py, lazy_prices.py), and
an S&P 400/600 universe from iShares holdings (fetch_us_mid.py, panel_mid.py).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 13:40:15 +08:00

100 lines
4.2 KiB
Python

"""
What would it take to earn 50% a year, and at what drawdown? The US book
over 2006-2026, including 2008, with the trend sleeve rebuilt from synthetic
3x ETFs (research/lab/letf_history.py), then levered.
PYTHONPATH=. uv run python -m research.lab.us_final # sleeves (once)
PYTHONPATH=. uv run python -m research.lab.letf_history # TrendRider on synthetic ETFs
PYTHONPATH=. uv run python -m research.lab.vol_trend # simple vol-targeted trend
PYTHONPATH=. uv run python -m research.lab.us_target50
Books (sleeves at equal capital, reset monthly):
current momentum + lowrisk + TrendRider V7 vt24 (the live us_three_sleeve)
simple momentum + lowrisk + QQQ trend (ma200, vol target 20%, cap 3x; a
mid-grid configuration, not the best one)
Then the leverage each book needs for 50% (static, and vol-managed as in
levered_book.py) and what that leverage did in 2008 and in the worst years.
"""
from __future__ import annotations
import json
import os
import pickle
import warnings
import numpy as np
import pandas as pd
from research.lab.factor_scan import RESULTS
from research.lab.levered_book import fmt, lever, managed, stats, tbill
from research.lab.us_final import monthly_blend
warnings.filterwarnings("ignore")
LONG = ("2006-09-27", "2026-09-24")
BLOCKS = [("2006-09-27", "2010-12-31"), ("2011-01-01", "2015-12-31"), ("2016-01-01", "2020-12-31"),
("2021-01-01", "2026-09-24")]
def sleeves() -> pd.DataFrame:
R = pd.read_pickle(os.path.join(RESULTS, "us_final_sleeves.pkl"))[["momentum", "lowrisk", "spy"]]
with open(os.path.join("data", "research", "letf_history.pkl"), "rb") as fh:
tr = pickle.load(fh)["equity"]["trend_rider_v7_vt24 [lag]"]
with open(os.path.join(RESULTS, "vol_trend.pkl"), "rb") as fh:
vt = pickle.load(fh)["returns"]["QQQ ma200 vt20 cap3"]
R["trend_rider"] = tr.pct_change().reindex(R.index)
R["qqq_trend"] = vt.reindex(R.index)
return R.loc[LONG[0]:LONG[1]]
def blocks(r: pd.Series, rf: pd.Series) -> str:
return " ".join(f"{stats(r.loc[a:b], rf)['cagr']*100:5.1f}%/{stats(r.loc[a:b], rf)['sharpe']:4.2f}"
for a, b in BLOCKS)
def main() -> None:
R = sleeves()
rf = tbill(R.index)
out = {}
print("=== sleeves 2006-2026 (trend sleeves on synthetic 3x ETFs, lag fills)")
print(f"{'':46s}{'':104s} blocks 06-10 / 11-15 / 16-20 / 21-26")
for c in R.columns:
s = stats(R[c].dropna(), rf)
print(fmt(c, s), "|", blocks(R[c].fillna(0.0), rf))
books = {"current": {"momentum": 1 / 3, "lowrisk": 1 / 3, "trend_rider": 1 / 3},
"simple": {"momentum": 1 / 3, "lowrisk": 1 / 3, "qqq_trend": 1 / 3}}
m = (1 + R.fillna(0.0)).resample("ME").prod() - 1
print("\nmonthly correlation:\n", m.corr().round(2).to_string())
print("\n=== books 2006-2026 and the leverage for 50%")
for name, w in books.items():
b = monthly_blend(R.fillna(0.0), w)
s = stats(b, rf)
out[name] = {"1x": s}
print(fmt(f"{name} 1x", s), "|", blocks(b, rf))
for L in (1.5, 2.0, 3.0):
e = pd.Series(L, index=b.index)
x = lever(b, rf, e)
out[name][f"{L}x"] = stats(x, rf)
print(fmt(f" {name} static {L}x", out[name][f"{L}x"], e), "|", blocks(x, rf))
for target in (0.25, 0.35, 0.45):
x, e = managed(b, rf, target, 3.0, "ewma20")
out[name][f"vt{int(target*100)}"] = stats(x, rf)
print(fmt(f" {name} vt{int(target*100)} ewma20 cap 3x", out[name][f"vt{int(target*100)}"], e),
"|", blocks(x, rf))
print("\n=== yearly returns: current book 1x / 2x static / vt35, SPY")
b = monthly_blend(R.fillna(0.0), books["current"])
x2 = lever(b, rf, pd.Series(2.0, index=b.index))
x35, _ = managed(b, rf, 0.35, 3.0, "ewma20")
yr = pd.DataFrame({k: (1 + v).groupby(v.index.year).prod() - 1
for k, v in {"1x": b, "2x": x2, "vt35": x35, "spy": R["spy"].fillna(0.0)}.items()})
print((yr * 100).round(1).to_string())
for k in out:
for kk in out[k]:
out[k][kk] = {a: float(v) for a, v in out[k][kk].items()}
with open(os.path.join(RESULTS, "us_target50.json"), "w") as fh:
json.dump(out, fh, indent=1)
if __name__ == "__main__":
main()