82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""Raw tension regression baseline. NO lattice. Three feature regression on fwd_60 return."""
|
|
import pandas as pd, numpy as np, glob
|
|
from pathlib import Path
|
|
from sklearn.linear_model import LinearRegression
|
|
from sklearn.metrics import r2_score
|
|
from scipy.stats import spearmanr, pearsonr
|
|
|
|
# load tensions (deterministic per minute — arm_T and arm_A have identical tension columns)
|
|
T = pd.read_parquet("/mnt/d/Resonance_Engine/traj/tension_20260607T130445/arm_T_tension.parquet")
|
|
T = T[["minute","funding_bps","bs_ratio_signed","activity_excess","cvd_divergence"]].copy()
|
|
print(f"tensions: {len(T):,} minutes")
|
|
|
|
# load BTC mid_price from raw HL data
|
|
files = sorted(glob.glob("/mnt/d/PaperTrader/research/hl_data/minutes/202604*/*.parquet"))
|
|
print(f"loading {len(files)} hl files...")
|
|
px_parts=[]
|
|
for f in files:
|
|
d = pd.read_parquet(f, columns=["minute","coin","mid_price"])
|
|
px_parts.append(d[d.coin=="BTC"][["minute","mid_price"]])
|
|
px = pd.concat(px_parts, ignore_index=True).drop_duplicates("minute").sort_values("minute").reset_index(drop=True)
|
|
print(f"price rows: {len(px):,} span: minute {px.minute.min()} -> {px.minute.max()}")
|
|
|
|
# fwd_60 in bps
|
|
px["mid_60"] = px["mid_price"].shift(-60)
|
|
px["fwd_60"] = (np.log(px.mid_60) - np.log(px.mid_price)) * 10000.0
|
|
|
|
# join
|
|
df = T.merge(px[["minute","fwd_60"]], on="minute", how="inner")
|
|
df = df.dropna(subset=["funding_bps","bs_ratio_signed","activity_excess","cvd_divergence","fwd_60"])
|
|
print(f"after dropna + fwd_60: {len(df):,} rows")
|
|
|
|
X_cols = ["funding_bps","bs_ratio_signed","activity_excess","cvd_divergence"]
|
|
X = df[X_cols].values
|
|
y = df["fwd_60"].values
|
|
|
|
# scale features to unit std so coef magnitudes are comparable
|
|
Xz = (X - X.mean(axis=0)) / (X.std(axis=0) + 1e-12)
|
|
|
|
print()
|
|
print("="*88)
|
|
print("RAW TENSION REGRESSION — fwd_60 (bps) no lattice")
|
|
print("="*88)
|
|
print(f" n={len(df):,} y mean={y.mean():+.2f}bps y std={y.std():.2f}bps")
|
|
|
|
# 1) univariate Spearman + Pearson per tension
|
|
print()
|
|
print("=== univariate ===")
|
|
print(f"{'feature':<22} {'spearman':>10} {'pearson':>10} p (spearman)")
|
|
for c,name in zip(X.T, X_cols):
|
|
rho_s,p_s = spearmanr(c, y)
|
|
rho_p,p_p = pearsonr(c, y)
|
|
print(f" {name:<20} {rho_s:>+10.4f} {rho_p:>+10.4f} {p_s:.2g}")
|
|
|
|
# 2) full OLS on standardized features
|
|
print()
|
|
print("=== OLS (standardized features) ===")
|
|
reg = LinearRegression().fit(Xz, y)
|
|
pred = reg.predict(Xz)
|
|
print(f" R^2 (in-sample, all April) = {r2_score(y, pred):.6f}")
|
|
print(f" intercept = {reg.intercept_:+.4f}")
|
|
for name, coef in zip(X_cols, reg.coef_):
|
|
print(f" beta[{name:<22}] = {coef:+.4f} bps per 1-sigma")
|
|
|
|
# 3) chronological 70/30 split (out-of-sample)
|
|
n=len(df); split=int(n*0.7)
|
|
reg_oos = LinearRegression().fit(Xz[:split], y[:split])
|
|
pred_oos = reg_oos.predict(Xz[split:])
|
|
r2_oos = r2_score(y[split:], pred_oos)
|
|
print()
|
|
print("=== OLS chronological 70/30 ===")
|
|
print(f" train={split:,} test={n-split:,}")
|
|
print(f" R^2 train = {r2_score(y[:split], reg_oos.predict(Xz[:split])):.6f}")
|
|
print(f" R^2 test = {r2_oos:.6f}")
|
|
print(f" directional accuracy test = {(np.sign(pred_oos)==np.sign(y[split:])).mean()*100:.2f}%")
|
|
|
|
# 4) for context — autocorrelation of fwd_60 (the trivial AR(1) baseline to beat)
|
|
print()
|
|
print("=== triviality check ===")
|
|
print(f" y autocorr lag1 = {pd.Series(y).autocorr(1):+.4f}")
|
|
print(f" y autocorr lag60 = {pd.Series(y).autocorr(60):+.4f}")
|
|
print(f" if R^2 above is in the same ballpark as autocorr^2, raw tensions add nothing.")
|