research(ortho): caccia all'ortogonale a TP01 — relative-value BTC/ETH reale ma NON deployabile (hedge mono-regime)
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>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""Is the basket uplift just 'helps when TP01 is weak'? Quantify regime-conditionality."""
|
||||
from __future__ import annotations
|
||||
import importlib.util, 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
|
||||
import altlib as al
|
||||
AGENTS = HERE / "agents"
|
||||
|
||||
|
||||
def _book(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):
|
||||
r = np.asarray(pd.Series(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 main():
|
||||
B = al.tp01_baseline_daily()
|
||||
excl = ["12_rebalance_harvest"]
|
||||
rows = {}
|
||||
for p in sorted(AGENTS.glob("agent_*.py")):
|
||||
nm = p.stem.replace("agent_", "")
|
||||
if nm in excl: continue
|
||||
ev = ol.eval_book(_book(p))
|
||||
if ev["daily"].std() > 0:
|
||||
rows[nm] = ev["daily"]
|
||||
M = pd.concat(rows, axis=1, join="inner").dropna()
|
||||
basket = M.mean(axis=1)
|
||||
J = pd.concat({"B": B, "C": basket}, axis=1, join="inner").dropna()
|
||||
|
||||
print("Per-year: TP01 own Sharpe | basket Sharpe | basket ret | blend-uplift")
|
||||
print(" (does the basket help SPECIFICALLY in TP01's weak years?)")
|
||||
for y in sorted(set(J.index.year)):
|
||||
sub = J[J.index.year == y]
|
||||
if len(sub) < 20: continue
|
||||
tp_sh = _sh(sub["B"]); bk_sh = _sh(sub["C"])
|
||||
bk_ret = float((1 + sub["C"]).prod() - 1)
|
||||
up = _sh(0.75 * sub["B"] + 0.25 * sub["C"]) - _sh(sub["B"])
|
||||
print(f" {y}: TP01 Sh {tp_sh:+6.2f} | basket Sh {bk_sh:+6.2f} | ret {bk_ret:+7.2%} | uplift {up:+.3f}")
|
||||
|
||||
# correlation: does basket-year-uplift track NEGATIVE TP01-year-Sharpe?
|
||||
rows2 = []
|
||||
for y in sorted(set(J.index.year)):
|
||||
sub = J[J.index.year == y]
|
||||
if len(sub) < 20: continue
|
||||
rows2.append((_sh(sub["B"]), _sh(0.75*sub["B"]+0.25*sub["C"]) - _sh(sub["B"])))
|
||||
arr = np.array(rows2)
|
||||
if len(arr) > 2:
|
||||
c = np.corrcoef(arr[:,0], arr[:,1])[0,1]
|
||||
print(f"\n corr(TP01 yearly Sharpe, basket yearly uplift) = {c:+.2f}")
|
||||
print(" (strongly NEGATIVE => basket only helps when TP01 is weak = regime hedge, not standing alpha)")
|
||||
|
||||
# conditional uplift: split days by whether TP01 trailing-60d return is up or down
|
||||
tp_trail = J["B"].rolling(60).sum()
|
||||
up_days = J[tp_trail > 0]; dn_days = J[tp_trail <= 0]
|
||||
for lbl, d in [("TP01 trailing-60d UP", up_days), ("TP01 trailing-60d DOWN", dn_days)]:
|
||||
if len(d) < 30: continue
|
||||
u = _sh(0.75*d["B"]+0.25*d["C"]) - _sh(d["B"])
|
||||
print(f" [{lbl}] n={len(d)} basket Sh {_sh(d['C']):+.2f} blend-uplift {u:+.3f}")
|
||||
|
||||
# ETH/BTC ratio regime: is the basket net-short ETH (i.e. is it just shorting the bleed)?
|
||||
btc, eth = ol.aligned()
|
||||
wbs, wes = [], []
|
||||
for nm in rows:
|
||||
wb, we = _book(AGENTS / f"agent_{nm}.py")(btc, eth)
|
||||
wbs.append(np.nan_to_num(np.asarray(wb,float))); wes.append(np.nan_to_num(np.asarray(we,float)))
|
||||
wb = np.clip(np.mean(wbs,axis=0),-0.5,0.5); we = np.clip(np.mean(wes,axis=0),-0.5,0.5)
|
||||
idx = pd.DatetimeIndex(btc["dt"])
|
||||
wbS = pd.Series(wb, index=idx); weS = pd.Series(we, index=idx)
|
||||
print("\n Mean basket leg weights per year (is it structurally short ETH / long BTC?):")
|
||||
print(" year w_btc w_eth (positive=long)")
|
||||
for y in sorted(set(idx.year)):
|
||||
m = idx.year == y
|
||||
print(f" {y}: {wbS[m].mean():+.3f} {weS[m].mean():+.3f}")
|
||||
print(f"\n FULL mean: w_btc {wbS.mean():+.3f} w_eth {weS.mean():+.3f}")
|
||||
print(" (a persistent long-BTC/short-ETH tilt = it's a static ETH-bleed short, not RV alpha)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user