feat: 15 nuovi indicatori quant (common + deribit + bybit + macro + sentiment)
Common (mcp_common): - indicators.py: vol_cone, hurst_exponent, half_life_mean_reversion, garch11_forecast, autocorrelation, rolling_sharpe, var_cvar - options.py (nuovo): oi_weighted_skew, smile_asymmetry, atm_vs_wings_vol, dealer_gamma_profile, vanna_charm_aggregate - microstructure.py (nuovo): orderbook_imbalance (ratio + microprice + slope) - stats.py (nuovo): cointegration_test Engle-Granger + ADF helper Deribit (+6 tool MCP): - get_dealer_gamma_profile (net dealer gamma + flip level) - get_vanna_charm (vanna/charm aggregati pesati OI) - get_oi_weighted_skew, get_smile_asymmetry, get_atm_vs_wings_vol - get_orderbook_imbalance Bybit (+2 tool MCP): - get_orderbook_imbalance, get_basis_term_structure (futures dated curve) Macro (+2 tool MCP): - get_yield_curve_slope (2y10y/5y30y + butterfly + regime) - get_breakeven_inflation (FRED T5YIE/T10YIE/T5YIFR) Sentiment (+3 tool MCP): - get_funding_arb_spread (opportunità arb compatte annualizzate) - get_liquidation_heatmap (heuristic da OI delta + funding extreme, no feed paid Coinglass) - get_cointegration_pairs (Engle-Granger su coppie crypto Binance hourly) Tutto in TDD pure-Python (no numpy/scipy in mcp_common). README aggiornato con elenco completo. 442 test totali verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Test statistici puri (cointegration, ADF, half-life già in indicators).
|
||||
Nessuna dipendenza esterna: pure-Python.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def _ols_slope_intercept(xs: list[float], ys: list[float]) -> tuple[float, float] | None:
|
||||
if len(xs) != len(ys) or len(xs) < 3:
|
||||
return None
|
||||
n = len(xs)
|
||||
mx = sum(xs) / n
|
||||
my = sum(ys) / n
|
||||
num = sum((xs[i] - mx) * (ys[i] - my) for i in range(n))
|
||||
den = sum((xs[i] - mx) ** 2 for i in range(n))
|
||||
if den == 0:
|
||||
return None
|
||||
slope = num / den
|
||||
intercept = my - slope * mx
|
||||
return slope, intercept
|
||||
|
||||
|
||||
def _adf_t_stat(series: list[float]) -> float | None:
|
||||
"""Augmented Dickey-Fuller test stat semplificato (lag=0 → DF):
|
||||
Δy_t = a + b*y_{t-1} + eps. t-stat di b vs zero.
|
||||
Più negativo = più stazionario. Approssimazione: critical value ~ -2.86 al 5%.
|
||||
"""
|
||||
if len(series) < 30:
|
||||
return None
|
||||
y_lag = series[:-1]
|
||||
delta = [series[i] - series[i - 1] for i in range(1, len(series))]
|
||||
res = _ols_slope_intercept(y_lag, delta)
|
||||
if res is None:
|
||||
return None
|
||||
b, a = res
|
||||
n = len(y_lag)
|
||||
mx = sum(y_lag) / n
|
||||
den = sum((x - mx) ** 2 for x in y_lag)
|
||||
if den == 0:
|
||||
return None
|
||||
fitted = [a + b * y_lag[i] for i in range(n)]
|
||||
resid = [delta[i] - fitted[i] for i in range(n)]
|
||||
rss = sum(r * r for r in resid)
|
||||
if n - 2 <= 0:
|
||||
return None
|
||||
sigma2 = rss / (n - 2)
|
||||
se_b = math.sqrt(sigma2 / den)
|
||||
if se_b == 0:
|
||||
return None
|
||||
return b / se_b
|
||||
|
||||
|
||||
def cointegration_test(
|
||||
series_a: list[float],
|
||||
series_b: list[float],
|
||||
significance_t: float = -2.86,
|
||||
) -> dict[str, float | bool | None]:
|
||||
"""Engle-Granger cointegration:
|
||||
1. OLS: y_t = alpha + beta * x_t + eps
|
||||
2. ADF su residui: se t-stat < critical (-2.86 @ 5%) → cointegrate.
|
||||
"""
|
||||
if len(series_a) != len(series_b) or len(series_a) < 50:
|
||||
return {
|
||||
"cointegrated": None,
|
||||
"beta": None,
|
||||
"alpha": None,
|
||||
"adf_t_stat": None,
|
||||
"spread_mean": None,
|
||||
"spread_std": None,
|
||||
}
|
||||
res = _ols_slope_intercept(series_b, series_a)
|
||||
if res is None:
|
||||
return {
|
||||
"cointegrated": None,
|
||||
"beta": None,
|
||||
"alpha": None,
|
||||
"adf_t_stat": None,
|
||||
"spread_mean": None,
|
||||
"spread_std": None,
|
||||
}
|
||||
beta, alpha = res
|
||||
spread = [series_a[i] - alpha - beta * series_b[i] for i in range(len(series_a))]
|
||||
t_stat = _adf_t_stat(spread)
|
||||
cointegrated = (t_stat is not None and t_stat < significance_t)
|
||||
n = len(spread)
|
||||
mean = sum(spread) / n
|
||||
var = sum((s - mean) ** 2 for s in spread) / (n - 1) if n > 1 else 0.0
|
||||
return {
|
||||
"cointegrated": cointegrated,
|
||||
"beta": beta,
|
||||
"alpha": alpha,
|
||||
"adf_t_stat": t_stat,
|
||||
"spread_mean": mean,
|
||||
"spread_std": math.sqrt(var),
|
||||
}
|
||||
Reference in New Issue
Block a user