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>
134 lines
6.5 KiB
Python
134 lines
6.5 KiB
Python
"""ortholib — lab for strategies ORTHOGONAL to TP01 (the only thing worth a NEW live slot).
|
|
|
|
The blind fleet proved (again) that directional BTC/ETH is trend-beta of TP01 — a ~1.3
|
|
ceiling, nothing new to deploy. The ONLY way to earn a live slot is a mechanism whose
|
|
returns are NOT explained by TP01's trend. The most promising one that is ALSO executable
|
|
at our real ~$600 (unlike XS01's 19-leg book, which needs ~$20k) is a **2-leg BTC/ETH
|
|
relative-value book**: long one leg / short (or under-weight) the other. Being roughly
|
|
market-neutral, its correlation to TP01 (a long-flat trend on the SUM) is naturally low.
|
|
|
|
A candidate here is a BOOK function:
|
|
def book(btc, eth) -> (w_btc, w_eth)
|
|
where w_*[i] in [-1,1] is the per-leg weight (fraction of equity, sign = long/short),
|
|
decided causally with data <= close[i]. The evaluator SHIFTS both legs (held during bar
|
|
i+1), charges fees on BOTH legs' turnover, and:
|
|
* builds the book's net DAILY return series,
|
|
* scores it MARGINALLY vs TP01 via altlib.marginal_vs_tp01 (corr, OOS blend uplift,
|
|
residual alpha, robust_oos jackknife) — the verdict that matters is ADDS, not Sharpe,
|
|
* checks EXECUTABILITY at $600 (per-leg notional within the live cap, turnover sane),
|
|
* runs the same online causality guard as the blind lab.
|
|
|
|
Judge a candidate on: marginal_verdict == 'ADDS' AND robust_oos AND executable. Absolute
|
|
PnL/DD are reported but are NOT the gate (a market-neutral sleeve has modest absolute
|
|
numbers by design; its job is to improve the PORTFOLIO, not to win alone).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
|
import altlib as al # noqa: E402
|
|
|
|
FEE_SIDE = al.FEE_SIDE
|
|
CAPITAL = 600.0 # real mainnet capital (NOT the 2000 paper nominal)
|
|
CAP_NOTIONAL = 300.0 # live guardrail: $300 notional / asset
|
|
MIN_ORDER = 5.0
|
|
CAP_FRAC = CAP_NOTIONAL / CAPITAL # 0.5 of capital per leg
|
|
|
|
|
|
def aligned():
|
|
"""BTC & ETH 1d on COMMON dates (inner join), as two parallel DataFrames with a
|
|
shared 'datetime'. Relative-value needs both legs at the same bar."""
|
|
b = al.get("BTC", "1d").copy()
|
|
e = al.get("ETH", "1d").copy()
|
|
b["dt"] = pd.to_datetime(b["datetime"], utc=True)
|
|
e["dt"] = pd.to_datetime(e["datetime"], utc=True)
|
|
common = pd.Index(b["dt"]).intersection(pd.Index(e["dt"]))
|
|
b = b[b["dt"].isin(common)].sort_values("dt").reset_index(drop=True)
|
|
e = e[e["dt"].isin(common)].sort_values("dt").reset_index(drop=True)
|
|
return b, e
|
|
|
|
|
|
def eval_book(book_fn, fee_side: float = FEE_SIDE) -> dict:
|
|
"""Honest backtest of a 2-leg BTC/ETH book. Weights decided at close[i] are HELD
|
|
during bar i+1 (shift here -> no leak). Fee on both legs' turnover. Returns standalone
|
|
metrics, the net DAILY series (for marginal scoring), and executability stats."""
|
|
btc, eth = aligned()
|
|
wb, we = book_fn(btc, eth)
|
|
wb = np.clip(np.nan_to_num(np.asarray(wb, float), nan=0.0), -1, 1)
|
|
we = np.clip(np.nan_to_num(np.asarray(we, float), nan=0.0), -1, 1)
|
|
n = len(btc)
|
|
rb = al.simple_returns(btc["close"].values.astype(float))
|
|
re = al.simple_returns(eth["close"].values.astype(float))
|
|
pb = np.zeros(n); pb[1:] = wb[:-1]
|
|
pe = np.zeros(n); pe[1:] = we[:-1]
|
|
gross = pb * rb + pe * re
|
|
turn = np.abs(np.diff(pb, prepend=0.0)) + np.abs(np.diff(pe, prepend=0.0))
|
|
net = gross - fee_side * turn
|
|
net[0] = 0.0
|
|
idx = pd.DatetimeIndex(btc["dt"])
|
|
m = al._metrics_from_net(net, idx)
|
|
daily = al._to_daily(pd.Series(net, index=idx))
|
|
# executability: worst per-leg notional fraction, and net market exposure
|
|
max_leg = float(np.max(np.maximum(np.abs(pb), np.abs(pe)))) if n else 0.0
|
|
gross_lev = float(np.max(np.abs(pb) + np.abs(pe))) if n else 0.0
|
|
net_beta = float(np.mean(pb + pe)) # ~0 => market-neutral
|
|
turnover_yr = float(turn.sum() / (len(turn) / 365.25))
|
|
return dict(
|
|
pnl=m["ret"], maxdd=m["maxdd"], sharpe=m["sharpe"], cagr=m["cagr"],
|
|
net=net, idx=idx, daily=daily,
|
|
max_leg_frac=round(max_leg, 3), gross_lev=round(gross_lev, 3),
|
|
net_beta=round(net_beta, 3), turnover_per_year=round(turnover_yr, 1),
|
|
executable=bool(max_leg <= CAP_FRAC + 1e-9 and max_leg * CAPITAL >= MIN_ORDER),
|
|
)
|
|
|
|
|
|
def marginal(book_fn, fee_side: float = FEE_SIDE) -> dict:
|
|
"""eval_book -> altlib.marginal_vs_tp01 on the book's daily returns. The real verdict."""
|
|
ev = eval_book(book_fn, fee_side)
|
|
marg = al.marginal_vs_tp01(ev["daily"])
|
|
return dict(book=ev, marginal=marg)
|
|
|
|
|
|
def causality_ok(book_fn, tail: int = 60, tol: float = 1e-4) -> dict:
|
|
"""Online-consistency guard: book(btc[:cut], eth[:cut]) must match book(full)[:cut]
|
|
on the tail before each cut. Catches any look-ahead / global fit in either leg."""
|
|
btc, eth = aligned()
|
|
wbf, wef = book_fn(btc, eth)
|
|
wbf = np.nan_to_num(np.asarray(wbf, float)); wef = np.nan_to_num(np.asarray(wef, float))
|
|
n = len(btc)
|
|
max_diff = 0.0; frac_bad = 0.0; checked = []
|
|
for cut in (int(n * 0.80), int(n * 0.92)):
|
|
if cut <= tail + 5 or cut >= n:
|
|
continue
|
|
wbs, wes = book_fn(btc.iloc[:cut].reset_index(drop=True),
|
|
eth.iloc[:cut].reset_index(drop=True))
|
|
wbs = np.nan_to_num(np.asarray(wbs, float)); wes = np.nan_to_num(np.asarray(wes, float))
|
|
if len(wbs) != cut or len(wes) != cut:
|
|
return dict(ok=False, reason=f"book returned wrong length on prefix {cut}",
|
|
max_diff=9.99, frac_bad=1.0)
|
|
d = np.maximum(np.abs(wbs[cut - tail:cut] - wbf[cut - tail:cut]),
|
|
np.abs(wes[cut - tail:cut] - wef[cut - tail:cut]))
|
|
max_diff = max(max_diff, float(np.max(d)) if len(d) else 0.0)
|
|
frac_bad = max(frac_bad, float(np.mean(d > tol)) if len(d) else 0.0)
|
|
checked.append(cut)
|
|
ok = (max_diff <= max(tol * 10, 1e-3)) and (frac_bad <= 0.02)
|
|
return dict(ok=bool(ok), max_diff=round(max_diff, 6), frac_bad=round(frac_bad, 4),
|
|
checked_at=checked)
|
|
|
|
|
|
# convenient causal helpers re-exported (same as blind/altlib)
|
|
simple_returns = al.simple_returns
|
|
log_returns = al.log_returns
|
|
ema = al.ema; sma = al.sma; rolling_std = al.rolling_std; zscore = al.zscore
|
|
rsi = al.rsi; realized_vol = al.realized_vol; donchian = al.donchian; bbands = al.bbands
|
|
vol_target = al.vol_target
|
|
|
|
__all__ = ["aligned", "eval_book", "marginal", "causality_ok", "CAPITAL", "CAP_FRAC",
|
|
"FEE_SIDE", "simple_returns", "log_returns", "ema", "sma", "rolling_std",
|
|
"zscore", "rsi", "realized_vol", "donchian", "bbands", "vol_target"]
|