What to do with diversification? Tested sqrt(N) lever: base-6 vs expanded-12 (+REITs/TIPS/HY/intl/ EM/vol-premium). Expanded WORSE (+1.20->+1.04, 2022 +0.1->-1.4): candidates are equity/bond-beta in disguise -> concentrate not diversify. Trust layer manages edge-health not redundancy. Genuine uncorrelated net-positive streams are scarce; base-6 already spans the distinct premia. Diversification is maxed in the deployable book; the lever is capital, not more streams. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""What can we DO with diversification: add more uncorrelated net-positive streams (the sqrt(N) lever).
|
|
|
|
Base 6 (SPY/IEF/GLD/PDBC/DBMF/BTC) + candidates: VNQ(REITs), TIP(infl-linked), HYG(HY credit),
|
|
EFA(intl eq), EEM(EM eq), PUTW(vol-risk-premium/buy-write). Same adaptive pipeline (edge-decay
|
|
trust auto-down-weights the bad ones). Does the expanded book beat the base-6? The trust layer
|
|
decides which candidates earn their place.
|
|
"""
|
|
import datetime
|
|
import json
|
|
import math
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from multistrat_paper import book_series # exact adaptive pipeline # noqa: E402
|
|
|
|
BASE = [("SPY", "equity"), ("IEF", "bond"), ("GLD", "gold"), ("PDBC", "commod"), ("DBMF", "trend"), ("BTC-USD", "crypto")]
|
|
CAND = [("VNQ", "reit"), ("TIP", "tips"), ("HYG", "hycredit"), ("EFA", "intl"), ("EEM", "em"), ("PUTW", "volprem")]
|
|
|
|
|
|
def yhist(sym):
|
|
res = json.loads(urllib.request.urlopen(urllib.request.Request(
|
|
f"https://query1.finance.yahoo.com/v8/finance/chart/{sym}?interval=1d&range=10y",
|
|
headers={"User-Agent": "Mozilla/5.0"}), timeout=30).read())["chart"]["result"][0]
|
|
ts = res["timestamp"]; ind = res["indicators"]
|
|
adj = ind.get("adjclose", [{}])[0].get("adjclose") or ind["quote"][0]["close"]
|
|
return {datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d"): float(c) for t, c in zip(ts, adj) if c is not None}
|
|
|
|
|
|
def stats(r):
|
|
r = r[np.isfinite(r) & (r != 0)]
|
|
ann = r.mean() * 252; vol = r.std() * math.sqrt(252)
|
|
eq = np.cumprod(1 + r); dd = float((eq / np.maximum.accumulate(eq) - 1).min())
|
|
return ann, vol, (ann / vol if vol > 0 else float("nan")), dd
|
|
|
|
|
|
def run(instr, data, label):
|
|
dates = sorted(set.intersection(*[set(data[nm]) for _, nm in instr]))
|
|
R = np.zeros((len(dates), len(instr)))
|
|
for j, (_, nm) in enumerate(instr):
|
|
s = np.array([data[nm][d] for d in dates]); R[1:, j] = s[1:] / s[:-1] - 1
|
|
book, w, L = book_series(R)
|
|
a, v, sh, dd = stats(book)
|
|
yr = np.array([int(d[:4]) for d in dates])
|
|
py = " ".join(f"{y}:{stats(book[yr==y])[2]:+.1f}" for y in sorted(set(yr)) if (yr == y).sum() > 100)
|
|
print(f" {label:>16} ({len(instr)} streams, {len(dates)}d): Sharpe {sh:+.2f} ann {100*a:+.1f}% maxDD {100*dd:+.1f}%")
|
|
print(f" per-year: {py}")
|
|
return w, [nm for _, nm in instr]
|
|
|
|
|
|
def main():
|
|
allinstr = BASE + CAND
|
|
data = {nm: yhist(sym) for sym, nm in allinstr}
|
|
print(f"EXPANDED BOOK (diversification lever) — base 6 vs +candidates")
|
|
run(BASE, data, "base-6")
|
|
w, names = run(allinstr, data, "expanded-12")
|
|
print(f"\n trust-layer weights in expanded book (which candidates earned their place):")
|
|
order = sorted(range(len(names)), key=lambda j: -w[j])
|
|
for j in order:
|
|
print(f" {names[j]:>9}: {100*w[j]:>5.1f}%")
|
|
print("\n VERDICT: expanded Sharpe > base-6 = more uncorrelated streams lift the book (diversification works).")
|
|
print(" If ~equal, the candidates are too correlated / not net-positive -> 6 streams already captures it.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|