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,154 @@
|
||||
"""Adversarial dissection of the SELECTION-FREE relative-value basket vs TP01."""
|
||||
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: 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) -> float:
|
||||
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 _dd(s) -> float:
|
||||
eq = (1.0 + pd.Series(s).dropna()).cumprod()
|
||||
return float((eq / eq.cummax() - 1.0).min())
|
||||
|
||||
|
||||
def uplift_at(cand: pd.Series, B: pd.Series, lo=None, hi=None, w=0.25) -> float:
|
||||
J = pd.concat({"B": B, "C": cand}, axis=1, join="inner").dropna()
|
||||
if lo is not None:
|
||||
J = J[J.index >= pd.Timestamp(lo, tz="UTC")]
|
||||
if hi is not None:
|
||||
J = J[J.index < pd.Timestamp(hi, 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()
|
||||
|
||||
# ---- collect ALL books, classify ----
|
||||
rows = {}
|
||||
meta = {}
|
||||
for p in sorted(AGENTS.glob("agent_*.py")):
|
||||
try:
|
||||
ev = ol.eval_book(_book(p))
|
||||
except Exception as e:
|
||||
print(f"skip {p.stem}: {e}"); continue
|
||||
d = ev["daily"]
|
||||
if d.std() == 0:
|
||||
print(f"skip degenerate {p.stem}"); continue
|
||||
rows[p.stem.replace("agent_", "")] = d
|
||||
meta[p.stem.replace("agent_", "")] = ev
|
||||
|
||||
names = list(rows)
|
||||
M = pd.concat(rows, axis=1, join="inner").dropna()
|
||||
print(f"\nBooks loaded: {len(names)} common-date matrix: {M.shape[0]} days "
|
||||
f"[{M.index.min().date()} .. {M.index.max().date()}]")
|
||||
|
||||
# selection-free: ALL non-degenerate, market-neutral. exclude only the OBVIOUS
|
||||
# degenerate DILUTE (rebalance_harvest, sharpe<0 standalone & negative every metric).
|
||||
excl = ["12_rebalance_harvest"]
|
||||
keep = [n for n in names if n not in excl]
|
||||
print(f"Basket = equal-weight of {len(keep)} books (excluded only {excl})")
|
||||
|
||||
basket = M[keep].mean(axis=1)
|
||||
basket_all = M.mean(axis=1) # truly ALL incl rebalance_harvest
|
||||
|
||||
for label, bk in [("BASKET(excl harvest)", basket), ("BASKET(all 18)", basket_all)]:
|
||||
print(f"\n===== {label} =====")
|
||||
Bal = pd.concat({"B": B, "C": bk}, axis=1, join="inner").dropna()
|
||||
corr = Bal["B"].corr(Bal["C"])
|
||||
print(f" standalone Sharpe {_sh(bk):.3f} maxDD {_dd(bk):.3f} corr->TP01 {corr:.3f} "
|
||||
f"n={len(bk)}")
|
||||
# full + per-cut uplift (forward windows from cut)
|
||||
print(f" uplift (w=0.25, window = [cut, end)):")
|
||||
for cut in ["2018-01-01", "2021-01-01", "2022-01-01", "2023-01-01", "2024-01-01", "2025-01-01"]:
|
||||
u = uplift_at(bk, B, lo=cut)
|
||||
print(f" from {cut[:4]}+ : {u:+.3f}")
|
||||
# PRE-2025 ONLY (exclude suspect window)
|
||||
u_pre = uplift_at(bk, B, hi="2025-01-01")
|
||||
u_2025 = uplift_at(bk, B, lo="2025-01-01")
|
||||
print(f" uplift 2019-2024 ONLY (exclude suspect window): {u_pre:+.3f}")
|
||||
print(f" uplift 2025+ ONLY : {u_2025:+.3f}")
|
||||
# per-year standalone returns & per-year uplift
|
||||
print(f" per-year: standalone ret | blend-uplift(w0.25, that year only)")
|
||||
for y in sorted(set(bk.index.year)):
|
||||
sub = bk[bk.index.year == y]
|
||||
ret = float((1 + sub).prod() - 1)
|
||||
uy = uplift_at(bk, B, lo=f"{y}-01-01", hi=f"{y+1}-01-01")
|
||||
print(f" {y}: ret {ret:+7.2%} uplift {uy:+.3f}")
|
||||
|
||||
# ---- decompose full-sample uplift: pre vs post 2025 ----
|
||||
print("\n===== UPLIFT DECOMPOSITION (basket excl harvest, w=0.25) =====")
|
||||
full = uplift_at(basket, B, lo="2018-01-01")
|
||||
print(f" full-sample uplift {full:+.3f}")
|
||||
print(f" pre-2025 (2019-2024) {uplift_at(basket,B,hi='2025-01-01'):+.3f}")
|
||||
print(f" 2025+ only {uplift_at(basket,B,lo='2025-01-01'):+.3f}")
|
||||
|
||||
# ---- fee sensitivity: rebuild basket at higher fees ----
|
||||
print("\n===== FEE SENSITIVITY (rebuild every book's daily net at higher RT fee) =====")
|
||||
for fee_side in [0.0005, 0.0010, 0.0015]:
|
||||
dd = {}
|
||||
for p in sorted(AGENTS.glob("agent_*.py")):
|
||||
nm = p.stem.replace("agent_", "")
|
||||
if nm in excl: continue
|
||||
try:
|
||||
ev = ol.eval_book(_book(p), fee_side=fee_side)
|
||||
if ev["daily"].std() > 0:
|
||||
dd[nm] = ev["daily"]
|
||||
except Exception:
|
||||
pass
|
||||
Mf = pd.concat(dd, axis=1, join="inner").dropna()
|
||||
bk = Mf.mean(axis=1)
|
||||
u_full = uplift_at(bk, B, lo="2018-01-01")
|
||||
u_pre = uplift_at(bk, B, hi="2025-01-01")
|
||||
u_25 = uplift_at(bk, B, lo="2025-01-01")
|
||||
print(f" RT={2*fee_side*100:.2f}% standalone Sh {_sh(bk):+.3f} "
|
||||
f"uplift full {u_full:+.3f} | pre25 {u_pre:+.3f} | 25+ {u_25:+.3f}")
|
||||
|
||||
# ---- turnover / executability of the basket ----
|
||||
print("\n===== EXECUTION REALISM (per-book at $600, basket-averaged legs) =====")
|
||||
tns = [meta[n]["turnover_per_year"] for n in keep]
|
||||
print(f" per-book turnover/yr: min {min(tns):.1f} med {np.median(tns):.1f} max {max(tns):.1f}")
|
||||
print(f" Basket = mean of {len(keep)} books; each leg capped 0.5. Averaged turnover lower.")
|
||||
# build averaged book legs to measure REAL basket turnover & notional
|
||||
btc, eth = ol.aligned()
|
||||
wbs, wes = [], []
|
||||
for n in keep:
|
||||
wb, we = _book(AGENTS / f"agent_{n}.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)
|
||||
pb = np.zeros(len(wb)); pb[1:] = wb[:-1]
|
||||
pe = np.zeros(len(we)); pe[1:] = we[:-1]
|
||||
turn = np.abs(np.diff(pb, prepend=0.0)) + np.abs(np.diff(pe, prepend=0.0))
|
||||
turn_yr = turn.sum() / (len(turn) / 365.25)
|
||||
max_leg = float(np.max(np.maximum(np.abs(pb), np.abs(pe))))
|
||||
print(f" AVERAGED-BASKET book: turnover/yr {turn_yr:.1f} max-leg-frac {max_leg:.3f} "
|
||||
f"max per-leg notional @$600 = ${max_leg*600:.0f}")
|
||||
print(f" typical daily notional traded @$600 satellite (say 20% slot=$120): "
|
||||
f"${turn_yr/365.25*120:.2f}/day both legs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user