0adc69a357
18 agenti su book market-neutral a 2 gambe BTC/ETH (eseguibili a $600, a differenza di XS01), giudicati sul MARGINALE vs TP01 (altlib.marginal_vs_tp01), non sullo Sharpe assoluto. Lab: ortholib.py (eval_book leak-free a 2 gambe + causalità + eseguibilità@600), ortho_score.py (giudice), meta_ortho.py (corr mutua + persistenza multi-cut), sleeve_rv.py (curated, SELECTION- BIASED, non deployare). Esito: 17/18 "ADDS" -> gonfiato dall'hold-out corto fisso-2025 (finestra ETH-bleed dove TP01 è debole). Diagnosi orchestratore: collassano a 8 bet (corr 0.43); persistenza multi-cut e selezione walk-forward smascherano i 2025-only (kalman/xs2). Scettico indipendente: basket selection-free ha uplift pre-2025 +0.027 = 49° percentile di asset-rumore corr-zero (matematica di diversificazione, non segnale); corr(Sharpe-TP01, uplift) -0.87 (è un HEDGE dei drawdown di TP01); muore a 0.30% RT. Verdetto: NIENTE in live. Resta solo TP01. Lezione: lo scorer marginale va indurito (multi-cut + null-asset-rumore + distinguere hedge da alpha). Diario 2026-06-21-ortho-tp01-relative-value.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
4.4 KiB
Python
107 lines
4.4 KiB
Python
"""meta_ortho — the orchestrator's decisive read on the ortho fleet.
|
|
|
|
17/18 books "earn a slot". That cannot be 17 alphas. This asks the three questions that
|
|
decide whether ANY of it is deployable:
|
|
|
|
1. ARE THEY ONE BET? Mutual correlation of the books' daily returns. Relative-momentum
|
|
variants will cluster ~1; we collapse them to de-correlated representatives.
|
|
2. IS THE UPLIFT PERSISTENT OR JUST THE 2025-26 WINDOW? altlib.marginal_vs_tp01 fixes
|
|
the hold-out at 2025-01-01 — exactly the window where ETH bled vs BTC and TP01 was
|
|
weak. We re-measure the blend uplift at SEVERAL cut dates (2022/2023/2024/2025). A
|
|
real orthogonal premium adds at every cut; a regime artifact only adds at 2025.
|
|
3. WHAT DOES A SINGLE COMBINED SLEEVE LOOK LIKE? Equal-weight the representatives into
|
|
one relative-value sleeve and score THAT vs TP01 (full + per-cut).
|
|
|
|
uv run python scripts/research/ortho/meta_ortho.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE))
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
|
import ortholib as ol # noqa: E402
|
|
import altlib as al # noqa: E402
|
|
|
|
AGENTS = HERE / "agents"
|
|
CUTS = ["2022-01-01", "2023-01-01", "2024-01-01", "2025-01-01"]
|
|
|
|
|
|
def _book(path: Path):
|
|
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod.book
|
|
|
|
|
|
def _sh(s: pd.Series) -> float:
|
|
r = np.asarray(s.dropna().values, float)
|
|
return float(np.mean(r) / np.std(r) * np.sqrt(365.25)) if len(r) > 2 and np.std(r) > 0 else 0.0
|
|
|
|
|
|
def uplift_at(cand: pd.Series, B: pd.Series, cut: str, w: float = 0.25) -> float:
|
|
J = pd.concat({"B": B, "C": cand}, axis=1, join="inner").dropna()
|
|
J = J[J.index >= pd.Timestamp(cut, tz="UTC")]
|
|
if len(J) < 30:
|
|
return float("nan")
|
|
return _sh((1 - w) * J["B"] + w * J["C"]) - _sh(J["B"])
|
|
|
|
|
|
def main():
|
|
B = al.tp01_baseline_daily()
|
|
dailies = {}
|
|
for p in sorted(AGENTS.glob("agent_*.py")):
|
|
try:
|
|
ev = ol.eval_book(_book(p))
|
|
d = ev["daily"]
|
|
if d.std() > 0:
|
|
dailies[p.stem.replace("agent_", "")] = d
|
|
except Exception as e:
|
|
print(f" skip {p.stem}: {e}")
|
|
|
|
names = list(dailies)
|
|
M = pd.concat(dailies, axis=1, join="inner").dropna()
|
|
C = M.corr()
|
|
|
|
# greedy de-correlation: order by full-sample uplift, keep if corr<0.6 to all kept
|
|
upf = {n: uplift_at(dailies[n], B, "2018-01-01") for n in names}
|
|
order = sorted(names, key=lambda n: upf[n], reverse=True)
|
|
reps = []
|
|
for n in order:
|
|
if all(abs(C.loc[n, r]) < 0.6 for r in reps):
|
|
reps.append(n)
|
|
|
|
print(f"\n ORTHO META — {len(names)} books, mutual-corr clusters -> {len(reps)} de-correlated reps")
|
|
print(f" mean |corr| among all books = {C.abs().values[np.triu_indices(len(names),1)].mean():.2f}")
|
|
print(f"\n DE-CORRELATED REPRESENTATIVES (corr<0.6 to each other):")
|
|
print(f" {'book':<20}{'up_full':>8} uplift at cut: " + " ".join(c[:7] for c in CUTS))
|
|
for n in reps:
|
|
ups = [uplift_at(dailies[n], B, c) for c in CUTS]
|
|
print(f" {n:<20}{upf[n]:>8.3f} " + " ".join(f"{u:>+6.2f}" for u in ups))
|
|
|
|
# combined sleeve = equal-weight of representatives
|
|
combo = M[reps].mean(axis=1)
|
|
print(f"\n COMBINED relative-value sleeve (equal-weight of {len(reps)} reps):")
|
|
print(f" {'':<20}{'up_full':>8} uplift at cut: " + " ".join(c[:7] for c in CUTS))
|
|
ups = [uplift_at(combo, B, c) for c in CUTS]
|
|
print(f" {'COMBO':<20}{uplift_at(combo,B,'2018-01-01'):>8.3f} " + " ".join(f"{u:>+6.2f}" for u in ups))
|
|
print(f" combo standalone: Sharpe {_sh(combo):.2f} corr->TP01 {M[reps].mean(axis=1).corr(pd.concat({'b':B},axis=1).reindex(combo.index)['b']):.2f}")
|
|
|
|
# also: ALL ADDS equal-weight (what 'just deploy everything' would be)
|
|
allc = M.mean(axis=1)
|
|
ups = [uplift_at(allc, B, c) for c in CUTS]
|
|
print(f"\n ALL-{len(names)} equal-weight sleeve:")
|
|
print(f" {'ALL':<20}{uplift_at(allc,B,'2018-01-01'):>8.3f} " + " ".join(f"{u:>+6.2f}" for u in ups))
|
|
print("\n READ: a column that is +ve at 2022/2023/2024 cuts (not only 2025) = persistent.")
|
|
print(" all-positive-only-at-2025 = the ETH/BTC-bleed regime, not a standing premium.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|