"""Pure-compute calibration pass. No injections. Pull all April BTC + funding, compute 4hr levels and 4hr velocities, report distributions so we can set MAP_SCALE values that produce strength p90 ~ 0.5-1.0 (responsive but not saturated). """ import glob, json, urllib.request, time import numpy as np, pandas as pd from pathlib import Path DATA = "/mnt/d/PaperTrader/research/hl_data/minutes" LEVEL_WIN = 240 ROLL_ACT = 500 ROLL_CVD = 60 EPS_BPS = 0.5 # load BTC april files=sorted(glob.glob(f"{DATA}/202604*/*.parquet")) print(f"loading {len(files)} files...") dfs=[pd.read_parquet(f) for f in files] df=pd.concat(dfs,ignore_index=True) df=df[df.coin=="BTC"].sort_values("minute").drop_duplicates("minute").reset_index(drop=True) print(f"{len(df):,} minutes BTC") # funding (re-fetch or use cached) cache = Path("/tmp/april_funding_verify.parquet") if cache.exists(): fnd = pd.read_parquet(cache) fnd["minute_hour"] = (fnd["time"].astype("int64") // (1000*60)) fnd["funding_bps_annual"] = fnd["fundingRate"].astype(float)*8760*10000 fnd = fnd[["minute_hour","funding_bps_annual"]] else: raise SystemExit("need /tmp/april_funding_verify.parquet") print(f"{len(fnd)} funding rows") # tensions df["minute_hour_floor"]=(df.minute//60)*60 df=df.merge(fnd.rename(columns={"minute_hour":"minute_hour_floor"}),on="minute_hour_floor",how="left") df["funding_bps"]=df["funding_bps_annual"].ffill() denom=(df.taker_buy_usd+df.taker_sell_usd).replace(0,np.nan) df["bs_ratio_signed"]=(df.taker_buy_usd/denom)-0.5 rm=df.trade_count.astype(float).shift(1).rolling(ROLL_ACT,min_periods=50).mean() df["activity_excess"]=(df.trade_count.astype(float)/rm)-1.0 sflow=df.signed_flow_usd.astype(float) cvd60=sflow.shift(1).rolling(ROLL_CVD,min_periods=10).sum() px=df.mid_price.astype(float) pn=px.shift(1); pt=px.shift(1+ROLL_CVD) chg=((pn-pt)/pt*10000).abs() df["cvd_divergence"]=cvd60/(chg+EPS_BPS) # 4hr levels for c in ["funding_bps","bs_ratio_signed","activity_excess","cvd_divergence"]: df[f"{c}_level"]=df[c].astype(float).shift(1).rolling(LEVEL_WIN,min_periods=60).mean() # 4hr velocities for c in ["funding_bps","bs_ratio_signed","activity_excess","cvd_divergence"]: df[f"{c}_vel"]=df[f"{c}_level"]-df[f"{c}_level"].shift(LEVEL_WIN) print() print(f"{'tension':<28} {'n_fin':>7} {'mean':>10} {'std':>10} {'p5':>10} {'p50':>10} {'p95':>10} {'p99':>10} {'absp50':>10} {'absp90':>10} {'absp99':>10}") for col in ["funding_bps","funding_bps_level","funding_bps_vel", "bs_ratio_signed","bs_ratio_signed_level","bs_ratio_signed_vel", "activity_excess","activity_excess_level","activity_excess_vel", "cvd_divergence","cvd_divergence_level","cvd_divergence_vel"]: v = pd.to_numeric(df[col], errors="coerce").dropna() av = v.abs() print(f" {col:<26} {len(v):>7} {v.mean():>+10.3g} {v.std():>10.3g} {v.quantile(.05):>+10.3g} {v.quantile(.5):>+10.3g} {v.quantile(.95):>+10.3g} {v.quantile(.99):>+10.3g} {av.quantile(.5):>10.3g} {av.quantile(.9):>10.3g} {av.quantile(.99):>10.3g}") # proposed scales: per_strength = absp90 (so q90 maps to strength ~1.0) print() print("=== PROPOSED SCALES (per_strength = |val| q90 -> strength 1.0) ===") for col,prefix in [("funding_bps_level","funding_level"),("bs_ratio_signed_level","bsratio_level"), ("activity_excess_level","activity_level"),("cvd_divergence_level","cvd_level"), ("funding_bps_vel","funding_vel"),("bs_ratio_signed_vel","bsratio_vel"), ("activity_excess_vel","activity_vel"),("cvd_divergence_vel","cvd_vel")]: av = df[col].abs().dropna() if len(av)==0: continue p90 = av.quantile(.9); p99 = av.quantile(.99) print(f" {prefix:<22} per_strength = {p90:.3g} (q99 would map to {p99/p90:.2f}, suggest cap = {min(p99/p90*1.1, 5.0):.1f})")