Files
resonance-engine/inject_sn_battery.py
T
2026-06-06 17:34:30 +07:00

405 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""inject_sn_battery.py — signal-to-noise battery for per-variable lattice response
Decision authority: Claude Desktop, 2026-06-06.
Methodology
-----------
1. Baseline (10 min, no injects):
subscribe to 5556 telemetry, record stress_xx/yy/xy each sample,
compute σ per channel = the lattice's intrinsic noise floor.
2. Per-variable battery:
for each independent trade variable (6 of them) at 5 magnitudes
sampled from the real BTC day distribution (p10, p25, p50, p75, p90),
do exactly one isolated pulse:
- record pre-pulse stress (mean of last ~30s window)
- send single inject_density at (512, 512) sigma=32, strength encoded
- monitor next ~5s for peak |Δstress_*|
- wait for full decay (~150s ≈ 15k cycles) before next pulse
Record peak Δ per axis. S/N = peak_Δ / σ_baseline.
Output
------
/mnt/d/Resonance_Engine/sn_battery_results.jsonl
one record per (variable, magnitude) trial:
{
"variable": "signed_flow_usd",
"percentile": 90,
"raw_value": ...,
"z": ...,
"strength": ...,
"pre_cycle": ..., "post_cycle": ...,
"pre": {"stress_xx": ..., "stress_yy": ..., "stress_xy": ...,
"asymmetry": ..., "coherence": ...},
"peak_delta_abs": {"stress_xx": ..., ...}, # max |during-pre|
"peak_delta_signed": {"stress_xx": ..., ...}, # signed delta at moment of max abs
}
followed by one summary record at end with sigma_baseline and S/N table.
LLM observer is NOT in this loop. Fractonaut watches passively, can be
queried afterward.
Run inside WSL (Windows pyzmq can't reach WSL loopback):
cd /mnt/d/Resonance_Engine
setsid nohup python3 -u inject_sn_battery.py > /tmp/sn_battery.log 2>&1 < /dev/null & disown
"""
from __future__ import annotations
import glob, json, math, sys, threading, time
from collections import deque
from pathlib import Path
import pyarrow.parquet as pq
import pandas as pd
import numpy as np
import zmq
# -------- config --------
DATA_DAY_WSL = "/mnt/d/PaperTrader/research/hl_data/minutes/20260601"
COIN = "BTC"
TEL_ADDR = "tcp://127.0.0.1:5556"
CMD_ADDR = "tcp://127.0.0.1:5557"
OUT_PATH = Path("/mnt/d/Resonance_Engine/sn_battery_results.jsonl")
INJECT_X = 512.0
INJECT_Y = 512.0
INJECT_SIG = 32.0
# Strength encoding: clip(z/3, ±1) * 0.3 — same regime as v1
STR_CAP = 0.3
# Independent trade variables (drop redundant buy/sell, signed_flow has both)
VARS = [
"mid_price",
"signed_flow_usd",
"trade_count",
"large_print_cnt",
"wallet_entropy",
"vwap_drift",
]
# Magnitudes sampled from real distribution
PERCENTILES = [10, 25, 50, 75, 90]
# Timing (wall-clock seconds; daemon runs ~100 cycle/s)
BASELINE_S = 600 # 10 min baseline
PEAK_WATCH_S = 5 # post-inject capture window for finding peak Δ
DECAY_S = 150 # full-decay wait before next pulse (~15k cycles)
# Channels we care about for S/N
STRESS_CHANNELS = ["stress_xx", "stress_yy", "stress_xy"]
EXTRA_CHANNELS = ["asymmetry", "coherence", "vel_mean", "vel_var", "vorticity_mean"]
ALL_CHANNELS = STRESS_CHANNELS + EXTRA_CHANNELS
# -------- telemetry subscriber thread --------
class TelemetrySub:
def __init__(self, addr):
self.ctx = zmq.Context()
self.sub = self.ctx.socket(zmq.SUB)
self.sub.setsockopt_string(zmq.SUBSCRIBE, "")
self.sub.connect(addr)
self.lock = threading.Lock()
self.latest = None
self.history = deque(maxlen=20000) # ~30 min at 10 samples/s
self.stop = False
self.thread = threading.Thread(target=self._run, daemon=True)
self.thread.start()
def _run(self):
while not self.stop:
try:
raw = self.sub.recv_string(flags=0)
msg = json.loads(raw)
msg["_recv_wall"] = time.time() # stamp arrival
with self.lock:
self.latest = msg
self.history.append(msg)
except Exception as e:
print(f"[tel] {e}", flush=True)
time.sleep(0.1)
def get_latest(self):
with self.lock:
return dict(self.latest) if self.latest else None
def get_history_since(self, t0):
with self.lock:
return [m for m in self.history if m.get("_recv_wall", 0) >= t0]
def snapshot_history(self):
with self.lock:
return list(self.history)
def close(self):
self.stop = True
self.sub.close()
self.ctx.term()
# -------- helpers --------
def load_btc_day():
files = glob.glob(str(Path(DATA_DAY_WSL) / "*.parquet"))
dfs = [pq.read_table(f).to_pandas() for f in files]
df = pd.concat(dfs).sort_values("minute").reset_index(drop=True)
return df[df.coin == COIN].reset_index(drop=True)
def percentile_strengths(df: pd.DataFrame):
"""For each VAR, for each percentile, pick the row at that percentile
of the variable and compute (raw, z, strength)."""
out = {}
for v in VARS:
s = df[v]
mu, sd = float(s.mean()), float(s.std())
out[v] = []
for p in PERCENTILES:
raw = float(np.percentile(s, p))
z = (raw - mu) / sd if sd > 0 else 0.0
z_clip = max(-3.0, min(3.0, z))
strength = (z_clip / 3.0) * STR_CAP
out[v].append({
"percentile": p,
"raw_value": raw,
"z": z,
"z_clip": z_clip,
"strength": strength,
})
return out
def send_pulse(pub, strength):
cmd = {
"cmd": "inject_density",
"x": INJECT_X,
"y": INJECT_Y,
"sigma": INJECT_SIG,
"strength": strength,
}
pub.send_string(json.dumps(cmd))
def channel_stats(samples, ch):
vals = [s[ch] for s in samples if ch in s]
if not vals:
return None
arr = np.array(vals, dtype=float)
return {
"n": len(arr),
"mean": float(arr.mean()),
"std": float(arr.std()),
"min": float(arr.min()),
"max": float(arr.max()),
}
def appendln(rec):
with OUT_PATH.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(rec) + "\n")
# -------- main --------
def main():
OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
# truncate
OUT_PATH.write_text("", encoding="utf-8")
print(f"[BAT] start {time.strftime('%Y-%m-%dT%H:%M:%S')}", flush=True)
appendln({"event": "battery_start", "wall_iso": time.strftime("%Y-%m-%dT%H:%M:%S"),
"params": {"baseline_s": BASELINE_S, "peak_watch_s": PEAK_WATCH_S,
"decay_s": DECAY_S, "vars": VARS, "percentiles": PERCENTILES,
"x": INJECT_X, "y": INJECT_Y, "sigma": INJECT_SIG,
"str_cap": STR_CAP}})
# ---- load data, compute strengths ----
print(f"[BAT] loading {COIN} parquet day", flush=True)
df = load_btc_day()
strengths = percentile_strengths(df)
for v in VARS:
for tr in strengths[v]:
print(f" {v:18s} p{tr['percentile']:>2} raw={tr['raw_value']:>+15.4f} "
f"z={tr['z']:>+6.2f} str={tr['strength']:>+7.4f}", flush=True)
appendln({"event": "trial_grid", "strengths": strengths})
# ---- start subscribers ----
tel = TelemetrySub(TEL_ADDR)
ctx_pub = zmq.Context()
pub = ctx_pub.socket(zmq.PUB)
pub.connect(CMD_ADDR)
time.sleep(0.7) # slow joiner
# ---- phase 1: baseline ----
print(f"[BAT] phase 1: {BASELINE_S}s baseline (no injects)", flush=True)
t0 = time.time()
# wait until telemetry actually arriving
while tel.get_latest() is None and time.time() - t0 < 30:
time.sleep(0.5)
if tel.get_latest() is None:
print("[BAT] FATAL: no telemetry on 5556 within 30s", flush=True)
sys.exit(1)
baseline_start_ts = time.time()
baseline_start_cycle = tel.get_latest().get("cycle", -1)
# let it run
end = baseline_start_ts + BASELINE_S
while time.time() < end:
time.sleep(5)
l = tel.get_latest()
if l:
print(f" [baseline t+{int(time.time()-baseline_start_ts):4d}s] "
f"cycle={l.get('cycle','?')} "
f"asym={l.get('asymmetry',0):.4f} "
f"sxx={l.get('stress_xx',0):+.5f} "
f"syy={l.get('stress_yy',0):+.5f} "
f"sxy={l.get('stress_xy',0):+.5f}", flush=True)
baseline_samples = tel.get_history_since(baseline_start_ts)
print(f"[BAT] baseline collected {len(baseline_samples)} samples", flush=True)
baseline_stats = {ch: channel_stats(baseline_samples, ch) for ch in ALL_CHANNELS}
appendln({"event": "baseline_stats",
"n_samples": len(baseline_samples),
"start_cycle": baseline_start_cycle,
"stats": baseline_stats})
print("[BAT] baseline σ:", flush=True)
for ch in STRESS_CHANNELS:
s = baseline_stats[ch]
if s:
print(f" {ch:14s} mean={s['mean']:+.6f} std={s['std']:.6f} "
f"range=[{s['min']:+.6f},{s['max']:+.6f}]", flush=True)
# ---- phase 2: per-variable pulses ----
# Build flat trial list, shuffle for fairness (avoid ordering bias)
trials = []
for v in VARS:
for tr in strengths[v]:
trials.append({"variable": v, **tr})
rng = np.random.default_rng(seed=42)
rng.shuffle(trials)
print(f"[BAT] phase 2: {len(trials)} trials, ~{(PEAK_WATCH_S+DECAY_S)*len(trials)/60:.1f} min", flush=True)
appendln({"event": "trial_order",
"order": [(t["variable"], t["percentile"]) for t in trials]})
for i, trial in enumerate(trials):
var = trial["variable"]
pct = trial["percentile"]
strn = trial["strength"]
# pre-pulse snapshot: last 5s before fire
pre_window_t = time.time() - 5.0
pre_samples = [s for s in tel.snapshot_history()
if s.get("_recv_wall", 0) >= pre_window_t]
pre_stats = {ch: channel_stats(pre_samples, ch) for ch in ALL_CHANNELS}
latest_pre = tel.get_latest() or {}
pre_cycle = latest_pre.get("cycle", -1)
pre_means = {ch: (pre_stats[ch]["mean"] if pre_stats[ch] else None) for ch in ALL_CHANNELS}
# fire
wall_fire = time.time()
send_pulse(pub, strn)
print(f"[BAT] trial {i+1:2d}/{len(trials)} {time.strftime('%H:%M:%S')} "
f"{var:18s} p{pct:>2} str={strn:+7.4f} pre_cycle={pre_cycle}",
flush=True)
# capture peak over next PEAK_WATCH_S seconds
peak_end = wall_fire + PEAK_WATCH_S
peak_abs = {ch: 0.0 for ch in ALL_CHANNELS}
peak_signed = {ch: 0.0 for ch in ALL_CHANNELS}
peak_at_cycle = {ch: None for ch in ALL_CHANNELS}
while time.time() < peak_end:
time.sleep(0.05)
l = tel.get_latest()
if not l:
continue
for ch in ALL_CHANNELS:
base = pre_means[ch]
cur = l.get(ch)
if base is None or cur is None:
continue
d = cur - base
if abs(d) > peak_abs[ch]:
peak_abs[ch] = abs(d)
peak_signed[ch] = d
peak_at_cycle[ch] = l.get("cycle")
post_cycle = (tel.get_latest() or {}).get("cycle", -1)
# write trial record
rec = {
"event": "trial",
"i": i,
"variable": var,
"percentile": pct,
"raw_value": trial["raw_value"],
"z": trial["z"],
"z_clip": trial["z_clip"],
"strength": strn,
"wall_iso": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(wall_fire)),
"pre_cycle": pre_cycle,
"post_cycle": post_cycle,
"pre_means": pre_means,
"peak_delta_abs": peak_abs,
"peak_delta_signed": peak_signed,
"peak_at_cycle": peak_at_cycle,
}
# immediate S/N preview using baseline σ
sn_preview = {}
for ch in STRESS_CHANNELS:
sigma = baseline_stats[ch]["std"] if baseline_stats[ch] else None
if sigma and sigma > 0:
sn_preview[ch] = peak_abs[ch] / sigma
rec["sn_preview_vs_baseline_sigma"] = sn_preview
appendln(rec)
print(f" peak |Δ| sxx={peak_abs['stress_xx']:.5f} "
f"syy={peak_abs['stress_yy']:.5f} "
f"sxy={peak_abs['stress_xy']:.5f} "
f"asym={peak_abs['asymmetry']:.4f} "
f"sn_xy={sn_preview.get('stress_xy',0):.1f}",
flush=True)
# wait full decay
time.sleep(DECAY_S)
# ---- phase 3: summary ----
print(f"[BAT] all trials done. Computing S/N table.", flush=True)
# Re-read jsonl, build table
trial_recs = []
with OUT_PATH.open() as fh:
for line in fh:
r = json.loads(line)
if r.get("event") == "trial":
trial_recs.append(r)
summary = {}
for r in trial_recs:
v = r["variable"]; p = r["percentile"]
key = f"{v}@p{p}"
sn = {}
for ch in STRESS_CHANNELS:
sigma = baseline_stats[ch]["std"] if baseline_stats[ch] else None
d = r["peak_delta_abs"].get(ch)
if sigma and sigma > 0 and d is not None:
sn[ch] = d / sigma
summary[key] = {
"strength": r["strength"],
"peak_delta_abs": {ch: r["peak_delta_abs"].get(ch) for ch in STRESS_CHANNELS},
"peak_delta_signed":{ch: r["peak_delta_signed"].get(ch) for ch in STRESS_CHANNELS},
"sn": sn,
}
appendln({"event": "summary",
"baseline_sigma": {ch: (baseline_stats[ch]["std"] if baseline_stats[ch] else None)
for ch in STRESS_CHANNELS},
"trials": summary})
print(f"[BAT] DONE. results: {OUT_PATH}", flush=True)
print(f"[BAT] {time.strftime('%Y-%m-%dT%H:%M:%S')}", flush=True)
pub.close()
ctx_pub.term()
tel.close()
if __name__ == "__main__":
main()