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>
This commit is contained in:
@@ -10,8 +10,9 @@ import pandas as pd
|
||||
import trader
|
||||
from strategies.permanent import ETF_UNIVERSE
|
||||
from strategies.robust_momentum import RobustMomentumStrategy
|
||||
from strategies.qqq_trend import QQQVolTrend
|
||||
from strategies.sleeve_portfolio import (SleevePortfolioStrategy, decisions, drifting_shares,
|
||||
us_three_sleeve)
|
||||
us_momentum_qqq_trend, us_three_sleeve)
|
||||
|
||||
|
||||
def _bars(n_days: int = 520, n_stocks: int = 30, seed: int = 1) -> dict[str, pd.DataFrame]:
|
||||
@@ -89,5 +90,51 @@ class SleevePortfolioTests(unittest.TestCase):
|
||||
self.assertEqual(trader.strategy_fields("us_three_sleeve"), ("close", "open"))
|
||||
|
||||
|
||||
def _qqq_bars(n_days: int = 520, drift: float = 0.0005, seed: int = 2) -> dict[str, pd.DataFrame]:
|
||||
"""Stocks plus the combo's ETFs only (its live universe: S&P 500 + QQQ, TQQQ, SHY)."""
|
||||
other = [c for c in ETF_UNIVERSE if c not in ("TQQQ", "SHY")]
|
||||
bars = {f: b.drop(columns=other) for f, b in _bars(n_days, seed=seed).items()}
|
||||
rng = np.random.default_rng(seed + 1)
|
||||
idx = bars["close"].index
|
||||
q = pd.Series(100 * np.exp(np.cumsum(rng.normal(drift, 0.012, n_days))), index=idx)
|
||||
for f in bars:
|
||||
bars[f]["QQQ"] = q if f == "close" else q.shift(1).bfill()
|
||||
return bars
|
||||
|
||||
|
||||
class QQQTrendTests(unittest.TestCase):
|
||||
def test_weights_on_a_day_ignore_that_day_and_later_closes(self):
|
||||
close = _qqq_bars()["close"]
|
||||
base = QQQVolTrend().generate_signals(close)
|
||||
shocked = close.copy()
|
||||
cut = close.index[400]
|
||||
shocked.loc[cut:, "QQQ"] *= 0.5
|
||||
pd.testing.assert_frame_equal(base.loc[:cut], QQQVolTrend().generate_signals(shocked).loc[:cut])
|
||||
|
||||
def test_out_below_the_average_and_capped_above_it(self):
|
||||
up = _qqq_bars(drift=0.002)["close"]
|
||||
down = _qqq_bars(drift=-0.002)["close"]
|
||||
w_up = QQQVolTrend(cap=1.5).generate_signals(up).iloc[260:]
|
||||
w_down = QQQVolTrend().generate_signals(down).iloc[260:]
|
||||
self.assertLessEqual(w_up["TQQQ"].max(), 0.5 + 1e-12)
|
||||
self.assertGreater(w_up["TQQQ"].mean(), 0.3)
|
||||
np.testing.assert_allclose(w_up[["TQQQ", "SHY"]].sum(axis=1), 1.0)
|
||||
self.assertLess(w_down["TQQQ"].mean(), 0.05)
|
||||
|
||||
def test_momentum_qqq_trend_is_a_us_mixed_strategy(self):
|
||||
self.assertIsInstance(trader.STRATEGY_REGISTRY["us_momentum_qqq_trend"](), SleevePortfolioStrategy)
|
||||
self.assertEqual(set(trader.strategy_extra_tickers("us_momentum_qqq_trend")), {"QQQ", "SHY", "TQQQ"})
|
||||
self.assertFalse(trader.strategy_supports_market("us_momentum_qqq_trend", "cn"))
|
||||
bars = _qqq_bars()
|
||||
strat = us_momentum_qqq_trend()
|
||||
w = strat.generate_signals(bars)
|
||||
self.assertLessEqual(w.sum(axis=1).max(), 1.0 + 1e-9)
|
||||
stocks = [c for c in w.columns if c[1:].isdigit()]
|
||||
self.assertLessEqual(int((w[stocks].iloc[-1] > 0).sum()), 10)
|
||||
self.assertGreater(w[["TQQQ", "SHY"]].iloc[-1].sum(), 0.3)
|
||||
days = bars["close"].index[-2:]
|
||||
pd.testing.assert_frame_equal(trader.bar_session_weights(strat, bars, days), w.loc[days])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user