From 5e0ab6eb3b604a2937bccd3233ce72da1e0c8886 Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Tue, 29 Sep 2026 14:42:49 +0800 Subject: [PATCH] chore: untrack generated data; add momentum + fundamentals experiment - Stop tracking data/sp500_history.json (rebuilt daily from Wikipedia by universe_history.load_sp500_history) and ignore data/books/ (per-machine live book state, like data/trader_*.json). - research/lab/momentum_fundamentals.py: financial-statement vetoes and confirmations on overnight_recovery_top10 (2011-16 design, 2017-26 holdout). No return gain; re-sorting the momentum top 30 by fundamental momentum lowers MaxDD at equal return. Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitignore | 2 + CLAUDE.md | 3 +- data/sp500_history.json | 1 - research/lab/momentum_fundamentals.py | 140 ++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 2 deletions(-) delete mode 100644 data/sp500_history.json create mode 100644 research/lab/momentum_fundamentals.py diff --git a/.gitignore b/.gitignore index 8127e83..2bd0b02 100644 --- a/.gitignore +++ b/.gitignore @@ -18,9 +18,11 @@ data/*.png # Universe caches — re-fetched daily data/universe_*.json +data/sp500_history.json # Trader state — per-machine, regenerated by auto/simulate data/trader_*.json +data/books/ # Factor attribution output and cached factors data/attribution_*/ diff --git a/CLAUDE.md b/CLAUDE.md index acb5ab4..9e57342 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,7 @@ PYTHONPATH=. uv run python -m research.lab.diversifiers # multi PYTHONPATH=. uv run python -m research.lab.us_target50 # the book 2006-2026 and the leverage 50%/yr needs PYTHONPATH=. uv run --with numba python -m research.lab.qqq_scan # alpha vs QQQ of every sleeve / factor / mega-cap variant PYTHONPATH=. uv run --with numba python -m research.lab.beat_qqq # sleeves picked on 2006-16, judged 2017-26; odds of beating QQQ +PYTHONPATH=. uv run --with numba python -m research.lab.momentum_fundamentals [--sensitivity] # financial-statement vetoes/confirmations on overnight_recovery_top10 uv run python -m research.lab.fetch_insider && PYTHONPATH=. uv run python -m research.lab.insider # SEC Form 4 insider factors PYTHONPATH=. uv run python -m research.lab.fetch_lazy_prices && PYTHONPATH=. uv run python -m research.lab.lazy_prices # 10-K/10-Q text similarity, 8-K earnings dates, SIC PYTHONPATH=. uv run --with numba python -m research.lab.earnings # earnings-announcement return (needs sec_8k_earnings.csv) @@ -118,7 +119,7 @@ uv run python -m unittest discover tests - Strategies output **weights**, not buy/sell signals. Weights are normalized so each row sums to 1 (or 0 during warm-up). - Warm-up periods: each strategy zeros out its first N rows where indicators are undefined. -- `data/` contains persistent price CSVs, universe JSON caches, and trader state files, plus `data/precomputed/` (precomputed backtests), `data/web/` (watchlist, per-stock cache, backtest result cache) and `data/research/` (research panels, ~2 GB). Not checked into git (only `data/.gitkeep`, `data/sp500_history.json` and `data/csi300_history.json` are tracked). +- `data/` contains persistent price CSVs, universe JSON caches, and trader state files, plus `data/precomputed/` (precomputed backtests), `data/web/` (watchlist, per-stock cache, backtest result cache) and `data/research/` (research panels, ~2 GB). Not checked into git (only `data/.gitkeep` and `data/csi300_history.json` are tracked; `data/sp500_history.json` is rebuilt from Wikipedia on first use). - Python 3.12+, managed with `uv`. - Do not show matplotlib figures when running backtests; use `--no-plot`. diff --git a/data/sp500_history.json b/data/sp500_history.json deleted file mode 100644 index ba9cdf2..0000000 --- a/data/sp500_history.json +++ /dev/null @@ -1 +0,0 @@ -{"date": "2026-09-25", "intervals": {"BKR": [["2017-07-07", null]], "PNR": [["2012-10-01", null]], "MNST": [["2012-06-29", null]], "BE": [["2026-09-21", null]], "TSN": [[null, null]], "NFLX": [["2010-12-17", null]], "WMT": [[null, null]], "PYPL": [["2015-07-20", null]], "CRWD": [["2024-06-24", null]], "RDDT": [["2026-08-18", null]], "CBRE": [[null, null]], "TER": [[null, "2013-12-23"], ["2020-09-21", null]], "KVUE": [["2023-08-25", null]], "KHC": [["2015-07-06", null]], "SCHW": [[null, null]], "FRT": [["2016-02-01", null]], "EMR": [[null, null]], "GEHC": [["2023-01-04", null]], "MCK": [[null, null]], "XOM": [[null, null]], "BRK-B": [[null, null]], "MRVL": [["2026-06-22", null]], "MDT": [[null, null]], "LEN": [[null, null]], "TJX": [[null, null]], "MGM": [["2017-07-26", null]], "AEP": [[null, null]], "JBHT": [["2015-07-01", null]], "ES": [[null, null]], "EFX": [[null, null]], "NI": [[null, null]], "CMS": [[null, null]], "SBUX": [["2000-06-07", null]], "KDP": [["2022-06-21", null]], "LVS": [["2019-10-03", null]], "OXY": [[null, null]], "EVRG": [["2018-06-05", null]], "TEL": [[null, "2009-06-25"], ["2011-10-14", null]], "FDS": [["2021-12-20", null]], "DVN": [[null, null]], "F": [[null, null]], "NRG": [[null, null]], "ADBE": [[null, null]], "GDDY": [["2024-06-24", null]], "NEM": [[null, null]], "ZTS": [["2013-06-21", null]], "LYV": [["2019-12-23", null]], "EXE": [["2025-03-24", null]], "NEE": [[null, null]], "VMC": [[null, null]], "WBD": [["2022-04-11", null]], "AEE": [[null, null]], "AVY": [[null, null]], "NUE": [[null, null]], "SLB": [[null, null]], "BXP": [[null, null]], "SWKS": [["2015-03-12", null]], "BF-B": [[null, null]], "APTV": [[null, null]], "CI": [[null, null]], "MAR": [[null, null]], "SMCI": [["2024-03-18", null]], "ELV": [[null, null]], "WDC": [[null, null]], "FOX": [["2015-09-18", "2019-03-19"], ["2019-03-19", null]], "BAX": [[null, null]], "CMI": [[null, null]], "WRB": [["2019-12-05", null]], "DRI": [[null, null]], "VTR": [["2009-03-03", null]], "KMI": [["2012-05-17", null]], "AIG": [[null, null]], "FDXF": [["2026-06-01", null]], "DASH": [["2025-03-24", null]], "CPAY": [[null, null]], "FTV": [["2016-07-05", null]], "PODD": [["2023-03-15", null]], "DOV": [[null, null]], "ALL": [[null, null]], "WY": [[null, null]], "STZ": [["2005-07-01", null]], "VLTO": [["2023-10-02", null]], "ZBH": [[null, null]], "EQIX": [["2015-03-23", null]], "FDX": [[null, null]], "AOS": [["2017-07-26", null]], "CTAS": [[null, null]], "DUK": [[null, null]], "FTNT": [["2018-10-11", null]], "NDSN": [["2022-02-15", null]], "FIX": [["2025-12-22", null]], "BMY": [[null, null]], "TFC": [[null, null]], "NDAQ": [[null, null]], "SYY": [[null, null]], "IVZ": [[null, null]], "PM": [[null, null]], "BR": [["2018-06-18", null]], "EXR": [["2016-01-19", null]], "WAT": [[null, null]], "PCAR": [[null, null]], "FFIV": [["2010-12-17", null]], "CPT": [["2022-04-04", null]], "ISRG": [[null, null]], "BAC": [[null, null]], "MU": [[null, null]], "AXON": [["2023-05-04", null]], "HWM": [["2020-04-01", null]], "BRO": [["2021-09-20", null]], "J": [[null, null]], "ICE": [["2007-09-26", null]], "NWSA": [["2013-07-01", null]], "OTIS": [["2020-04-03", null]], "LUV": [[null, null]], "HAS": [[null, null]], "LYB": [["2012-09-05", null]], "JNJ": [[null, null]], "MA": [[null, null]], "MOS": [["2011-09-23", null]], "TRGP": [["2022-10-12", null]], "UHS": [["2014-09-20", null]], "STT": [[null, null]], "TKO": [["2025-03-24", null]], "PRU": [[null, null]], "IP": [[null, null]], "XYL": [["2011-10-31", null]], "RSG": [[null, null]], "PFG": [[null, null]], "CME": [[null, null]], "KLAC": [[null, null]], "BG": [["2023-03-15", null]], "GOOG": [[null, null]], "CSGP": [["2022-09-19", null]], "CVS": [[null, null]], "IEX": [["2019-08-09", null]], "AVGO": [["2014-05-08", null]], "CCI": [["2012-03-13", null]], "HBAN": [[null, null]], "DOC": [[null, null]], "HSIC": [["2015-03-18", null]], "MS": [[null, null]], "PEP": [[null, null]], "FISV": [[null, null]], "BSX": [[null, null]], "KKR": [["2024-06-24", null]], "HOOD": [["2025-09-22", null]], "PPL": [[null, null]], "CIEN": [[null, "2009-12-18"], ["2026-02-09", null]], "NWS": [["2015-09-18", null]], "EBAY": [[null, null]], "DECK": [["2024-03-18", null]], "HSY": [[null, null]], "CHD": [["2015-12-29", null]], "APP": [["2025-09-22", null]], "MCO": [[null, null]], "TPR": [[null, null]], "CRL": [["2021-05-14", null]], "MSCI": [["2018-04-04", null]], "TGT": [[null, null]], "CARR": [["2020-04-03", null]], "CTVA": [["2019-06-03", null]], "NTAP": [[null, null]], "BLK": [["2011-04-01", null]], "LIN": [[null, null]], "SPG": [[null, null]], "GOOGL": [["2014-04-03", null]], "GM": [["2013-06-06", null]], "VST": [["2024-05-08", null]], "TYL": [["2020-06-22", null]], "EXPE": [["2007-10-02", null]], "TPL": [["2024-11-26", null]], "IDXX": [["2017-01-05", null]], "VTRS": [[null, null]], "AMD": [[null, "2013-09-20"], ["2017-03-20", null]], "MRK": [[null, null]], "TROW": [[null, null]], "UAL": [["2015-09-02", null]], "GILD": [[null, null]], "SBAC": [["2017-09-01", null]], "AMAT": [[null, null]], "MKC": [[null, null]], "MTD": [["2016-09-06", null]], "OKE": [[null, null]], "VZ": [[null, null]], "MRSH": [[null, null]], "SYK": [[null, null]], "Q": [[null, "2011-03-31"], ["2017-08-29", "2017-11-15"], ["2025-11-03", null]], "ZBRA": [["2019-12-23", null]], "DHI": [[null, null]], "AMP": [[null, null]], "KEY": [[null, null]], "CHRW": [[null, null]], "EOG": [[null, null]], "REG": [["2017-03-02", null]], "NXPI": [["2021-03-22", null]], "APH": [[null, null]], "COR": [[null, null]], "CRH": [["2025-12-22", null]], "EIX": [[null, null]], "MCD": [[null, null]], "CL": [[null, null]], "ANET": [["2018-08-28", null]], "MAA": [["2016-12-02", null]], "GIS": [[null, null]], "MSFT": [[null, null]], "DTE": [[null, null]], "AAPL": [[null, null]], "FAST": [["2008-09-12", null]], "RL": [[null, null]], "MRNA": [["2021-07-21", null]], "JCI": [[null, null]], "RVTY": [[null, null]], "GEV": [["2024-04-02", null]], "JBL": [[null, "2014-11-05"], ["2023-12-18", null]], "PLTR": [["2024-09-23", null]], "WAB": [["2019-02-27", null]], "LITE": [["2026-03-23", null]], "VLO": [[null, null]], "BA": [[null, null]], "HUM": [[null, null]], "CHTR": [["2016-09-08", null]], "META": [[null, null]], "UDR": [["2016-03-07", null]], "LNT": [["2016-07-01", null]], "ROL": [["2018-10-01", null]], "FIS": [[null, null]], "AME": [["2013-09-20", null]], "PAYX": [[null, null]], "WST": [["2020-05-22", null]], "CSX": [[null, null]], "STE": [["2019-12-23", null]], "EXC": [[null, null]], "CBOE": [["2017-03-01", null]], "ALLE": [["2013-12-02", null]], "ABBV": [["2013-01-02", null]], "PH": [[null, null]], "VRT": [["2026-03-23", null]], "COIN": [["2025-05-19", null]], "REGN": [["2013-04-30", null]], "PSKY": [[null, null]], "PLD": [[null, null]], "CNC": [["2016-03-30", null]], "GWW": [[null, null]], "SHW": [[null, null]], "CEG": [[null, "2012-03-13"], ["2022-02-02", null]], "CCL": [["1998-12-11", null]], "SWK": [[null, null]], "SNA": [[null, null]], "IR": [["2020-03-02", null]], "T": [[null, null]], "ODFL": [["2019-12-09", null]], "ROST": [["2009-12-18", null]], "SOLV": [["2024-04-01", null]], "GNRC": [["2021-03-22", null]], "PGR": [[null, null]], "STX": [["2012-07-02", null]], "FERG": [["2026-08-05", null]], "BKNG": [[null, null]], "JKHY": [["2018-11-13", null]], "EL": [[null, null]], "COO": [["2016-09-22", null]], "ATO": [["2019-02-15", null]], "CVX": [[null, null]], "PEG": [[null, null]], "VRSN": [[null, null]], "CNP": [[null, null]], "BBY": [[null, null]], "HII": [["2018-01-03", null]], "KMB": [[null, null]], "AMZN": [["2005-11-18", null]], "PANW": [["2023-06-20", null]], "COP": [[null, null]], "AZO": [[null, null]], "DXCM": [["2020-05-12", null]], "URI": [["2014-09-20", null]], "CVNA": [["2025-12-22", null]], "XEL": [[null, null]], "RCL": [["2014-12-05", null]], "AWK": [["2016-03-04", null]], "CAH": [[null, null]], "MPWR": [["2021-02-12", null]], "LOW": [[null, null]], "RTX": [[null, null]], "AMT": [[null, null]], "GPN": [["2016-04-25", null]], "ETR": [[null, null]], "WYNN": [[null, null]], "MSI": [[null, null]], "ERIE": [["2024-09-23", null]], "SPGI": [[null, null]], "HRL": [["2009-03-03", null]], "MMM": [[null, null]], "ARE": [["2017-03-20", null]], "LHX": [[null, null]], "CFG": [["2016-02-01", null]], "CAT": [[null, null]], "TECH": [["2021-08-30", null]], "ULTA": [["2016-04-18", null]], "PNW": [[null, null]], "SRE": [[null, null]], "KIM": [[null, null]], "FICO": [["2023-03-20", null]], "DVA": [[null, null]], "DG": [["2012-12-03", null]], "ABT": [[null, null]], "VRTX": [["2013-09-20", null]], "RMD": [["2017-07-26", null]], "TXN": [[null, null]], "SJM": [[null, null]], "RF": [[null, null]], "COHR": [["2026-03-23", null]], "LLY": [[null, null]], "HUBB": [["2023-10-18", null]], "GPC": [[null, null]], "LII": [["2024-12-23", null]], "DE": [[null, null]], "UNP": [[null, null]], "MO": [[null, null]], "TRMB": [["2021-01-21", null]], "PTC": [["2021-04-20", null]], "DPZ": [["2020-05-12", null]], "AES": [[null, null]], "HONA": [["2026-06-29", null]], "HAL": [[null, null]], "EQT": [[null, "2018-11-13"], ["2022-10-03", null]], "TMO": [[null, null]], "VICI": [["2022-06-08", null]], "LULU": [["2023-10-18", null]], "BNY": [[null, null]], "L": [[null, null]], "GS": [[null, null]], "TSLA": [["2020-12-21", null]], "FITB": [[null, null]], "CSCO": [[null, null]], "WDAY": [["2024-12-23", null]], "FCX": [[null, null]], "CMG": [["2011-04-27", null]], "A": [[null, null]], "TTWO": [["2018-03-19", null]], "CB": [["2010-07-14", null]], "PFE": [[null, null]], "V": [["2009-12-18", null]], "PPG": [[null, null]], "HIG": [[null, null]], "DD": [[null, "2017-09-01"], ["2019-06-03", null]], "CF": [[null, null]], "PSX": [["2012-04-23", null]], "DIS": [["1976-07-01", null]], "VEEV": [["2026-05-07", null]], "NCLH": [["2017-10-13", null]], "VMRK": [[null, null]], "MCHP": [[null, null]], "ON": [["2022-06-21", null]], "BIIB": [[null, null]], "ECL": [[null, null]], "WTW": [["2022-01-10", null]], "YUM": [[null, null]], "UBER": [["2023-12-18", null]], "CRM": [["2008-09-12", null]], "EXPD": [[null, null]], "AKAM": [[null, null]], "ACGL": [["2022-11-01", null]], "DDOG": [["2025-07-09", null]], "ADP": [[null, null]], "CDW": [["2019-09-23", null]], "SNDK": [[null, "2016-05-13"], ["2025-11-28", null]], "DGX": [[null, null]], "ARES": [["2025-12-11", null]], "BEN": [[null, null]], "DLR": [["2016-05-18", null]], "CTSH": [[null, null]], "KEYS": [["2018-11-06", null]], "ESS": [["2014-04-02", null]], "AMCR": [["2019-06-11", null]], "ABNB": [["2023-09-18", null]], "TMUS": [["2019-07-15", null]], "DAL": [["2013-09-10", null]], "AXP": [[null, null]], "APO": [["2024-12-23", null]], "CPRT": [["2018-07-02", null]], "DELL": [[null, "2013-10-29"], ["2024-09-23", null]], "ALGN": [["2017-06-19", null]], "IT": [["2017-04-05", null]], "IFF": [[null, null]], "IRM": [[null, null]], "TRV": [[null, null]], "ROP": [[null, null]], "GEN": [[null, null]], "WFC": [[null, null]], "CMCSA": [[null, null]], "WMB": [[null, null]], "HON": [[null, null]], "IQV": [["2017-11-15", null]], "INVH": [["2022-09-19", null]], "UPS": [[null, null]], "STLD": [["2022-12-22", null]], "MLM": [["2014-07-02", null]], "PCG": [[null, "2019-01-18"], ["2022-10-03", null]], "TDY": [["2020-06-22", null]], "UNH": [[null, null]], "TT": [[null, null]], "NOW": [["2019-11-21", null]], "AJG": [["2016-05-31", null]], "TSCO": [["2014-01-24", null]], "APA": [[null, null]], "PNC": [[null, null]], "ED": [[null, null]], "USB": [[null, null]], "ITW": [[null, null]], "ORLY": [[null, null]], "WM": [[null, null]], "ROK": [[null, null]], "FSLR": [[null, "2017-03-20"], ["2022-12-19", null]], "NTRS": [[null, null]], "C": [[null, null]], "GRMN": [["2012-12-11", null]], "LDOS": [["2019-08-09", null]], "AIZ": [[null, null]], "PKG": [["2017-07-26", null]], "DHR": [[null, null]], "MAS": [[null, null]], "ACN": [["2011-07-05", null]], "MDLZ": [["2012-10-02", null]], "DOW": [[null, "2017-09-01"], ["2019-04-02", null]], "ILMN": [["2015-11-19", "2024-06-24"], ["2026-09-21", null]], "PHM": [[null, null]], "EW": [["2011-03-31", null]], "WELL": [[null, null]], "COF": [[null, null]], "ADSK": [[null, null]], "NSC": [[null, null]], "HD": [[null, null]], "CLX": [[null, null]], "HST": [[null, null]], "INTU": [["2000-12-05", null]], "SNPS": [["2017-03-16", null]], "MET": [[null, null]], "GE": [[null, null]], "FLEX": [["2026-06-22", null]], "FANG": [["2018-12-03", null]], "AMGN": [[null, null]], "D": [[null, null]], "TDG": [["2016-06-03", null]], "AON": [[null, null]], "HCA": [["2015-01-27", null]], "XYZ": [["2025-07-23", null]], "NVDA": [[null, null]], "ALB": [["2016-07-01", null]], "NVR": [["2019-09-26", null]], "LRCX": [["2012-06-05", null]], "FE": [[null, null]], "GL": [[null, null]], "COST": [[null, null]], "SW": [[null, null]], "AFL": [[null, null]], "VRSK": [["2015-10-07", null]], "QCOM": [[null, null]], "APD": [[null, null]], "KR": [[null, null]], "NKE": [[null, null]], "HPQ": [[null, null]], "EG": [[null, null]], "IBM": [[null, null]], "LMT": [[null, null]], "GD": [[null, null]], "DLTR": [["2011-12-16", null]], "RJF": [["2017-03-20", null]], "BX": [["2023-09-18", null]], "FOXA": [[null, "2019-03-19"], ["2019-03-19", null]], "PWR": [[null, null]], "LH": [[null, null]], "PG": [[null, null]], "SO": [[null, null]], "ORCL": [[null, null]], "KO": [[null, null]], "EME": [["2025-09-22", null]], "INTC": [[null, null]], "CASY": [["2026-04-09", null]], "PSA": [[null, null]], "GLW": [[null, null]], "OMC": [[null, null]], "CINF": [[null, null]], "NOC": [[null, null]], "SYF": [["2015-11-18", null]], "CDNS": [["2017-09-18", null]], "MTB": [[null, null]], "ECHO": [[null, null]], "WSM": [["2025-03-24", null]], "BALL": [[null, null]], "JPM": [[null, null]], "ETN": [[null, null]], "HLT": [["2017-06-19", null]], "BDX": [[null, null]], "MPC": [["2011-06-30", null]], "IBKR": [["2025-08-28", null]], "P": [["2026-09-21", null]], "ADM": [[null, null]], "HPE": [["2015-11-02", null]], "INCY": [["2017-02-28", null]], "TXT": [[null, null]], "O": [["2015-04-07", null]], "WEC": [[null, null]], "ADI": [[null, null]], "TAP": [[null, "2026-09-21"]], "BLDR": [["2023-12-18", "2026-09-21"]], "TTD": [["2025-07-18", "2026-09-21"]], "AVB": [["2007-01-10", "2026-08-18"]], "EA": [[null, "2026-08-05"]], "CAG": [[null, "2026-06-30"]], "POOL": [["2020-10-07", "2026-06-22"]], "CPB": [[null, "2026-06-22"]], "EPAM": [["2021-12-14", "2026-06-02"]], "CTRA": [[null, "2026-05-07"]], "HOLX": [["2016-03-30", "2026-04-09"]], "MOH": [["2022-03-02", "2026-03-23"]], "PAYC": [["2020-01-28", "2026-03-23"]], "MTCH": [["2021-09-20", "2026-03-23"]], "LW": [["2018-12-03", "2026-03-23"]], "DAY": [["2024-02-01", "2026-02-09"]], "LKQ": [["2016-05-23", "2025-12-22"]], "SOLS": [["2025-10-30", "2025-12-22"]], "MHK": [["2013-12-23", "2025-12-22"]], "K": [[null, "2025-12-11"]], "IPG": [[null, "2025-11-28"]], "EMN": [[null, "2025-11-04"]], "KMX": [["2010-06-28", "2025-10-31"]], "MKTX": [["2019-07-01", "2025-09-22"]], "ENPH": [["2021-01-07", "2025-09-22"]], "CZR": [["2021-03-22", "2025-09-22"]], "WBA": [[null, "2025-08-28"]], "HES": [[null, "2025-07-23"]], "ANSS": [["2017-06-19", "2025-07-18"]], "JNPR": [["2006-06-02", "2025-07-09"]], "DFS": [["2007-07-02", "2025-05-19"]], "BWA": [["2011-12-16", "2025-03-24"]], "TFX": [["2019-01-18", "2025-03-24"]], "CE": [["2018-12-24", "2025-03-24"]], "FMC": [["2009-08-19", "2025-03-24"]], "CTLT": [["2020-09-21", "2024-12-23"]], "AMTM": [["2024-09-30", "2024-12-23"]], "QRVO": [["2015-06-11", "2024-12-23"]], "MRO": [[null, "2024-11-26"]], "BBWI": [[null, "2024-10-01"]], "AAL": [["2015-03-23", "2024-09-23"]], "ETSY": [["2020-09-21", "2024-09-23"]], "BIO": [["2020-06-22", "2024-09-23"]], "RHI": [[null, "2024-06-24"]], "CMA": [[null, "2024-06-24"]], "PXD": [[null, "2024-05-08"]], "XRAY": [[null, "2024-04-03"]], "VFC": [[null, "2024-04-03"]], "WHR": [[null, "2024-03-18"]], "ZION": [[null, "2024-03-18"]], "CDAY": [["2021-09-20", "2024-02-01"]], "SEE": [[null, "2023-12-18"]], "ALK": [["2016-05-13", "2023-12-18"]], "SEDG": [["2021-12-20", "2023-12-18"]], "OGN": [["2021-06-03", "2023-10-18"]], "ATVI": [["2015-08-28", "2023-10-18"]], "DXC": [["2017-04-04", "2023-10-03"]], "LNC": [[null, "2023-09-18"]], "NWL": [[null, "2023-09-18"]], "AAP": [["2015-07-08", "2023-08-25"]], "DISH": [["2017-03-13", "2023-06-20"]], "FRC": [["2019-01-02", "2023-05-04"]], "LUMN": [[null, "2023-03-20"]], "SBNY": [["2021-12-20", "2023-03-15"]], "SIVB": [["2018-03-19", "2023-03-15"]], "VNO": [[null, "2023-01-05"]], "ABMD": [["2018-05-31", "2022-12-22"]], "MBC": [["2022-12-15", "2022-12-19"]], "FBHS": [["2016-06-22", "2022-12-19"]], "TWTR": [["2018-06-07", "2022-11-01"]], "NLSN": [["2013-07-08", "2022-10-12"]], "CTXS": [[null, "2022-10-03"]], "DRE": [["2017-07-26", "2022-10-03"]], "PVH": [["2013-02-15", "2022-09-19"]], "PENN": [["2021-03-22", "2022-09-19"]], "UA": [["2016-04-08", "2022-06-21"]], "UAA": [[null, "2022-06-21"]], "IPGP": [["2018-03-07", "2022-06-21"]], "CERN": [["2010-04-29", "2022-06-08"]], "DISCK": [["2014-08-06", "2022-04-11"]], "DISCA": [[null, "2022-04-11"]], "PBCT": [[null, "2022-04-04"]], "INFO": [["2017-06-02", "2022-03-02"]], "XLNX": [[null, "2022-02-15"]], "GPS": [[null, "2022-02-03"]], "WLTW": [["2016-01-05", "2022-01-10"]], "LEG": [[null, "2021-12-20"]], "HBI": [["2015-03-23", "2021-12-20"]], "WU": [[null, "2021-12-20"]], "KSU": [["2013-05-23", "2021-12-14"]], "NOV": [[null, "2021-09-20"]], "PRGO": [["2011-12-16", "2021-09-20"]], "UNM": [[null, "2021-09-20"]], "MXIM": [[null, "2007-09-27"], ["2018-12-03", "2021-08-30"]], "ALXN": [["2012-05-21", "2021-07-21"]], "HFC": [["2018-06-18", "2021-06-04"]], "FLIR": [[null, "2021-05-14"]], "VAR": [[null, "2021-04-20"]], "FLS": [[null, "2021-03-22"]], "SLG": [["2015-03-23", "2021-03-22"]], "XRX": [[null, "2021-03-22"]], "VNT": [["2020-10-09", "2021-03-22"]], "FTI": [["2009-06-05", "2021-02-12"]], "CXO": [["2016-02-22", "2021-01-21"]], "TIF": [[null, "2021-01-07"]], "AIV": [[null, "2020-12-21"]], "NBL": [[null, "2020-10-12"]], "ETFC": [[null, "2020-10-07"]], "HRB": [[null, "2020-09-21"]], "COTY": [["2016-09-30", "2020-09-21"]], "KSS": [[null, "2020-09-21"]], "ADS": [["2013-12-23", "2020-06-22"]], "HOG": [[null, "2020-06-22"]], "JWN": [[null, "2020-06-22"]], "HP": [["2010-02-26", "2020-05-22"]], "CPRI": [[null, "2020-05-12"]], "AGN": [[null, "2020-05-12"]], "M": [[null, "2020-04-06"]], "RTN": [[null, "2020-04-06"]], "ARNC": [["2016-11-01", "2020-04-01"]], "XEC": [["2014-06-20", "2020-03-02"]], "WCG": [["2018-09-14", "2020-01-28"]], "AMG": [["2014-07-01", "2019-12-23"]], "TRIP": [["2011-12-20", "2019-12-23"]], "MAC": [["2013-05-08", "2019-12-23"]], "STI": [[null, "2019-12-09"]], "VIAB": [[null, "2019-12-05"]], "CELG": [[null, "2019-11-21"]], "NKTR": [["2018-03-19", "2019-10-03"]], "JEF": [[null, "2019-09-26"]], "TSS": [[null, "2019-09-23"]], "APC": [[null, "2019-08-09"]], "FL": [["2016-04-04", "2019-08-09"]], "RHT": [[null, "2019-07-15"]], "LLL": [[null, "2019-07-01"]], "BMS": [[null, "2014-12-05"], ["2019-06-07", "2019-06-11"]], "MAT": [[null, "2019-06-07"]], "FLR": [[null, "2019-06-03"]], "DWDP": [["2017-09-01", "2019-06-03"]], "BHF": [["2017-08-08", "2019-04-02"]], "GT": [[null, "2019-02-27"]], "NFX": [["2010-12-17", "2019-02-15"]], "SCG": [[null, "2019-01-02"]], "ESRX": [["2003-09-25", "2018-12-24"]], "COL": [[null, "2018-12-03"]], "AET": [[null, "2018-12-03"]], "SRCL": [[null, "2018-12-03"]], "CA": [[null, "2018-11-06"]], "EVHC": [["2016-12-02", "2018-10-11"]], "ANDV": [[null, "2018-10-01"]], "XL": [[null, "2018-09-14"]], "GGP": [["2013-12-10", "2018-08-28"]], "DPS": [[null, "2018-07-02"]], "TWX": [[null, "2018-06-20"]], "RRC": [["2007-12-20", "2018-06-18"]], "AYI": [["2016-05-03", "2018-06-18"]], "MON": [[null, "2018-06-07"]], "NAVI": [["2014-05-01", "2018-06-05"]], "WYN": [[null, "2018-05-31"]], "CSRA": [["2015-12-01", "2018-04-04"]], "SIG": [["2015-07-29", "2018-03-19"]], "CHK": [[null, "2018-03-19"]], "PDCO": [[null, "2018-03-19"]], "SNI": [[null, "2018-03-07"]], "BCR": [[null, "2018-01-03"]], "LVLT": [["2014-11-05", "2017-10-13"]], "SPLS": [[null, "2017-09-18"]], "WFM": [[null, "2017-08-29"]], "AN": [[null, "2017-08-08"]], "MNK": [["2014-08-18", "2017-07-26"]], "RAI": [[null, "2017-07-26"]], "RIG": [["2013-10-29", "2017-07-26"]], "MUR": [[null, "2017-07-26"]], "BBBY": [[null, "2017-07-26"]], "BHI": [[null, "2017-07-07"]], "YHOO": [["1999-12-08", "2017-06-19"]], "TDC": [["2007-10-01", "2017-06-19"]], "R": [[null, "2017-06-19"]], "MJN": [["2009-12-18", "2017-06-19"]], "TGNA": [[null, "2017-06-02"]], "DNB": [[null, "2017-04-05"]], "SWN": [[null, "2017-04-04"]], "URBN": [[null, "2017-03-20"]], "FTR": [[null, "2017-03-20"]], "HAR": [[null, "2017-03-16"]], "LLTC": [[null, "2017-03-13"]], "ENDP": [["2015-01-27", "2017-03-02"]], "PBI": [[null, "2017-03-01"]], "SE": [[null, "2017-02-28"]], "STJ": [[null, "2017-01-05"]], "OI": [[null, "2000-12-05"], ["2008-12-31", "2016-12-02"]], "LM": [[null, "2016-12-02"]], "AA": [[null, "2016-11-01"]], "DO": [[null, "2016-09-30"]], "HOT": [[null, "2016-09-22"]], "EMC": [[null, "2016-09-08"]], "TYC": [["2010-08-26", "2016-09-06"]], "CPGX": [["2015-07-02", "2016-07-05"]], "GAS": [[null, "2011-12-12"], ["2011-12-12", "2016-07-01"]], "TE": [[null, "2016-07-01"]], "CVC": [["2010-12-17", "2016-06-22"]], "BXLT": [["2015-07-01", "2016-06-03"]], "CCE": [[null, "2016-05-31"]], "ARG": [["2009-09-28", "2016-05-23"]], "TWC": [[null, "2016-05-18"]], "ADT": [["2012-10-01", "2016-05-03"]], "GME": [["2007-12-13", "2016-04-25"]], "THC": [[null, "2016-04-18"]], "CAM": [[null, "2016-04-04"]], "POM": [[null, "2016-03-30"]], "ESV": [["2012-07-31", "2016-03-30"]], "GMCR": [["2014-03-21", "2016-03-07"]], "CNX": [[null, "2016-03-04"]], "PCL": [[null, "2016-02-22"]], "PCP": [[null, "2016-02-01"]], "BRCM": [[null, "2016-02-01"]], "ACE": [[null, "2016-01-19"]], "FOSL": [["2012-04-03", "2016-01-05"]], "ALTR": [[null, "2015-12-29"]], "CMCSK": [["2015-09-18", "2015-12-15"]], "CSC": [[null, "2015-12-01"]], "SIAL": [[null, "2015-11-19"]], "GNW": [[null, "2015-11-18"]], "HCBK": [[null, "2015-11-02"]], "JOY": [[null, "2015-10-07"]], "HSP": [[null, "2015-09-02"]], "PLL": [[null, "2015-08-28"]], "DTV": [[null, "2015-07-29"]], "NE": [[null, "2015-07-20"]], "FDO": [[null, "2015-07-08"]], "KRFT": [["2012-10-02", "2015-07-06"]], "ATI": [[null, "2015-07-02"]], "TEG": [[null, "2015-07-01"]], "QEP": [["2010-06-30", "2015-07-01"]], "LO": [["2008-06-10", "2015-06-11"]], "WIN": [[null, "2015-04-07"]], "DNR": [[null, "2015-03-23"]], "NBR": [[null, "2015-03-23"]], "AVP": [[null, "2015-03-23"]], "CFN": [[null, "2015-03-18"]], "PETM": [["2012-10-10", "2015-03-12"]], "SWY": [[null, "2015-01-27"]], "COV": [[null, "2009-06-05"], ["2011-02-28", "2015-01-27"]], "BTU": [[null, "2014-09-20"]], "GHC": [[null, "2014-09-20"]], "RDC": [[null, "2014-08-18"]], "X": [[null, "2014-07-02"]], "FRX": [[null, "2014-07-01"]], "IGT": [[null, "2014-06-20"]], "LSI": [[null, "2014-05-08"]], "BEAM": [[null, "2014-05-01"]], "SLM": [[null, "2014-05-01"]], "CLF": [["2009-12-18", "2014-04-02"]], "WPX": [["2011-12-31", "2014-03-21"]], "LIFE": [[null, "2014-01-24"]], "ANF": [[null, "2013-12-23"]], "JDSU": [["2000-07-27", "2013-12-23"]], "MOLX": [[null, "2013-12-10"]], "JCP": [[null, "2013-12-02"]], "NYX": [[null, "2013-11-13"]], "SAI": [["2009-12-18", "2013-09-20"]], "BMC": [[null, "2013-09-10"]], "S": [[null, "2013-07-08"]], "APOL": [[null, "2013-07-01"]], "FHN": [[null, "2013-06-21"]], "HNZ": [[null, "2013-06-06"]], "DF": [[null, "2013-05-23"]], "CVH": [[null, "2013-05-08"]], "PCS": [["2009-06-29", "2013-04-30"]], "BIG": [[null, "2013-02-15"]], "FII": [[null, "2013-01-02"]], "TIE": [[null, "2012-12-21"]], "RRD": [[null, "2012-12-11"]], "CBE": [[null, "2009-09-28"], ["2011-11-18", "2012-12-03"]], "SUN": [[null, "2012-10-10"]], "ANR": [["2011-06-01", "2012-10-02"]], "KFT": [["2007-03-30", "2012-10-02"]], "LXK": [[null, "2012-10-01"]], "DV": [[null, "2012-10-01"]], "SHLD": [[null, "2012-09-05"]], "GR": [[null, "2012-07-31"]], "PGN": [[null, "2012-07-02"]], "SLE": [[null, "2012-06-29"]], "NVLS": [[null, "2012-06-05"]], "MMI": [["2011-01-03", "2012-05-21"]], "EP": [[null, "2012-05-17"]], "SVU": [[null, "2012-04-23"]], "MHS": [[null, "2012-04-03"]], "CPWR": [["1998-12-11", "2011-12-31"]], "TLAB": [[null, "2011-12-20"]], "AKS": [["2008-07-01", "2011-12-16"]], "MWW": [[null, "2011-12-16"]], "WFR": [[null, "2011-12-16"]], "JNS": [[null, "2011-11-18"]], "ITT": [[null, "2011-10-31"]], "CEPH": [[null, "2011-10-14"]], "NSM": [[null, "2011-09-23"]], "MI": [[null, "2011-07-05"]], "RSH": [[null, "2011-06-30"]], "MEE": [["2008-06-23", "2011-06-01"]], "NOVL": [[null, "2011-04-27"]], "GENZ": [[null, "2011-04-01"]], "MFE": [[null, "2011-02-28"]], "AYE": [[null, "1976-07-01"], ["2000-12-05", "2011-02-25"]], "MDP": [[null, "2011-01-03"]], "ODP": [[null, "2010-12-17"]], "NYT": [[null, "2010-12-17"]], "EK": [[null, "2010-12-17"]], "KG": [[null, "2010-12-17"]], "PTV": [[null, "2010-11-17"]], "SII": [[null, "2010-08-26"]], "MIL": [[null, "2010-07-14"]], "STR": [[null, "2010-06-30"]], "XTO": [[null, "2010-06-28"]], "BJS": [[null, "2010-04-29"]], "RX": [[null, "2010-02-26"]], "MBI": [[null, "2009-12-18"]], "CVG": [["2000-06-12", "2009-12-18"]], "KBH": [[null, "2009-12-18"]], "DYN": [[null, "2009-12-18"]], "SGP": [[null, "2009-11-03"]], "CTX": [[null, "2009-08-19"]], "ACAS": [[null, "2009-03-03"]], "JNY": [[null, "2009-03-03"]], "WB": [[null, "2008-12-31"]], "LEH": [[null, "2008-09-16"]], "FNM": [[null, "2008-09-12"]], "FRE": [[null, "2008-09-12"]], "CFC": [[null, "2008-07-01"]], "BC": [[null, "2008-06-23"]], "OMX": [[null, "2008-06-23"]], "ABK": [["2000-12-05", "2008-06-10"]], "TRB": [[null, "2007-12-20"]], "DJ": [[null, "2007-12-13"]], "AV": [[null, "2007-10-26"]], "SLR": [[null, "2007-10-02"]], "NCR": [[null, "2007-10-01"]], "FDC": [[null, "2007-09-26"]], "KSE": [[null, "2007-08-24"]], "ADCT": [[null, "2007-07-02"]], "TSG": [[null, "2007-03-30"]], "SBL": [["2000-12-05", "2007-01-10"]], "ABS": [[null, "2006-06-02"]], "GLK": [[null, "2005-07-01"]], "QTRN": [[null, "2003-09-25"]], "BS": [[null, "2000-12-05"]], "GRA": [[null, "2000-12-05"]], "CCK": [[null, "2000-12-05"]], "RAD": [[null, "2000-07-27"]], "TMC": [[null, "2000-06-12"]], "SMS": [[null, "2000-06-07"]], "LDW": [[null, "1999-12-08"]], "HPH": [[null, "1999-06-09"]], "GRN": [[null, "1998-12-11"]], "USL": [[null, "1997-06-17"]], "HNG": [[null, "1976-07-01"]]}} \ No newline at end of file diff --git a/research/lab/momentum_fundamentals.py b/research/lab/momentum_fundamentals.py new file mode 100644 index 0000000..d117a0c --- /dev/null +++ b/research/lab/momentum_fundamentals.py @@ -0,0 +1,140 @@ +""" +Does financial-statement analysis improve overnight_recovery_top10? + + PYTHONPATH=. uv run --with numba python -m research.lab.momentum_fundamentals [--sensitivity] + +Averaging fundamental ranks into the momentum score was already tried and +hurts (fund_eval.py: Sharpe 0.80 -> 0.63; momentum's alpha lives in its most +extreme names). So every variant here keeps momentum's ranking and uses the +statements only to veto or to confirm, the way an analyst would screen a +momentum list. Fundamentals are point-in-time EDGAR values (first filing, +usable the day after it; research/lab/fundamentals.py); they cover ~90% of +S&P 500 members from 2011, so the test runs 2011-2026 (design 2011-2016, +holdout 2017-2026). A name without data is never vetoed. + +Variants, fixed before looking at results (7 trials): + profitable veto trailing losses: ROE < 0 or operating cash flow < 0 + quality_veto veto the bottom 20% of a sector-relative quality score + (mean rank of ROE, CFO/assets, accruals, gross profit/assets) + earnings_confirm keep only a positive latest earnings surprise (SUE > 0): + price momentum backed by earnings momentum (Chan, Jegadeesh & + Lakonishok 1996; Novy-Marx 2015) + revenue_confirm keep only positive year-on-year revenue growth + fund_momentum top 30 by momentum, then the 10 with the best fundamental + momentum (mean rank of SUE, revenue SUE, gross-margin change, + ROA change) + red_flags veto the worst decile of accruals (Sloan 1996) or of share + issuance (dilution) + fscore veto a Piotroski-style F-score below 4 +Then a neighbourhood check of the two that looked best (``--sensitivity``): +fund_momentum with the top 20 / 40 instead of 30, quality_veto at 10% / 30%. +Base: overnight_mom + rec_126, top 10, every 10 days, buffer 2 (the live +overnight_recovery_top10). Each variant is compared with the base on the +same days (paired difference, Newey-West t) and against QQQ. +""" + +from __future__ import annotations + +import dataclasses +import json +import os +import warnings + +import numpy as np +import pandas as pd + +from research.lab import fundamentals as fu +from research.lab.alpha import _nw_se, alpha_stats, bench_returns +from research.lab.factor_scan import RESULTS, load +from research.lab.report import stats +from research.lab.rotation import factor_ranks +from research.lab.us_sweep import _blend, log_trials, run, sector_rank + +warnings.filterwarnings("ignore") +SPAN = ("2011-01-03", "2026-09-24") +DESIGN = ("2011-01-03", "2016-12-31") +HOLDOUT = ("2017-01-01", "2026-09-24") + + +def variants(p, sensitivity: bool = False) -> dict[str, tuple[pd.DataFrame, pd.DataFrame, float]]: + """name -> (score, eligible mask, rank buffer).""" + pr = factor_ranks(p, "us") + F = fu.load(p) + mom = _blend(pr["overnight_mom"], pr["rec_126"]).where(p.member) + m = p.member + ok = lambda veto: m & ~veto.fillna(False) # noqa: E731 + pct = lambda x: x.where(m).rank(axis=1, pct=True) # noqa: E731 + quality = _blend(*(sector_rank(F[k], p) for k in ("roe", "cfo_a", "accruals", "gp_a"))) + fmom = _blend(*(pct(F[k]) for k in ("sue", "sue_rev", "gm_change", "roa_change"))) + mom_rank = mom.rank(axis=1, ascending=False) + top30 = mom_rank <= 30 + if sensitivity: + return { + "base": (mom, m, 2.0), + "fund_momentum top20": (fmom.fillna(0.5).where(mom_rank <= 20), m, 1.0), + "fund_momentum top30": (fmom.fillna(0.5).where(top30), m, 1.0), + "fund_momentum top40": (fmom.fillna(0.5).where(mom_rank <= 40), m, 1.0), + "quality_veto 10%": (mom, ok(quality < 0.1), 2.0), + "quality_veto 30%": (mom, ok(quality < 0.3), 2.0), + } + return { + "base": (mom, m, 2.0), + "profitable": (mom, ok((F["roe"] < 0) | (F["cfo_a"] < 0)), 2.0), + "quality_veto": (mom, ok(quality < 0.2), 2.0), + "earnings_confirm": (mom, ok(F["sue"] <= 0), 2.0), + "revenue_confirm": (mom, ok(F["rev_growth"] <= 0), 2.0), + "fund_momentum": (fmom.fillna(0.5).where(top30), m, 1.0), # no data: neutral + "red_flags": (mom, ok((pct(F["accruals"]) < 0.1) | (pct(F["net_issuance"]) < 0.1)), 2.0), + "fscore": (mom, ok(F["fscore"] < 4), 2.0), + } + + +def paired_t(d: pd.Series) -> float: + d = d.dropna() + X = np.ones((len(d), 1)) + se = _nw_se(X, (d - d.mean()).to_numpy())[0] + return float(d.mean() / se) + + +def main() -> None: + import sys + sens = "--sensitivity" in sys.argv + p, _ = load("us") + b = bench_returns(p.dates) + R, rows = {}, [] + for name, (score, elig, buf) in variants(p, sens).items(): + pv = dataclasses.replace(p, member=elig) + st = run(pv, score.where(elig), [SPAN], top_n=10, buffer=buf, every=10) + R[name] = st["_rets"] + rows.append({"score": f"momfund:{name}", **{k: v for k, v in st.items() if not k.startswith("_")}}) + log_trials(rows[1:], "momentum_fundamentals" + ("_sensitivity" if sens else "")) + R = pd.DataFrame(R) + out = {} + print(f"{'':18s}{'2011-2026':>34s} | {'2011-2016 (design)':>34s} | {'2017-2026 (holdout)':>34s} | vs base 17-26") + for name in R.columns: + r = R[name] + cells, rec = [], {} + for label, (a, z) in (("full", SPAN), ("design", DESIGN), ("holdout", HOLDOUT)): + x = r.loc[a:z] + s = stats((1 + x).cumprod()) + al = alpha_stats(x, b) + rec[label] = {"cagr": s["cagr"], "sharpe": s["sharpe"], "mdd": s["mdd"], "alpha": al["alpha"], "t": al["t"]} + cells.append(f"{s['cagr']*100:5.1f}% sh {s['sharpe']:.2f} mdd {s['mdd']*100:4.0f}% a {al['alpha']*100:5.1f}%") + d = (r - R["base"]).loc[HOLDOUT[0]:HOLDOUT[1]] + dd = (r - R["base"]).loc[DESIGN[0]:DESIGN[1]] + extra = "" if name == "base" else (f"{d.mean()*252*100:+5.1f}%/yr t {paired_t(d):+.2f} " + f"(design {dd.mean()*252*100:+5.1f}% t {paired_t(dd):+.2f})") + rec["diff_holdout"] = float(d.mean() * 252) + out[name] = rec + print(f"{name:18s}" + " | ".join(cells) + f" | {extra}", flush=True) + yr = (1 + R).groupby(R.index.year).prod() - 1 + print("\nyearly returns (%):") + print((yr * 100).round(1).to_string()) + tag = "_sensitivity" if sens else "" + with open(os.path.join(RESULTS, f"momentum_fundamentals{tag}.json"), "w") as fh: + json.dump(out, fh, indent=1, default=float) + R.to_pickle(os.path.join(RESULTS, f"momentum_fundamentals{tag}.pkl")) + + +if __name__ == "__main__": + main()