153 lines
6.6 KiB
Python
153 lines
6.6 KiB
Python
"""regime_analyzer.py — Claude Desktop Option B test.
|
|
|
|
Hypothesis (Claude Desktop, 2026-06-08):
|
|
HIGH TENSION = asymmetry > arm A q90 OR coherence < arm A q10
|
|
LOW TENSION = asymmetry < arm A q10 AND coherence > arm A q90
|
|
NEUTRAL = otherwise
|
|
|
|
Primary test:
|
|
vol_ratio for (HIGH ∩ top-decile trade_count)
|
|
must exceed vol_ratio for top-decile trade_count alone (best 2.42x).
|
|
|
|
Secondary test:
|
|
For HIGH TENSION minutes, sign of (taker_buy_z - taker_sell_z) → sign(fwd_60).
|
|
Also try (stress_xx - stress_yy) as the lattice-derived direction signal.
|
|
|
|
Usage: edit RUN below, then `wsl python3 /mnt/d/Resonance_Engine/regime_analyzer.py`
|
|
"""
|
|
import pandas as pd, numpy as np, glob, sys
|
|
|
|
RUN = sys.argv[1] if len(sys.argv) > 1 else "/mnt/d/Resonance_Engine/traj/regime_20260608T131732"
|
|
print(f"=== regime_analyzer on {RUN} ===")
|
|
|
|
A = pd.read_parquet(f"{RUN}/arm_A_no_inject.parquet")
|
|
T = pd.read_parquet(f"{RUN}/arm_T_3ch.parquet")
|
|
print(f"arm A: {len(A):,} arm T: {len(T):,}")
|
|
|
|
# Load BTC prices (joined to compute fwd_60 returns)
|
|
files = sorted(glob.glob("/mnt/d/PaperTrader/research/hl_data/minutes/202604*/*.parquet"))
|
|
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)
|
|
px["fwd_60_bps"] = (np.log(px["mid_price"].shift(-60)) - np.log(px["mid_price"])) * 10000.0
|
|
print(f"prices: {len(px):,} minutes")
|
|
|
|
# Join price returns into arm T (we measure on T's telemetry + raw fields)
|
|
df = T.merge(px[["minute", "fwd_60_bps"]], on="minute", how="inner").sort_values("minute").reset_index(drop=True)
|
|
df = df.dropna(subset=["fwd_60_bps", "asymmetry", "coherence", "trade_count"]).reset_index(drop=True)
|
|
print(f"joined arm T rows with fwd_60 + telemetry: {len(df):,}")
|
|
|
|
# ─── thresholds from arm A free-running baseline ───
|
|
asym_p10 = A["asymmetry"].quantile(0.10)
|
|
asym_p90 = A["asymmetry"].quantile(0.90)
|
|
coh_p10 = A["coherence"].quantile(0.10)
|
|
coh_p90 = A["coherence"].quantile(0.90)
|
|
print()
|
|
print("=== arm A baseline thresholds ===")
|
|
print(f" asymmetry p10={asym_p10:.4f} p90={asym_p90:.4f} (mean={A.asymmetry.mean():.4f})")
|
|
print(f" coherence p10={coh_p10:.4f} p90={coh_p90:.4f} (mean={A.coherence.mean():.4f})")
|
|
|
|
# Label regimes on arm T using arm A thresholds
|
|
high = (df["asymmetry"] > asym_p90) | (df["coherence"] < coh_p10)
|
|
low = (df["asymmetry"] < asym_p10) & (df["coherence"] > coh_p90)
|
|
neutral = ~(high | low)
|
|
|
|
df["regime"] = "NEUTRAL"
|
|
df.loc[high, "regime"] = "HIGH"
|
|
df.loc[low, "regime"] = "LOW"
|
|
print()
|
|
print("=== regime label distribution (arm T) ===")
|
|
print(df["regime"].value_counts())
|
|
print(f" HIGH frac: {high.mean()*100:.2f}%")
|
|
print(f" LOW frac: {low.mean()*100:.2f}%")
|
|
print(f" NEUTRAL frac: {neutral.mean()*100:.2f}%")
|
|
|
|
# ─── trade_count top decile (the validated raw signal) ───
|
|
tc_p90 = df["trade_count"].quantile(0.90)
|
|
top_tc = df["trade_count"] >= tc_p90
|
|
print()
|
|
print(f"=== trade_count top decile (>= {tc_p90:.0f}) ===")
|
|
print(f" fraction: {top_tc.mean()*100:.2f}% ({top_tc.sum():,} rows)")
|
|
|
|
# ─── PRIMARY TEST: vol ratios ───
|
|
base_abs = df["fwd_60_bps"].abs().mean()
|
|
print()
|
|
print("=" * 90)
|
|
print("PRIMARY TEST — |fwd_60_bps| vol ratios vs full sample")
|
|
print("=" * 90)
|
|
print(f" baseline |fwd_60| mean (all minutes): {base_abs:.3f} bps")
|
|
print()
|
|
|
|
|
|
def vol_report(mask, name):
|
|
n = int(mask.sum())
|
|
if n < 50:
|
|
print(f" {name:<60} n={n:>6,} too small")
|
|
return
|
|
sub = df.loc[mask, "fwd_60_bps"].abs()
|
|
ratio = sub.mean() / base_abs
|
|
print(f" {name:<60} n={n:>6,} mean|y|={sub.mean():>6.2f}bps ratio={ratio:.3f}x")
|
|
|
|
|
|
vol_report(top_tc, "top decile trade_count (validated baseline)")
|
|
vol_report(high, "lattice HIGH TENSION (all)")
|
|
vol_report(low, "lattice LOW TENSION (all)")
|
|
vol_report(high & top_tc, "HIGH ∩ top-decile trade_count <-- THE TEST")
|
|
vol_report(low & top_tc, "LOW ∩ top-decile trade_count")
|
|
vol_report(neutral & top_tc, "NEUTRAL ∩ top-decile trade_count")
|
|
|
|
print()
|
|
top_ratio = (df.loc[top_tc, "fwd_60_bps"].abs().mean() / base_abs)
|
|
hi_top_ratio = (df.loc[high & top_tc, "fwd_60_bps"].abs().mean() / base_abs)
|
|
print(f" top-decile trade_count ratio: {top_ratio:.3f}x")
|
|
print(f" HIGH ∩ top-decile trade_count: {hi_top_ratio:.3f}x")
|
|
diff = hi_top_ratio - top_ratio
|
|
print(f" lattice lift over raw top-decile: {diff:+.3f}x ({diff/top_ratio*100:+.1f}%)")
|
|
print(f" success criterion (Claude): > 2.42x -> {'PASS' if hi_top_ratio > 2.42 else 'FAIL'}")
|
|
|
|
# ─── SECONDARY TEST: direction for HIGH TENSION minutes ───
|
|
print()
|
|
print("=" * 90)
|
|
print("SECONDARY TEST — direction for HIGH TENSION minutes")
|
|
print("=" * 90)
|
|
sub = df.loc[high].copy()
|
|
sub = sub.dropna(subset=["taker_buy_usd_z", "taker_sell_usd_z", "stress_xx", "stress_yy"])
|
|
print(f" n HIGH minutes (after dropna z+stress): {len(sub):,}")
|
|
|
|
if len(sub) > 100:
|
|
# direct injected-signal direction
|
|
buy_minus_sell = sub["taker_buy_usd_z"] - sub["taker_sell_usd_z"]
|
|
stress_xx_minus_yy = sub["stress_xx"] - sub["stress_yy"]
|
|
|
|
print()
|
|
print(f" Spearman (buy_z - sell_z, fwd_60): "
|
|
f"{buy_minus_sell.corr(sub.fwd_60_bps, method='spearman'):+.4f}")
|
|
print(f" Spearman (stress_xx - stress_yy, fwd_60): "
|
|
f"{stress_xx_minus_yy.corr(sub.fwd_60_bps, method='spearman'):+.4f}")
|
|
|
|
# directional accuracy of each sign-based predictor
|
|
def dacc(predictor, y):
|
|
valid = predictor.notna() & y.notna() & (predictor != 0) & (y != 0)
|
|
if valid.sum() == 0:
|
|
return None, 0
|
|
return (np.sign(predictor[valid]) == np.sign(y[valid])).mean() * 100, int(valid.sum())
|
|
|
|
d, n = dacc(buy_minus_sell, sub.fwd_60_bps)
|
|
print(f" dacc sign(buy_z - sell_z) vs sign(fwd_60): {d:.2f}% (n={n:,})")
|
|
d, n = dacc(stress_xx_minus_yy, sub.fwd_60_bps)
|
|
print(f" dacc sign(stress_xx - stress_yy) vs sign(fwd_60): {d:.2f}% (n={n:,})")
|
|
|
|
# mean signed return when each predictor is positive vs negative
|
|
pos = buy_minus_sell > 0
|
|
neg = buy_minus_sell < 0
|
|
print()
|
|
print(f" mean fwd_60 when buy_z > sell_z: {sub.loc[pos, 'fwd_60_bps'].mean():+.3f} bps (n={pos.sum():,})")
|
|
print(f" mean fwd_60 when buy_z < sell_z: {sub.loc[neg, 'fwd_60_bps'].mean():+.3f} bps (n={neg.sum():,})")
|
|
|
|
pos_s = stress_xx_minus_yy > 0
|
|
neg_s = stress_xx_minus_yy < 0
|
|
print(f" mean fwd_60 when xx > yy: {sub.loc[pos_s, 'fwd_60_bps'].mean():+.3f} bps (n={pos_s.sum():,})")
|
|
print(f" mean fwd_60 when xx < yy: {sub.loc[neg_s, 'fwd_60_bps'].mean():+.3f} bps (n={neg_s.sum():,})")
|