Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- README.md +12 -6
- book.py +6 -4
- engine/export_bundle.py +150 -0
- risk_screen.py +14 -5
- static/app.js +3 -2
- static/index.html +42 -36
README.md
CHANGED
|
@@ -13,12 +13,18 @@ short_description: Smart-money, quality, trend-adaptive paper fund + picks
|
|
| 13 |
|
| 14 |
A quality, trend-adaptive equity **paper fund** that drafts the smart money.
|
| 15 |
|
| 16 |
-
**Strategy (
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
The page shows the live Alpaca **paper** portfolio, performance vs SPY, the current
|
| 24 |
target book, and the informational **Top AI Picks of the Month** panels.
|
|
|
|
| 13 |
|
| 14 |
A quality, trend-adaptive equity **paper fund** that drafts the smart money.
|
| 15 |
|
| 16 |
+
**Strategy (rule v2, Oct 2026):** hold the large-caps that concentrated 13F managers are collectively
|
| 17 |
+
buying β every filer with 10β150 positions, $250Mβ$100B and β₯40% in its top 10 (rule-based, ~900β1,500 a
|
| 18 |
+
quarter); signal = 2Β·new + add β 2Β·exit conviction positions β restricted to the top-1,000 by market cap
|
| 19 |
+
and to companies whose ROA is in the **top 30% of their sector**; top-20 dollar-weighted (**1β10% per
|
| 20 |
+
name**, β€5 per sector) with a **dynamic sector cap** (each sector 30% Β± its 12-1m relative strength,
|
| 21 |
+
15β45%) and a **regime overlay** that rotates into large-cap momentum when SPY is in a confirmed
|
| 22 |
+
downtrend. An AI overlay excludes only names hit by a critical adverse event. Rebalanced monthly.
|
| 23 |
+
|
| 24 |
+
Backtest 2014-03 β 2026-10 (survivorship-free, data through *d* β 1, 20 bp/side, Sharpe net of T-bills):
|
| 25 |
+
CAGR 19.9% vs SPY 13.9%, Sharpe 0.85 vs 0.73, max drawdown -37.5% vs -33.7%,
|
| 26 |
+
alpha +3.9% (t 1.8), Ξ² 1.17. Chosen on 2016β2021 from ~210 configurations; it trailed SPY over the
|
| 27 |
+
never-used 2014-03 β 2016-02 window. Research and pre-registrations: `Quant Model/data/research/slipstream/`.
|
| 28 |
|
| 29 |
The page shows the live Alpaca **paper** portfolio, performance vs SPY, the current
|
| 30 |
target book, and the informational **Top AI Picks of the Month** panels.
|
book.py
CHANGED
|
@@ -18,7 +18,7 @@ import pandas as pd
|
|
| 18 |
DATA = Path(__file__).resolve().parent / "data"
|
| 19 |
TOPN, NAME_CAP, MOM_N = 20, 5, 50
|
| 20 |
CAP_BASE, CAP_LO, CAP_HI = 0.30, 0.15, 0.45
|
| 21 |
-
MAX_POS_W = 0.
|
| 22 |
MIN_POS_W = 0.01 # min weight in any one position (see _name_floor)
|
| 23 |
_CACHE_TTL_S = 15 * 60
|
| 24 |
|
|
@@ -130,7 +130,9 @@ def _name_cap(w, cap=MAX_POS_W):
|
|
| 130 |
See Quant Model/scripts/weighting_study.py: over 2014-2026 (top-20, monthly,
|
| 131 |
40bps) every cap from 8-30% beat the uncapped book on Sharpe, a smooth plateau
|
| 132 |
rather than a single lucky point; 15% gave the best drawdown-adjusted return
|
| 133 |
-
(Calmar 0.475 vs 0.387 uncapped, maxDD -35.0% vs -39.5%).
|
|
|
|
|
|
|
| 134 |
for _ in range(50):
|
| 135 |
over = w > cap + 1e-12
|
| 136 |
if not over.any():
|
|
@@ -236,8 +238,8 @@ def _compute_book(screen: bool = True) -> dict:
|
|
| 236 |
"n_holdings": len(holdings),
|
| 237 |
"holdings": holdings,
|
| 238 |
"risk_screen": screen_meta,
|
| 239 |
-
"rule": "
|
| 240 |
-
"dynamic sector cap 15β45% Β· AI
|
| 241 |
}
|
| 242 |
|
| 243 |
|
|
|
|
| 18 |
DATA = Path(__file__).resolve().parent / "data"
|
| 19 |
TOPN, NAME_CAP, MOM_N = 20, 5, 50
|
| 20 |
CAP_BASE, CAP_LO, CAP_HI = 0.30, 0.15, 0.45
|
| 21 |
+
MAX_POS_W = 0.10 # max weight in any ONE position (v2, Oct 2026: Study 7 β same Sharpe as 15%, top-5 48% vs 56%)
|
| 22 |
MIN_POS_W = 0.01 # min weight in any one position (see _name_floor)
|
| 23 |
_CACHE_TTL_S = 15 * 60
|
| 24 |
|
|
|
|
| 130 |
See Quant Model/scripts/weighting_study.py: over 2014-2026 (top-20, monthly,
|
| 131 |
40bps) every cap from 8-30% beat the uncapped book on Sharpe, a smooth plateau
|
| 132 |
rather than a single lucky point; 15% gave the best drawdown-adjusted return
|
| 133 |
+
(Calmar 0.475 vs 0.387 uncapped, maxDD -35.0% vs -39.5%). Rule v2 (Oct 2026) uses 10%:
|
| 134 |
+
on the concentrated-manager + ROA book it kept Sharpe (0.98 vs 0.98, 2016-26) while
|
| 135 |
+
cutting the top-5 weight from 56% to 48% (data/research/slipstream/quality, Study 7)."""
|
| 136 |
for _ in range(50):
|
| 137 |
over = w > cap + 1e-12
|
| 138 |
if not over.any():
|
|
|
|
| 238 |
"n_holdings": len(holdings),
|
| 239 |
"holdings": holdings,
|
| 240 |
"risk_screen": screen_meta,
|
| 241 |
+
"rule": "concentrated-manager 13F consensus Β· top-1000 Β· quality(ROA top-30% in sector) Β· top-20 $-wt "
|
| 242 |
+
"(1β10%/name) Β· dynamic sector cap 15β45% Β· AI critical-event exclusion Β· regimeβmomentum (SPY live) Β· monthly",
|
| 243 |
}
|
| 244 |
|
| 245 |
|
engine/export_bundle.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Slipstream β data-bundle exporter (runs LOCALLY, where the Sharadar cache lives).
|
| 3 |
+
|
| 4 |
+
Rule (v2, Oct 2026 β validated in data/research/slipstream/quality, Studies 4β8):
|
| 5 |
+
managers every 13F filer that is a concentrated stock-picker that quarter (10β150 positions,
|
| 6 |
+
$250Mβ$100B, top-10 β₯ 40%, filed the prior quarter) β built by
|
| 7 |
+
scripts/slipstream/manager_universe.py --sets CONC (no curated list, no insiders)
|
| 8 |
+
signal consensus = 2Β·new + add β 2Β·exit conviction positions; eligible if > 0; size = 13F $
|
| 9 |
+
quality ROA (SF1 ART, latest filing public by the signal date) in the top 30% of its sector,
|
| 10 |
+
ranked across the month's top-1,000 universe
|
| 11 |
+
|
| 12 |
+
This script exports a small, self-contained bundle the HF Space computes the fund from:
|
| 13 |
+
|
| 14 |
+
data/candidates.parquet β latest eligible names (top-1000 & ROA-30 & consensus BUY),
|
| 15 |
+
with score, $-size and sector
|
| 16 |
+
data/prices.parquet β daily adj close for the top-1000 universe + SPY, last ~400
|
| 17 |
+
trading days (drives regime, momentum and sector-RS on HF)
|
| 18 |
+
data/meta.json β as-of date, universe, generated-at
|
| 19 |
+
|
| 20 |
+
13F is quarterly and ~45d lagged, so re-running this quarterly (and committing the bundle
|
| 21 |
+
to the Space) keeps the fund current. Run: python3 engine/export_bundle.py
|
| 22 |
+
"""
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
import json
|
| 26 |
+
import sys
|
| 27 |
+
from datetime import datetime, timezone
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
|
| 30 |
+
import pandas as pd
|
| 31 |
+
|
| 32 |
+
import os as _os
|
| 33 |
+
# env-overridable: this runs inside the Refinery container too, where the Quant Model
|
| 34 |
+
# code lives at /app/quantmodel rather than on the Mac.
|
| 35 |
+
QM = Path(_os.environ.get("QM_DIR", "/Users/sttp/Desktop/Quant Model"))
|
| 36 |
+
sys.path.insert(0, str(QM))
|
| 37 |
+
import scripts.follow_the_money_backtest as B # noqa: E402
|
| 38 |
+
|
| 39 |
+
DATA = Path(_os.environ.get("SLIP_BUNDLE_DIR", Path(__file__).resolve().parent.parent / "data"))
|
| 40 |
+
LOOKBACK_DAYS = 400 # β₯ 252 for momentum / 200 for regime / 252 for sector-RS
|
| 41 |
+
# the concentrated-manager panel written by manager_universe.py (refresh_everything step 7b)
|
| 42 |
+
CONC_DIR = Path(_os.environ.get("SLIP_CONC_DIR", QM / "data/research/conc/panels/CONC"))
|
| 43 |
+
PULL = Path(_os.environ.get("SLIP_PULL_DIR", QM / "data/raw/ftm_pull"))
|
| 44 |
+
ROA_KEEP = 0.30 # top 30% of each sector by ROA
|
| 45 |
+
# parity testing only: restrict to signal dates before this day / prices through this day
|
| 46 |
+
MAX_DATE = _os.environ.get("SLIP_EXPORT_BEFORE")
|
| 47 |
+
PRICES_THROUGH = _os.environ.get("SLIP_PRICES_THROUGH")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def consensus_frame(rows: pd.DataFrame) -> pd.DataFrame:
|
| 51 |
+
"""2Β·new + add β 2Β·exit among concentrated managers (scripts/slipstream/backtest.signal_frame 'consensus')."""
|
| 52 |
+
df = rows.copy()
|
| 53 |
+
col = lambda c: df[c].fillna(0.0) if c in df.columns else pd.Series(0.0, index=df.index)
|
| 54 |
+
df["score"] = 2 * col("guru_new_buys") + col("guru_adds") - 2 * col("guru_exits")
|
| 55 |
+
df["ok"] = df["score"] > 0
|
| 56 |
+
df["size"] = col("guru_conv_value").clip(lower=0)
|
| 57 |
+
return df
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def roa_gate(d: pd.Timestamp, universe: list, sector: dict) -> set:
|
| 61 |
+
"""Names whose latest public ROA is in the top ROA_KEEP of their sector across `universe`
|
| 62 |
+
(scripts/slipstream/backtest.Data.roa_pct / quality_set 'roa_sector')."""
|
| 63 |
+
import duckdb
|
| 64 |
+
f = duckdb.connect().execute(f"""
|
| 65 |
+
SELECT ticker, CAST(datekey AS DATE) AS dk, TRY_CAST(roa AS DOUBLE) AS roa
|
| 66 |
+
FROM read_csv_auto('{PULL / "SF1.csv"}', header=true, all_varchar=true)
|
| 67 |
+
WHERE dimension = 'ART' AND datekey IS NOT NULL AND TRY_CAST(roa AS DOUBLE) IS NOT NULL
|
| 68 |
+
AND CAST(datekey AS DATE) >= DATE '2012-06-01' AND CAST(datekey AS DATE) <= DATE '{d.date()}'""").df()
|
| 69 |
+
f["dk"] = pd.to_datetime(f["dk"])
|
| 70 |
+
f = f[f["ticker"].isin(universe)].sort_values("dk")
|
| 71 |
+
x = f.groupby("ticker")["roa"].last().to_frame()
|
| 72 |
+
x["sector"] = [sector.get(t, "?") for t in x.index]
|
| 73 |
+
x["pct"] = x.groupby("sector")["roa"].rank(pct=True)
|
| 74 |
+
return set(x.index[x["pct"] > 1 - ROA_KEEP])
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def main():
|
| 78 |
+
DATA.mkdir(parents=True, exist_ok=True)
|
| 79 |
+
B.PANEL, B.PRICES = CONC_DIR / "ftm_panel.parquet", CONC_DIR / "ftm_prices.parquet"
|
| 80 |
+
B.DAILY, B.SF1, B.TICKERS = PULL / "DAILY.csv", PULL / "SF1.csv", PULL / "TICKERS.csv"
|
| 81 |
+
if not B.PANEL.exists():
|
| 82 |
+
raise SystemExit(f"missing {B.PANEL} β run scripts/slipstream/manager_universe.py --sets CONC first")
|
| 83 |
+
base_panel = QM / "data/research/ftm_panel.parquet"
|
| 84 |
+
if not _os.environ.get("SLIP_CONC_DIR") and base_panel.exists() and B.PANEL.stat().st_mtime < base_panel.stat().st_mtime:
|
| 85 |
+
# the concentrated-manager panel must be rebuilt AFTER the base panel in the same run,
|
| 86 |
+
# or the bundle would ship last week's 13F signals as if fresh
|
| 87 |
+
raise SystemExit(f"stale {B.PANEL}: older than {base_panel} β the conc_panel step did not run")
|
| 88 |
+
panel, R, spy, sector, wide = B.load()
|
| 89 |
+
if PRICES_THROUGH:
|
| 90 |
+
wide = wide.loc[:PRICES_THROUGH]
|
| 91 |
+
R = R.loc[:PRICES_THROUGH]
|
| 92 |
+
valid = set(R.columns)
|
| 93 |
+
bdr = {pd.Timestamp(d): g for d, g in panel.groupby("date")}
|
| 94 |
+
# Use the LATEST signal rows in the panel (weekly cadence), not only monthly firsts.
|
| 95 |
+
# The monthly-only pick made every live rebalance trade the prior month's signal row,
|
| 96 |
+
# which β because 13F filings land mid-quarter (45-day deadline) β lagged the fresh
|
| 97 |
+
# quarter by a whole cycle: on 2026-09-01 the fund would have traded the Aug-3 row
|
| 98 |
+
# built on Q1 13F, while the Aug-17 weekly row already carried Q2. The backtest's PIT
|
| 99 |
+
# semantics assume signals as-of the rebalance date, so freshest-usable matches the
|
| 100 |
+
# validated rule more closely, not less. Only the last few dates are screened
|
| 101 |
+
# (universe/quality checks hit the multi-GB DAILY/SF1 CSVs per date).
|
| 102 |
+
dts = sorted(pd.to_datetime(panel["date"]).unique())
|
| 103 |
+
if MAX_DATE:
|
| 104 |
+
dts = [x for x in dts if pd.Timestamp(x) < pd.Timestamp(MAX_DATE)]
|
| 105 |
+
dts = dts[-6:]
|
| 106 |
+
uni = B.mktcap_universe(dts, 1000)
|
| 107 |
+
|
| 108 |
+
# latest rebalance date with a usable book
|
| 109 |
+
d = next((pd.Timestamp(c) for c in reversed(dts)
|
| 110 |
+
if bdr.get(pd.Timestamp(c)) is not None and (valid & uni.get(pd.Timestamp(c), set()))), None)
|
| 111 |
+
if d is None:
|
| 112 |
+
raise SystemExit("no valid rebalance date")
|
| 113 |
+
universe = sorted(uni.get(d, set()) & valid)
|
| 114 |
+
qual = roa_gate(d, universe, sector)
|
| 115 |
+
|
| 116 |
+
# candidates: eligible names with a consensus BUY among concentrated managers at d
|
| 117 |
+
sf = consensus_frame(bdr[d])
|
| 118 |
+
elig = valid & set(universe) & qual
|
| 119 |
+
cand = sf[sf["ok"] & sf["ticker"].isin(elig)].copy()
|
| 120 |
+
cand["sector"] = cand["ticker"].map(lambda t: sector.get(t, "?"))
|
| 121 |
+
cand = cand[["ticker", "sector", "score", "size"]].reset_index(drop=True)
|
| 122 |
+
cand.to_parquet(DATA / "candidates.parquet", index=False)
|
| 123 |
+
|
| 124 |
+
# prices: universe + SPY, last LOOKBACK_DAYS trading days β long form
|
| 125 |
+
cols = [t for t in (set(universe) | {"SPY"} | set(cand["ticker"])) if t in wide.columns]
|
| 126 |
+
px = wide[cols].tail(LOOKBACK_DAYS)
|
| 127 |
+
long = px.reset_index().melt(id_vars=px.index.name or "index", var_name="ticker",
|
| 128 |
+
value_name="px").dropna()
|
| 129 |
+
long.columns = ["date", "ticker", "px"]
|
| 130 |
+
long.to_parquet(DATA / "prices.parquet", index=False)
|
| 131 |
+
|
| 132 |
+
meta = {
|
| 133 |
+
"fund": "Slipstream",
|
| 134 |
+
"rule_version": "v2-2026-10 Β· CONC consensus Β· ROA top-30% in sector Β· 10% cap",
|
| 135 |
+
"as_of": str(d.date()),
|
| 136 |
+
"universe": universe,
|
| 137 |
+
"sectors": {t: sector.get(t, "?") for t in cols},
|
| 138 |
+
"n_candidates": int(len(cand)),
|
| 139 |
+
"price_start": str(px.index.min().date()),
|
| 140 |
+
"price_end": str(px.index.max().date()),
|
| 141 |
+
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
|
| 142 |
+
}
|
| 143 |
+
(DATA / "meta.json").write_text(json.dumps(meta, indent=1))
|
| 144 |
+
print(f"β bundle in {DATA}")
|
| 145 |
+
print(f" as_of {meta['as_of']} Β· {len(cand)} candidates Β· {len(universe)} universe Β· "
|
| 146 |
+
f"prices {meta['price_start']}β{meta['price_end']} ({len(long):,} rows)")
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
main()
|
risk_screen.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
The 20 holdings are chosen by the systematic quant rule. This overlay then scans the candidate
|
| 4 |
names for RECENT, CONCRETE, MATERIALLY-ADVERSE EVENTS the price/fundamentals may not have caught
|
| 5 |
yet β legal, regulatory/FDA, geopolitical/trade, operational, competitive β and HARD-EXCLUDES any
|
| 6 |
-
with a
|
| 7 |
discrete event backed by a real headline, so ongoing/priced risks (general competition, valuation)
|
| 8 |
can never trip it. Excluded names are replaced deterministically by the next-ranked candidate.
|
| 9 |
|
|
@@ -23,6 +23,9 @@ MAX_EXCLUSIONS = int(os.getenv("SLIPSTREAM_MAX_EXCLUSIONS", "3"))
|
|
| 23 |
ENABLED = os.getenv("SLIPSTREAM_RISK_SCREEN", "1") == "1"
|
| 24 |
CACHE = config.DATA_DIR / "risk_screen_cache.json"
|
| 25 |
_SEV_RANK = {"none": 0, "medium": 1, "high": 2, "critical": 3}
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# Deterministic backstop for the prompt's "analyst actions are never events" rule β the model
|
| 28 |
# still returned `NFLX / medium / "Analyst target price cut and downgradeβ¦"` (2026-09), citing a
|
|
@@ -63,6 +66,11 @@ approximate market capitalization):
|
|
| 63 |
antitrust filings historically resolve over years without impairing 12-month returns.
|
| 64 |
β’ Adverse VERDICTS, injunctions, consent decrees, criminal indictments, or forced structural
|
| 65 |
changes CAN be "high" regardless of company size.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
β’ If headlines show the stock already fell only modestly (under ~5%) on the news, the market has
|
| 67 |
likely priced it β lean "medium" unless the event meets the tests above.
|
| 68 |
|
|
@@ -112,7 +120,7 @@ def screen(pool: list[str]) -> dict:
|
|
| 112 |
"""Return {excluded: set, meta: {...}} for a candidate pool. Cached per UTC day; safe on failure."""
|
| 113 |
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
| 114 |
pool = [t for t in pool if t]
|
| 115 |
-
key = today + "|" + ",".join(sorted(pool))
|
| 116 |
if not (ENABLED and config.GEMINI_API_KEY):
|
| 117 |
return {"excluded": set(), "meta": {"enabled": False, "scanned": 0, "cleared": 0, "excluded": [], "as_of": today}}
|
| 118 |
cache = _load_cache()
|
|
@@ -127,7 +135,7 @@ def screen(pool: list[str]) -> dict:
|
|
| 127 |
flags = _classify(pool, news_by_tkr)
|
| 128 |
ranked = sorted(flags.items(), key=lambda kv: -_SEV_RANK.get(kv[1]["severity"], 0))
|
| 129 |
for t, f in ranked:
|
| 130 |
-
if f["severity"]
|
| 131 |
excluded_list.append({"ticker": t, **f})
|
| 132 |
except Exception as e:
|
| 133 |
# any failure (no news, LLM error, bad JSON) β no exclusions, fund still trades
|
|
@@ -136,8 +144,9 @@ def screen(pool: list[str]) -> dict:
|
|
| 136 |
return {"excluded": set(), "meta": meta}
|
| 137 |
|
| 138 |
meta = {"enabled": True, "scanned": len(pool), "cleared": len(pool) - len(excluded_list),
|
| 139 |
-
"excluded": excluded_list, "
|
| 140 |
-
|
|
|
|
| 141 |
"as_of": today}
|
| 142 |
try:
|
| 143 |
CACHE.write_text(json.dumps({"key": key, "meta": meta}))
|
|
|
|
| 3 |
The 20 holdings are chosen by the systematic quant rule. This overlay then scans the candidate
|
| 4 |
names for RECENT, CONCRETE, MATERIALLY-ADVERSE EVENTS the price/fundamentals may not have caught
|
| 5 |
yet β legal, regulatory/FDA, geopolitical/trade, operational, competitive β and HARD-EXCLUDES any
|
| 6 |
+
with a CRITICAL event only (since Oct 2026; "high" events are shown as watch flags and held). It is deliberately NOT a subjective "risk score": it only fires on a
|
| 7 |
discrete event backed by a real headline, so ongoing/priced risks (general competition, valuation)
|
| 8 |
can never trip it. Excluded names are replaced deterministically by the next-ranked candidate.
|
| 9 |
|
|
|
|
| 23 |
ENABLED = os.getenv("SLIPSTREAM_RISK_SCREEN", "1") == "1"
|
| 24 |
CACHE = config.DATA_DIR / "risk_screen_cache.json"
|
| 25 |
_SEV_RANK = {"none": 0, "medium": 1, "high": 2, "critical": 3}
|
| 26 |
+
# Only events at or above this severity are excluded. "critical" = very high risk only: the backtested
|
| 27 |
+
# rule has no overlay, so the live fund should deviate from it only for events that threaten the company.
|
| 28 |
+
EXCLUDE_AT = os.getenv("SLIPSTREAM_EXCLUDE_SEVERITY", "critical").lower()
|
| 29 |
|
| 30 |
# Deterministic backstop for the prompt's "analyst actions are never events" rule β the model
|
| 31 |
# still returned `NFLX / medium / "Analyst target price cut and downgradeβ¦"` (2026-09), citing a
|
|
|
|
| 66 |
antitrust filings historically resolve over years without impairing 12-month returns.
|
| 67 |
β’ Adverse VERDICTS, injunctions, consent decrees, criminal indictments, or forced structural
|
| 68 |
changes CAN be "high" regardless of company size.
|
| 69 |
+
β’ "critical" is reserved for VERY HIGH risk β an event that threatens the company's survival or a large
|
| 70 |
+
part of its value: bankruptcy / going-concern warning, fraud or accounting restatement, criminal
|
| 71 |
+
indictment of the company or its CEO/CFO, loss of the license to operate (e.g. a key drug's approval
|
| 72 |
+
withdrawn, delisting), sanctions/export bans cutting off a major share of revenue, or plausible
|
| 73 |
+
exposure above ~20% of market value. When in doubt between "high" and "critical", choose "high".
|
| 74 |
β’ If headlines show the stock already fell only modestly (under ~5%) on the news, the market has
|
| 75 |
likely priced it β lean "medium" unless the event meets the tests above.
|
| 76 |
|
|
|
|
| 120 |
"""Return {excluded: set, meta: {...}} for a candidate pool. Cached per UTC day; safe on failure."""
|
| 121 |
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
| 122 |
pool = [t for t in pool if t]
|
| 123 |
+
key = today + "|" + EXCLUDE_AT + "|" + ",".join(sorted(pool))
|
| 124 |
if not (ENABLED and config.GEMINI_API_KEY):
|
| 125 |
return {"excluded": set(), "meta": {"enabled": False, "scanned": 0, "cleared": 0, "excluded": [], "as_of": today}}
|
| 126 |
cache = _load_cache()
|
|
|
|
| 135 |
flags = _classify(pool, news_by_tkr)
|
| 136 |
ranked = sorted(flags.items(), key=lambda kv: -_SEV_RANK.get(kv[1]["severity"], 0))
|
| 137 |
for t, f in ranked:
|
| 138 |
+
if _SEV_RANK.get(f["severity"], 0) >= _SEV_RANK.get(EXCLUDE_AT, 3) and len(excluded_list) < MAX_EXCLUSIONS:
|
| 139 |
excluded_list.append({"ticker": t, **f})
|
| 140 |
except Exception as e:
|
| 141 |
# any failure (no news, LLM error, bad JSON) β no exclusions, fund still trades
|
|
|
|
| 144 |
return {"excluded": set(), "meta": meta}
|
| 145 |
|
| 146 |
meta = {"enabled": True, "scanned": len(pool), "cleared": len(pool) - len(excluded_list),
|
| 147 |
+
"excluded": excluded_list, "exclude_at": EXCLUDE_AT,
|
| 148 |
+
"flags_medium": [{"ticker": t, **f} for t, f in sorted(flags.items(), key=lambda kv: -_SEV_RANK.get(kv[1]["severity"], 0))
|
| 149 |
+
if 0 < _SEV_RANK.get(f["severity"], 0) < _SEV_RANK.get(EXCLUDE_AT, 3)][:5],
|
| 150 |
"as_of": today}
|
| 151 |
try:
|
| 152 |
CACHE.write_text(json.dumps({"key": key, "meta": meta}))
|
static/app.js
CHANGED
|
@@ -91,8 +91,9 @@ function renderRiskScreen(rs) {
|
|
| 91 |
(rows ? `<ul class="rs-list">${rows}</ul>` : "") +
|
| 92 |
(medRows ? `<div class="rs-sub">Monitoring (below exclusion threshold β held):</div><ul class="rs-list">${medRows}</ul>` : "") +
|
| 93 |
`<p class="rs-note">A second-stage AI overlay scans the final candidates for <b>concrete, recent adverse events</b> β
|
| 94 |
-
legal Β· regulatory/FDA Β· geopolitical/trade Β· operational Β· competitive β and hard-excludes
|
| 95 |
-
(replaced by the next-ranked name).
|
|
|
|
| 96 |
}
|
| 97 |
|
| 98 |
async function previewRebalance() {
|
|
|
|
| 91 |
(rows ? `<ul class="rs-list">${rows}</ul>` : "") +
|
| 92 |
(medRows ? `<div class="rs-sub">Monitoring (below exclusion threshold β held):</div><ul class="rs-list">${medRows}</ul>` : "") +
|
| 93 |
`<p class="rs-note">A second-stage AI overlay scans the final candidates for <b>concrete, recent adverse events</b> β
|
| 94 |
+
legal Β· regulatory/FDA Β· geopolitical/trade Β· operational Β· competitive β and hard-excludes only <b>critical</b> hits
|
| 95 |
+
(threats to the company itself; replaced by the next-ranked name). High and medium events are shown above and held.
|
| 96 |
+
Ongoing, already-priced risks are deliberately ignored.</p>`;
|
| 97 |
}
|
| 98 |
|
| 99 |
async function previewRebalance() {
|
static/index.html
CHANGED
|
@@ -85,54 +85,53 @@
|
|
| 85 |
<!-- ============ ABOUT / RESEARCH TAB ============ -->
|
| 86 |
<div id="tab-about" class="tab-panel">
|
| 87 |
<article class="doc">
|
| 88 |
-
<p class="doc-lede">Slipstream is a quality, trend-adaptive equity paper fund that <b>drafts the smart money</b> β it holds
|
| 89 |
|
| 90 |
<h2 class="doc-h">1 Β· Strategy & approach</h2>
|
| 91 |
-
<p>The core question
|
| 92 |
<table class="doc-table">
|
| 93 |
<tbody>
|
| 94 |
-
<tr><td class="k">
|
| 95 |
-
<tr><td class="k">
|
| 96 |
-
<tr><td class="k">
|
| 97 |
-
<tr><td class="k">
|
| 98 |
-
<tr><td class="k">
|
| 99 |
-
<tr><td class="k">
|
| 100 |
-
<tr><td class="k">
|
| 101 |
-
<tr><td class="k">
|
|
|
|
| 102 |
</tbody>
|
| 103 |
</table>
|
| 104 |
-
<p class="doc-note">Why these pieces:
|
| 105 |
-
<p class="doc-note"><b>On the AI overlay β deliberately narrow.</b> Selection stays 100% systematic; the LLM is <em>not</em> a stock-picker
|
| 106 |
-
<em>relative to company size</em>: a lawsuit filing claiming ~1% of a mega-cap's value is monitored, not excluded β exclusion needs plausible exposure above ~5% of market value, a threat to the core
|
| 107 |
-
business model, or an actual verdict/indictment (added Sep-2026 after the screen over-reacted to an FTC filing against Amazon that the market priced at β2.5%). For a full qualitative read on any holding, the sister app <b>Crucible</b> runs a 4-model analyst debate on demand.</p>
|
| 108 |
|
| 109 |
<h2 class="doc-h">2 Β· Backtest results</h2>
|
| 110 |
-
<p>Survivorship-free backtest, 2014
|
| 111 |
<div class="doc-kpis">
|
| 112 |
-
<div class="dk"><span>CAGR</span><b>19.
|
| 113 |
-
<div class="dk"><span>Excess / yr</span><b class="pos">+5.
|
| 114 |
-
<div class="dk"><span>Sharpe</span><b>0.
|
| 115 |
-
<div class="dk"><span>Sortino</span><b>1.
|
| 116 |
-
<div class="dk"><span>Alpha (ann)</span><b class="pos">+
|
| 117 |
-
<div class="dk"><span>Max drawdown</span><b class="neg">
|
| 118 |
-
<div class="dk"><span>Years beat SPY</span><b>
|
| 119 |
-
<div class="dk"><span>In momentum</span><b>
|
| 120 |
</div>
|
| 121 |
<figure class="doc-chart">
|
| 122 |
<div class="dc-head"><span class="dc-title">Growth of $100 β Slipstream vs SPY</span>
|
| 123 |
-
<span class="dc-leg"><i class="lg-fund"></i> Slipstream <i class="lg-spy" style="margin-left:12px"></i> SPY <span class="faint" style="margin-left:12px">2014
|
| 124 |
-
<svg viewBox="0 0 1000 300" class="dc-svg" preserveAspectRatio="none" role="img" aria-label="Growth of $100, Slipstream versus SPY, log scale, 2014 to 2026"><defs><linearGradient id="dcg" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#37b8a4" stop-opacity="0.14"/><stop offset="1" stop-color="#37b8a4" stop-opacity="0"/></linearGradient></defs><line x1="48" y1="264.6" x2="986" y2="264.6" stroke="#1e2c42"/><text x="41" y="267.6" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">100</text><line x1="48" y1="220.4" x2="986" y2="220.4" stroke="#1e2c42"/><text x="41" y="223.4" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">150</text><line x1="48" y1="189.
|
| 125 |
</figure>
|
| 126 |
<table class="doc-table wide">
|
| 127 |
-
<thead><tr><th>
|
| 128 |
<tbody>
|
| 129 |
-
<tr><td>2014β2019 (calm bull)</td><td>
|
| 130 |
-
<tr><td>2020β2022 (COVID + rate shock)</td><td
|
| 131 |
-
<tr><td>2023β2026 (AI mega-cap rally)</td><td
|
| 132 |
</tbody>
|
| 133 |
</table>
|
| 134 |
-
<p class="doc-note">
|
| 135 |
-
<p class="doc-warn"><b>Read honestly.</b> This is a <em>simulated backtest</em>, not a live track record
|
| 136 |
|
| 137 |
<h2 class="doc-h">3 Β· Everything we explored</h2>
|
| 138 |
<p>We stress-tested the idea from every angle before settling on the rule above. Each row is a real backtest; the pattern that emerged: <b>every "buy it cheap / beaten-down" tilt hurt, and every "quality / large-cap / diversify / adapt" control helped.</b></p>
|
|
@@ -151,14 +150,21 @@
|
|
| 151 |
<tr><td>Momentum factor (in the 2020β22 window)</td><td>The one thing that beat the fund <em>and</em> SPY in that stretch β because it's regime-adaptive</td><td class="pos">β informed overlay</td></tr>
|
| 152 |
<tr><td><b>Regime switch β momentum (200DMA, confirmed)</b></td><td>Fixes 2020β22 (β2.8% β +6.0%), lifts full-period excess to +5.0%, same drawdown</td><td class="pos">β adopted</td></tr>
|
| 153 |
<tr><td>Multi-signal regime consensus (5 signals, K-of-N)</td><td>No better than the simple single 200DMA filter; more knobs = more overfitting risk</td><td class="warn">~ kept it simple</td></tr>
|
| 154 |
-
<tr><td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
</tbody>
|
| 156 |
</table>
|
| 157 |
<p class="doc-note">A recurring lesson worth keeping: the flashiest backtest numbers came from the configs taking the most <em>uncompensated</em> risk (concentration, single regime). Each time we tightened the methodology β survivorship, costs, sub-period robustness, sector control, multiple-testing thresholds β the "edge" shrank toward the market. Slipstream is what was left standing after all of that.</p>
|
| 158 |
|
| 159 |
<h2 class="doc-h">4 Β· Data & honesty notes</h2>
|
| 160 |
<ul class="doc-list">
|
| 161 |
-
<li><b>Data:</b> SEC 13F (Sharadar SF3
|
| 162 |
<li><b>Signal freshness:</b> 13F is quarterly and ~45 days lagged by law β so the smart-money signal is inherently stale; the fund is built to work <em>despite</em> that, not to pretend otherwise.</li>
|
| 163 |
<li><b>Picks panels</b> are informational and LLM-screened for manipulation/pump patterns; they are <em>not</em> individual buy recommendations. The fund trades a disciplined, risk-controlled subset automatically.</li>
|
| 164 |
<li><b>Paper only.</b> Slipstream runs on an Alpaca paper account. Nothing here is investment advice.</li>
|
|
@@ -174,13 +180,13 @@
|
|
| 174 |
asserts every dataset actually advanced β a stale input fails the run loudly instead of silently
|
| 175 |
trading on old data.</td></tr>
|
| 176 |
<tr><td><b>Monthly rebalance</b> (1st)</td><td>Trades the freshest weekly signal row through the
|
| 177 |
-
construction rules above (quality gate β AI event screen β
|
| 178 |
sector caps). Idempotent β a re-run trades only target-minus-actual.</td></tr>
|
| 179 |
<tr><td><b>Daily supervisor</b> (weekdays)</td><td>A deterministic health monitor β it <em>never
|
| 180 |
trades</em> β checking three things: <b>tracking</b> (trailing 4-week fund-vs-SPY gap; WATCH <β3pp,
|
| 181 |
ALERT <β6pp), <b>guardrails</b> (paper-only invariant, no margin, exposure β€100.5%, and position
|
| 182 |
-
drift β the
|
| 183 |
-
above
|
| 184 |
the OK / WATCH / ALERT chips at the top of the Fund tab, and an ALERT fails the scheduled run so it
|
| 185 |
can't be missed.</td></tr>
|
| 186 |
</tbody>
|
|
|
|
| 85 |
<!-- ============ ABOUT / RESEARCH TAB ============ -->
|
| 86 |
<div id="tab-about" class="tab-panel">
|
| 87 |
<article class="doc">
|
| 88 |
+
<p class="doc-lede">Slipstream is a quality, trend-adaptive equity paper fund that <b>drafts the smart money</b> β it holds the large-caps that concentrated, high-conviction 13F managers are collectively buying, keeps only the most profitable companies in each sector, caps any one stock at 10%, and rotates into momentum when the market trend breaks. What follows is the rule, the backtest, and an honest account of everything we tried on the way here.</p>
|
| 89 |
|
| 90 |
<h2 class="doc-h">1 Β· Strategy & approach</h2>
|
| 91 |
+
<p>The core question: does <em>following the smart money</em> actually work? Our October 2026 re-validation, run from a fresh data pull, found that the original rule (curated super-investors + insiders, ROE/debt quality gate) had <b>no demonstrated edge</b> over SPY. Eight pre-registered follow-up studies then identified what does carry information: <b>breadth of conviction buying among concentrated managers</b>, filtered for <b>profitability within each sector</b>. The rule (v2, from November 2026), applied monthly:</p>
|
| 92 |
<table class="doc-table">
|
| 93 |
<tbody>
|
| 94 |
+
<tr><td class="k">Managers</td><td>Every 13F filer that behaves like a concentrated stock-picker <em>that quarter</em>: 10β150 positions, $250Mβ$100B reported equity, β₯40% in its top 10, and a filing the previous quarter. Rule-based, ~900β1,500 managers a quarter β new managers join automatically, nobody curates the list</td></tr>
|
| 95 |
+
<tr><td class="k">Signal</td><td><b>Consensus:</b> for each stock, 2 Γ managers opening a conviction position + managers adding β 2 Γ managers exiting (a conviction position is β₯$1M and β₯0.5% of the manager's portfolio). Buy candidates have a positive count. 13F only β insider buying is no longer used</td></tr>
|
| 96 |
+
<tr><td class="k">Universe</td><td>Top ~1,000 US stocks by market cap, point-in-time</td></tr>
|
| 97 |
+
<tr><td class="k">Quality</td><td>Return on assets in the <b>top 30% of its own sector</b> (latest filing public at the time) β so every sector contributes its most profitable companies, and banks or utilities aren't screened out wholesale</td></tr>
|
| 98 |
+
<tr><td class="k">Construction</td><td>Top 20 by consensus, weighted by the dollars managers committed, each stock <b>1β10%</b>, at most 5 per sector</td></tr>
|
| 99 |
+
<tr><td class="k">AI event-risk overlay</td><td>A second-stage AI screen reads recent news on the final candidates and <b>hard-excludes only critical events</b> β threats to the company itself: going-concern / bankruptcy, fraud or restatement, criminal indictment, loss of the license to operate, sanctions cutting off a major share of revenue. Lesser events are shown and held. Excluded names are replaced by the next-ranked candidate</td></tr>
|
| 100 |
+
<tr><td class="k">Dynamic sector cap</td><td>Each sector capped at 30% <b>Β± its 12-1m relative strength vs SPY</b> (band 15β45%)</td></tr>
|
| 101 |
+
<tr><td class="k">Regime overlay</td><td>When SPY is in a <b>confirmed downtrend</b> (below its 200-day MA, confirmed a month earlier), the whole book rotates into the 50 large-caps with the strongest 12-month momentum</td></tr>
|
| 102 |
+
<tr><td class="k">Rebalance</td><td>Monthly (~3.5Γ one-way turnover a year), 1% cash buffer, 0.5% trade deadband</td></tr>
|
| 103 |
</tbody>
|
| 104 |
</table>
|
| 105 |
+
<p class="doc-note">Why these pieces: counting <em>how many</em> conviction managers are buying works better than following any curated few β no 13F manager's individual record proved persistent enough to pick on. The within-sector ROA filter is the single biggest improvement we found; it lifted every manager list we tested. The 10% stock cap keeps the same risk-adjusted return as 15% with a less concentrated book. The regime overlay is what carries the fund through bear markets.</p>
|
| 106 |
+
<p class="doc-note"><b>On the AI overlay β deliberately narrow.</b> Selection stays 100% systematic; the LLM is <em>not</em> a stock-picker. Because no backtest can include it, it now fires <b>only on critical events</b>, so the live fund deviates from the tested rule only when a company itself is under threat. It is capped (β€3 exclusions/rebalance), every exclusion cites its headline, and if the news feed or model is unavailable the fund trades the pure quant book unchanged.</p>
|
|
|
|
|
|
|
| 107 |
|
| 108 |
<h2 class="doc-h">2 Β· Backtest results</h2>
|
| 109 |
+
<p>Survivorship-free backtest, 2014-03 β 2026-10, rebuilt from a fresh Sharadar pull (4,323 delisted names included; delisting returns from corporate actions). Trades use only data available the day before, net of 20 bp per side, 1% cash; Sharpe and Sortino are net of T-bills. The AI overlay is not included (it can't be backtested).</p>
|
| 110 |
<div class="doc-kpis">
|
| 111 |
+
<div class="dk"><span>CAGR</span><b>19.9%</b><small>SPY 13.9%</small></div>
|
| 112 |
+
<div class="dk"><span>Excess / yr</span><b class="pos">+5.9%</b><small>vs SPY</small></div>
|
| 113 |
+
<div class="dk"><span>Sharpe</span><b>0.85</b><small>SPY 0.73</small></div>
|
| 114 |
+
<div class="dk"><span>Sortino</span><b>1.04</b><small></small></div>
|
| 115 |
+
<div class="dk"><span>Alpha (ann)</span><b class="pos">+3.9%</b><small>t 1.8 Β· Ξ² 1.17</small></div>
|
| 116 |
+
<div class="dk"><span>Max drawdown</span><b class="neg">-37.5%</b><small>SPY -33.7%</small></div>
|
| 117 |
+
<div class="dk"><span>Years beat SPY</span><b>6 / 11</b><small>2015β2025</small></div>
|
| 118 |
+
<div class="dk"><span>In momentum</span><b>24%</b><small>of months</small></div>
|
| 119 |
</div>
|
| 120 |
<figure class="doc-chart">
|
| 121 |
<div class="dc-head"><span class="dc-title">Growth of $100 β Slipstream vs SPY</span>
|
| 122 |
+
<span class="dc-leg"><i class="lg-fund"></i> Slipstream <i class="lg-spy" style="margin-left:12px"></i> SPY <span class="faint" style="margin-left:12px">2014-03 β 2026-10 Β· net of cost Β· log scale</span></span></div>
|
| 123 |
+
<svg viewBox="0 0 1000 300" class="dc-svg" preserveAspectRatio="none" role="img" aria-label="Growth of $100, Slipstream versus SPY, log scale, March 2014 to October 2026"><defs><linearGradient id="dcg" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#37b8a4" stop-opacity="0.14"/><stop offset="1" stop-color="#37b8a4" stop-opacity="0"/></linearGradient></defs><line x1="48" y1="264.6" x2="986" y2="264.6" stroke="#1e2c42"/><text x="41" y="267.6" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">100</text><line x1="48" y1="220.4" x2="986" y2="220.4" stroke="#1e2c42"/><text x="41" y="223.4" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">150</text><line x1="48" y1="189.0" x2="986" y2="189.0" stroke="#1e2c42"/><text x="41" y="192.0" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">200</text><line x1="48" y1="144.8" x2="986" y2="144.8" stroke="#1e2c42"/><text x="41" y="147.8" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">300</text><line x1="48" y1="89.2" x2="986" y2="89.2" stroke="#1e2c42"/><text x="41" y="92.2" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">500</text><line x1="48" y1="52.5" x2="986" y2="52.5" stroke="#1e2c42"/><text x="41" y="55.5" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">700</text><line x1="48" y1="13.6" x2="986" y2="13.6" stroke="#1e2c42"/><text x="41" y="16.6" fill="#5d6b83" font-size="10" text-anchor="end" font-family="monospace">1000</text><polygon points="48,274 48.0,264.8 53.7,264.8 59.8,261.4 66.1,257.8 72.1,257.6 78.4,257.2 84.7,254.0 90.8,254.1 97.1,250.3 103.2,245.6 109.5,247.4 115.7,251.1 121.4,243.9 127.7,247.0 133.8,244.6 140.1,244.4 146.2,250.1 152.4,246.1 158.7,253.3 164.8,259.4 171.1,257.4 177.2,255.1 183.5,257.5 189.8,267.2 195.6,268.9 201.9,261.7 208.0,261.5 214.3,260.4 220.4,263.5 226.7,259.4 233.0,256.7 239.0,255.4 245.3,257.1 251.4,257.3 257.7,256.3 264.0,252.2 269.7,246.2 276.0,244.4 282.0,240.3 288.3,236.1 294.4,235.8 300.7,232.3 307.0,231.4 313.1,229.8 319.4,224.4 325.4,222.8 331.7,222.3 338.0,213.0 343.7,216.3 350.0,219.3 356.1,218.7 362.4,211.0 368.4,210.8 374.7,209.9 381.0,208.0 387.1,207.6 393.4,215.5 399.5,217.0 405.8,228.2 412.0,214.2 417.7,208.2 424.0,207.3 430.1,200.7 436.4,208.4 442.5,202.6 448.8,199.9 455.0,199.6 461.1,199.8 467.4,196.1 473.5,190.8 479.8,186.6 486.1,184.9 492.0,190.6 498.2,210.7 504.3,192.7 510.6,183.7 516.7,173.6 523.0,167.4 529.3,155.5 535.4,159.8 541.6,161.6 547.7,154.8 554.0,152.3 560.3,153.7 566.0,152.1 572.3,150.0 578.4,142.7 584.6,144.7 590.7,138.3 597.0,135.5 603.3,133.2 609.4,139.3 615.7,131.5 621.8,130.4 628.0,127.0 634.3,131.7 640.0,140.9 646.3,136.0 652.4,144.7 658.7,137.4 664.7,154.6 671.0,145.0 677.3,142.7 683.4,156.8 689.7,141.6 695.8,142.1 702.1,151.0 708.4,147.7 714.0,154.6 720.3,144.1 726.4,143.0 732.7,136.5 738.8,131.0 745.1,125.9 751.3,127.1 757.4,131.3 763.7,132.7 769.8,118.1 776.1,112.7 782.4,109.6 788.3,101.8 794.5,100.2 800.6,102.4 806.9,94.8 813.0,87.9 819.3,90.1 825.6,84.5 831.7,79.0 837.9,78.3 844.0,75.6 850.3,77.3 856.6,70.7 862.3,72.0 868.6,80.8 874.7,77.0 880.9,66.5 887.0,57.3 893.3,56.2 899.6,54.7 905.7,49.8 912.0,46.2 918.1,45.1 924.3,46.4 930.6,46.5 936.3,49.4 942.6,55.8 948.7,35.9 955.0,20.4 961.1,27.2 967.3,22.4 973.6,19.6 979.7,17.5 986.0,16.5 986,274" fill="url(#dcg)"/><polyline points="48.0,264.6 53.7,262.9 59.8,262.2 66.1,259.7 72.1,257.4 78.4,258.9 84.7,254.7 90.8,256.2 97.1,253.7 103.2,250.7 109.5,251.0 115.7,254.3 121.4,248.3 127.7,250.1 133.8,249.0 140.1,247.6 146.2,249.8 152.4,247.4 158.7,254.2 164.8,257.1 171.1,248.2 177.2,247.8 183.5,249.6 189.8,255.2 195.6,255.3 201.9,248.2 208.0,247.8 214.3,245.9 220.4,245.6 226.7,241.7 233.0,241.5 239.0,241.5 245.3,243.4 251.4,239.5 257.7,237.3 264.0,235.4 269.7,231.2 276.0,231.0 282.0,229.9 288.3,228.4 294.4,227.7 300.7,225.5 307.0,225.2 313.1,223.0 319.4,220.5 325.4,217.2 331.7,215.9 338.0,209.9 343.7,213.9 350.0,217.0 356.1,216.4 362.4,213.8 368.4,213.2 374.7,209.2 381.0,205.8 387.1,205.1 393.4,212.9 399.5,210.9 405.8,221.0 412.0,212.6 417.7,209.1 424.0,207.1 430.1,202.8 436.4,210.0 442.5,202.6 448.8,201.0 455.0,202.8 461.1,200.7 467.4,198.3 473.5,194.5 479.8,191.3 486.1,191.4 492.0,200.4 498.2,214.9 504.3,201.9 510.6,196.8 516.7,194.9 523.0,188.6 529.3,181.3 535.4,185.4 541.6,188.2 547.7,176.9 554.0,173.0 560.3,174.1 566.0,171.1 572.3,166.2 578.4,160.6 584.6,159.9 590.7,157.5 597.0,154.9 603.3,151.7 609.4,156.9 615.7,149.5 621.8,150.4 628.0,145.4 634.3,151.3 640.0,154.6 646.3,150.6 652.4,160.6 658.7,160.3 664.7,169.7 671.0,160.1 677.3,164.7 683.4,175.2 689.7,166.7 695.8,160.8 702.1,167.3 708.4,160.6 714.0,163.4 720.3,159.4 726.4,157.7 732.7,157.2 738.8,150.4 745.1,146.8 751.3,148.6 757.4,153.9 763.7,156.3 769.8,146.8 776.1,141.9 782.4,140.2 788.3,134.7 794.5,131.1 800.6,135.6 806.9,130.3 813.0,126.5 819.3,125.2 825.6,122.6 831.7,120.4 837.9,121.4 844.0,115.0 850.3,117.7 856.6,114.8 862.3,116.2 868.6,122.5 874.7,123.4 880.9,116.8 887.0,111.3 893.3,108.8 899.6,106.6 905.7,102.8 912.0,100.2 918.1,100.0 924.3,99.9 930.6,98.3 936.3,99.3 942.6,104.8 948.7,93.9 955.0,88.3 961.1,89.4 967.3,89.4 973.6,86.5 979.7,86.9 986.0,85.9" fill="none" stroke="#5d6b83" stroke-width="1.6" stroke-dasharray="4 3"/><polyline points="48.0,264.8 53.7,264.8 59.8,261.4 66.1,257.8 72.1,257.6 78.4,257.2 84.7,254.0 90.8,254.1 97.1,250.3 103.2,245.6 109.5,247.4 115.7,251.1 121.4,243.9 127.7,247.0 133.8,244.6 140.1,244.4 146.2,250.1 152.4,246.1 158.7,253.3 164.8,259.4 171.1,257.4 177.2,255.1 183.5,257.5 189.8,267.2 195.6,268.9 201.9,261.7 208.0,261.5 214.3,260.4 220.4,263.5 226.7,259.4 233.0,256.7 239.0,255.4 245.3,257.1 251.4,257.3 257.7,256.3 264.0,252.2 269.7,246.2 276.0,244.4 282.0,240.3 288.3,236.1 294.4,235.8 300.7,232.3 307.0,231.4 313.1,229.8 319.4,224.4 325.4,222.8 331.7,222.3 338.0,213.0 343.7,216.3 350.0,219.3 356.1,218.7 362.4,211.0 368.4,210.8 374.7,209.9 381.0,208.0 387.1,207.6 393.4,215.5 399.5,217.0 405.8,228.2 412.0,214.2 417.7,208.2 424.0,207.3 430.1,200.7 436.4,208.4 442.5,202.6 448.8,199.9 455.0,199.6 461.1,199.8 467.4,196.1 473.5,190.8 479.8,186.6 486.1,184.9 492.0,190.6 498.2,210.7 504.3,192.7 510.6,183.7 516.7,173.6 523.0,167.4 529.3,155.5 535.4,159.8 541.6,161.6 547.7,154.8 554.0,152.3 560.3,153.7 566.0,152.1 572.3,150.0 578.4,142.7 584.6,144.7 590.7,138.3 597.0,135.5 603.3,133.2 609.4,139.3 615.7,131.5 621.8,130.4 628.0,127.0 634.3,131.7 640.0,140.9 646.3,136.0 652.4,144.7 658.7,137.4 664.7,154.6 671.0,145.0 677.3,142.7 683.4,156.8 689.7,141.6 695.8,142.1 702.1,151.0 708.4,147.7 714.0,154.6 720.3,144.1 726.4,143.0 732.7,136.5 738.8,131.0 745.1,125.9 751.3,127.1 757.4,131.3 763.7,132.7 769.8,118.1 776.1,112.7 782.4,109.6 788.3,101.8 794.5,100.2 800.6,102.4 806.9,94.8 813.0,87.9 819.3,90.1 825.6,84.5 831.7,79.0 837.9,78.3 844.0,75.6 850.3,77.3 856.6,70.7 862.3,72.0 868.6,80.8 874.7,77.0 880.9,66.5 887.0,57.3 893.3,56.2 899.6,54.7 905.7,49.8 912.0,46.2 918.1,45.1 924.3,46.4 930.6,46.5 936.3,49.4 942.6,55.8 948.7,35.9 955.0,20.4 961.1,27.2 967.3,22.4 973.6,19.6 979.7,17.5 986.0,16.5" fill="none" stroke="#37b8a4" stroke-width="2.4"/><circle cx="986.0" cy="16.5" r="3.5" fill="#37b8a4"/><text x="982.0" y="8.5" fill="#3ed88f" font-size="11" text-anchor="end" font-family="monospace">$973</text><text x="982.0" y="99.9" fill="#8695ad" font-size="11" text-anchor="end" font-family="monospace">$515</text><text x="109.7" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2015</text><text x="257.9" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2017</text><text x="406.0" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2019</text><text x="554.2" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2021</text><text x="702.3" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2023</text><text x="850.5" y="294" fill="#5d6b83" font-size="10" text-anchor="middle" font-family="monospace">2025</text></svg>
|
| 124 |
</figure>
|
| 125 |
<table class="doc-table wide">
|
| 126 |
+
<thead><tr><th>Period</th><th>Fund CAGR</th><th>SPY</th><th>Excess</th><th>Sharpe (SPY)</th></tr></thead>
|
| 127 |
<tbody>
|
| 128 |
+
<tr><td>2014β2019 (calm bull)</td><td>13.1%</td><td>12.2%</td><td class="pos">+0.9%</td><td>0.77 (0.88)</td></tr>
|
| 129 |
+
<tr><td>2020β2022 (COVID + rate shock)</td><td>11.5%</td><td>7.6%</td><td class="pos">+3.9%</td><td>0.49 (0.39)</td></tr>
|
| 130 |
+
<tr><td>2023β2026 (AI mega-cap rally)</td><td>39.1%</td><td>22.1%</td><td class="pos">+17.0%</td><td>1.46 (1.11)</td></tr>
|
| 131 |
</tbody>
|
| 132 |
</table>
|
| 133 |
+
<p class="doc-note">How the rule was chosen: settings were selected on 2016-03 β 2021-12 only (Sharpe 1.08 vs SPY 1.00). 2022 onward was then reviewed across several studies, so it is not a clean out-of-sample test (Sharpe 0.86 vs 0.52). The two years before selection, 2014-03 β 2016-02, were never used β and the fund <b>trailed SPY</b> there (β1.8% vs +4.4% a year, hurt by risk-off whipsaws in 2015β16).</p>
|
| 134 |
+
<p class="doc-warn"><b>Read honestly.</b> This is a <em>simulated backtest</em>, not a live track record, and it is the best of ~210 configurations tested β so expect live results below it. Over the full period the alpha is positive but only borderline significant (t 1.8); the fund carries more market risk than SPY (Ξ² 1.17, volatility ~22%, drawdowns to -37.5%); roughly half the book is usually tech-related; and much of the excess return came after 2023. Candidate rules are tracked in a forward "shadow book" whose books are recorded before their returns exist β that record, not this chart, is the real test. Not investment advice.</p>
|
| 135 |
|
| 136 |
<h2 class="doc-h">3 Β· Everything we explored</h2>
|
| 137 |
<p>We stress-tested the idea from every angle before settling on the rule above. Each row is a real backtest; the pattern that emerged: <b>every "buy it cheap / beaten-down" tilt hurt, and every "quality / large-cap / diversify / adapt" control helped.</b></p>
|
|
|
|
| 150 |
<tr><td>Momentum factor (in the 2020β22 window)</td><td>The one thing that beat the fund <em>and</em> SPY in that stretch β because it's regime-adaptive</td><td class="pos">β informed overlay</td></tr>
|
| 151 |
<tr><td><b>Regime switch β momentum (200DMA, confirmed)</b></td><td>Fixes 2020β22 (β2.8% β +6.0%), lifts full-period excess to +5.0%, same drawdown</td><td class="pos">β adopted</td></tr>
|
| 152 |
<tr><td>Multi-signal regime consensus (5 signals, K-of-N)</td><td>No better than the simple single 200DMA filter; more knobs = more overfitting risk</td><td class="warn">~ kept it simple</td></tr>
|
| 153 |
+
<tr><td>Dynamic cap + regime overlay (v1, Aug 2026)</td><td>Reported 19.5% CAGR / Sharpe 0.92 β <b>not reproducible</b> (its 13F input was overwritten); rebuilt on fresh data with live execution: 15.5% CAGR, Sharpe 0.67 vs SPY 0.73, no significant alpha</td><td class="warn">~ superseded</td></tr>
|
| 154 |
+
<tr><td>Wider / different manager lists (102, 210 curated; top-200 track records; "emerging" managers; top inflows)</td><td>No list beat the simple rule-based concentrated-manager list; past 13F skill doesn't persist (rank correlation β 0 across 44 quarters)</td><td class="neg">β rejected</td></tr>
|
| 155 |
+
<tr><td>Accumulation Radar (volume footprint nowcast of 13F buying)</td><td>Detects institutional accumulation well (AUC 0.76) but adds no return on top of 13F consensus</td><td class="neg">β rejected</td></tr>
|
| 156 |
+
<tr><td><b>Quality = ROA, top 30% within sector</b></td><td>Lifted every manager list (e.g. Sharpe 0.89 β 1.07 on 2016β21); smart-money book now beats SPY in risk-on months</td><td class="pos">β adopted</td></tr>
|
| 157 |
+
<tr><td>Add debt/equity screen</td><td>Lowered return in all 4 variants, no drawdown benefit β mostly removes high-ROA buyback compounders</td><td class="neg">β rejected</td></tr>
|
| 158 |
+
<tr><td><b>10% stock cap (vs 15%)</b></td><td>Same Sharpe, top-5 weight 56% β 48%</td><td class="pos">β adopted</td></tr>
|
| 159 |
+
<tr><td>Tech cap (Technology + Communication β€ 30β40%)</td><td>Costs 1.4β2.5%/yr, no drawdown benefit β a view on tech, not a free risk reduction</td><td class="warn">~ optional, not used</td></tr>
|
| 160 |
+
<tr><td><b>Concentrated-manager consensus + ROA-30 + 10% cap (v2)</b></td><td>19.9% CAGR vs SPY 13.9%, Sharpe 0.85 vs 0.73, -37.5% max DD (2014β2026)</td><td class="pos">β
Slipstream</td></tr>
|
| 161 |
</tbody>
|
| 162 |
</table>
|
| 163 |
<p class="doc-note">A recurring lesson worth keeping: the flashiest backtest numbers came from the configs taking the most <em>uncompensated</em> risk (concentration, single regime). Each time we tightened the methodology β survivorship, costs, sub-period robustness, sector control, multiple-testing thresholds β the "edge" shrank toward the market. Slipstream is what was left standing after all of that.</p>
|
| 164 |
|
| 165 |
<h2 class="doc-h">4 Β· Data & honesty notes</h2>
|
| 166 |
<ul class="doc-list">
|
| 167 |
+
<li><b>Data:</b> SEC 13F (Sharadar SF3, every filer), fundamentals (SF1), prices (SEP), corporate actions (delistings) via Nasdaq Data Link. Survivorship-free. Sector labels are current, not historical. Form 4 insider data (SF2) feeds the Insider picks panel only.</li>
|
| 168 |
<li><b>Signal freshness:</b> 13F is quarterly and ~45 days lagged by law β so the smart-money signal is inherently stale; the fund is built to work <em>despite</em> that, not to pretend otherwise.</li>
|
| 169 |
<li><b>Picks panels</b> are informational and LLM-screened for manipulation/pump patterns; they are <em>not</em> individual buy recommendations. The fund trades a disciplined, risk-controlled subset automatically.</li>
|
| 170 |
<li><b>Paper only.</b> Slipstream runs on an Alpaca paper account. Nothing here is investment advice.</li>
|
|
|
|
| 180 |
asserts every dataset actually advanced β a stale input fails the run loudly instead of silently
|
| 181 |
trading on old data.</td></tr>
|
| 182 |
<tr><td><b>Monthly rebalance</b> (1st)</td><td>Trades the freshest weekly signal row through the
|
| 183 |
+
construction rules above (ROA quality gate β AI critical-event screen β 10% name cap alternating with dynamic
|
| 184 |
sector caps). Idempotent β a re-run trades only target-minus-actual.</td></tr>
|
| 185 |
<tr><td><b>Daily supervisor</b> (weekdays)</td><td>A deterministic health monitor β it <em>never
|
| 186 |
trades</em> β checking three things: <b>tracking</b> (trailing 4-week fund-vs-SPY gap; WATCH <β3pp,
|
| 187 |
ALERT <β6pp), <b>guardrails</b> (paper-only invariant, no margin, exposure β€100.5%, and position
|
| 188 |
+
drift β the 10% cap applies <em>at</em> rebalance, so intra-month drift only flags above 13%, alerts
|
| 189 |
+
above 17%), and <b>freshness</b> (signals β€10 days old, last rebalance β€35 days). Results appear as
|
| 190 |
the OK / WATCH / ALERT chips at the top of the Fund tab, and an ALERT fails the scheduled run so it
|
| 191 |
can't be missed.</td></tr>
|
| 192 |
</tbody>
|