f/fundlab/searchlist.py
Greg Pomerantz afec7bda73 Alpha search: mine + screen the local DB for idiosyncratic alpha complements
Answer to 'find other alpha-driven funds that complement the portfolio':

- fundlab/search.py: complementarity screen - each fund's daily total
  returns vs the same 21 broad sleeve axes (BIC forward selection,
  |t|>2), full + 5y; alpha (intercept t), R2, rolling 6m alpha
  persistence, correlation vs the current qspnx/pmaix portfolio and the
  spy/agg/tlt benchmark mix. Verdict tiers: CANDIDATE (alpha +
  persistent + portfolio-corr<0.3) / semi-alpha / alpha-but-correlated /
  sleeve mix / weak.
- fundlab/dbmine.py: the actual search universe - the local stocks DB
  already holds ~100 US open-end alternatives (AQR, PIMCO, JPM,
  Principal, Calamos, GMO, Franklin K2, ...). Name-pattern miner with
  share-class family dedupe (keeps the longest-history class).
- fundlab/tickers.py + searchlist.py: external longlist resolution
  (chart-API name gate + EDGAR 497 cover tickers). Finding: the famous
  multi-strategy/macro names (Millennium, Balyasny, Two Sigma, Winton,
  Marshall Wace, Brevan Howard, AQR Event-Driven) are private/offshore
  or terminated - not US open-end accessible. Fidelity Multi-Asset
  Income (FMSDX) resolved and screens as weak alpha.
- app Fund Lab: 'Alpha search - all screened funds, ranked' table
  (80 funds: 13 shortlist + 59 mined + 1 external).
- results (ranked candidates, 5y alpha / t / portfolio-corr):
  wmnix Westwood Alt Income +3.8% t6.5 c0.09 | pyaix Payden ARB +3.0%
  t4.8 c0.13 | srdax Stone Ridge Div Alts +7.7% t4.2 c0.10 | padqx PGIM
  ARB +2.3% t2.4 c0.27 | bxmdx Blackstone Alt MS +3.5% t2.4 c0.30 |
  aqmix AQR Mngd Futures +8.0% t2.2 c0.21 | cmnix/gioix semi-alpha.
  Key insight: AQR MN / L/S-equity / Vanguard MN show strong alpha but
  corr 0.35-0.76 with the portfolio - it is already 50% market-neutral
  (qspnx), so more MN is not diversifying.
- tests: 59/59 fundlab (resolver gates, query ladder, family dedupe,
  ticker regex), 32/32 app, 14/14 data
2026-08-26 13:34:34 -04:00

69 lines
3.8 KiB
Python

"""Curated longlist of candidate funds for the alpha search.
Universe rationale: we cannot meaningfully screen all ~10k registered
funds (most are small, illiquid or strategy-unstable and would not
deserve portfolio consideration anyway). So the UNIVERSE is curated by
reputation — large, liquid, long-tracked funds in the buckets where
idiosyncratic alpha lives (market-neutral/quant, multi-strategy, global
macro, dynamic TA, dynamic credit/convertibles) — and the SELECTION is
data-driven (fundlab/search.py). Tickers are resolved from the fund NAME
via Yahoo search with a precision gate; a candidate that cannot be
resolved cleanly is dropped, never guessed.
The 13-fund shortlist already in funds.json is screened with the same
code so the ranking is consistent (qspnx/pmaix are the user's current
holdings and act as controls).
"""
from __future__ import annotations
# (fund name as searched, strategy bucket, ticker guess or None)
LONGLIST: list[tuple[str, str, str | None]] = [
# --- market-neutral / quant / pure alpha / event-driven ------------
("AQR Diversified Event-Driven Fund", "event_driven", None),
("Bridgewater Pure Alpha II Fund", "pure_alpha", None),
("Winton Global Quantitative Fund", "cta_quant", None),
("Two Sigma Dynamic Strategy Fund", "systematic", None),
("Brevan Howard Dymon Asia Fund", "macro_relative_value", None),
("Marshall Wace Global Opportunities Fund", "macro_relative_value",
None),
# --- multi-strategy -------------------------------------------------
("ExodusPoint Diversified Fund", "multi_strategy", None),
("Verition Dynamic Risk Fund", "multi_strategy", None),
("Millennium Focus Fund", "multi_strategy", None),
("Balyasny Absolute Return Multi-Strategy Fund", "multi_strategy", None),
("Schonfeld Strategic Opportunities Fund", "multi_strategy", None),
# --- global macro / risk parity ------------------------------------
# (T. Rowe Price New Global Opportunity: fund terminated - no data)
("Bridgewater All Weather Fund", "risk_parity", None),
("Oak Hill Tactical Allocation Fund", "tactical_allocation", None),
# --- multi-asset / dynamic TA open-ends (the accessible alpha pool) -
("Fidelity Multi-Asset Income Fund", "tactical_allocation", None),
("Janus Henderson Global Dynamic Dividend Fund", "dynamic_equity", None),
("Wellington Dynamic Global Diversified Fund", "tactical_allocation", None),
("Lord Abbett Global Opportunities Fund", "tactical_allocation", None),
("BlackRock Multi-Asset Income Fund", "tactical_allocation", None),
("PIMCO Income Strategy Fund", "tactical_allocation", None),
("JPMorgan Diversified Return Fund", "tactical_allocation", None),
("Invesco Diversified Equity and Income Fund", "tactical_allocation", None),
("T. Rowe Price Global Allocation Fund", "tactical_allocation", None),
("Morgan Stanley Global Multi Asset Fund", "tactical_allocation", None),
("Fidelity Diversified Multi-Asset Fund", "tactical_allocation", None),
# --- controls: credit (expected to classify as sleeve mix)
# (Calamos Dynamic Convertible CCD is a CEF - excluded)
("PIMCO Dynamic Income Fund", "dynamic_credit", None),
]
# the 13 unique shortlist funds (share classes resolved once)
SHORTLIST = ["atesx", "atrfx", "cvsix", "jlpsx", "pmaix", "pmorx", "qspnx",
"svarx", "cosix", "mbxix", "eagmx", "lcorx", "lamhx"]
# broad sleeve set used by the screen (same 21 axes for every fund -
# nothing is tuned to a specific fund, so selection is comparable)
BROAD_SLEEVES = ["qqq", "ivv", "iwm", "vea", "efa", "vwo", "vnq", "bil",
"shv", "ief", "tlt", "vblix", "agg", "vweax", "vmbix",
"finux", "djp", "gsg", "gld", "fxe", "fxy"]
# the user's current portfolio + benchmark mix (settings.json)
PORTFOLIO = {"qspnx": 0.5, "pmaix": 0.5}
BENCHMARKS = {"spy": 1 / 3, "agg": 1 / 3, "tlt": 1 / 3}