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>
100 lines
4.2 KiB
Python
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()
|