From 052eda04d1e6d72ec94642ab2028fa02c3cfeb82 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 23 Jul 2026 15:49:49 -0400 Subject: [PATCH] =?UTF-8?q?research:=20Study=20C=20FREEZE=20=E2=80=94=20ma?= =?UTF-8?q?ker=20inventory-lean=20pre-registration=20(follow=20arm=20verdi?= =?UTF-8?q?ct;=20fade=20failed=20its=20concentration=20gate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit score_lean wired into the nightly (walk-forward as-of screening, frozen trigger, chain truth); params/study_lean.json frozen; forward window = ledger rows dated after this commit. Fade $2k+ arm excluded by the pre-declared gate (top event 32%, tail net-negative) — tracked report-only. Follow $150-500: robust across 869 events (top 14%). Co-Authored-By: Claude Fable 5 --- .gitignore | 1 + research/forward.py | 60 ++++++++++++++++++++++++ research/maker_lean.py | 82 +++++++++++++++++++-------------- research/params/study_lean.json | 21 +++++++++ 4 files changed, 129 insertions(+), 35 deletions(-) create mode 100644 research/params/study_lean.json diff --git a/.gitignore b/.gitignore index 4dc7babb..3eaf0fa4 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ research/meta/ research/.surge*.pull.* research/.oracle*.pull.* research/replay_out/ +research/.maker_lean_triggers.json diff --git a/research/forward.py b/research/forward.py index ca040057..41f4e551 100644 --- a/research/forward.py +++ b/research/forward.py @@ -180,6 +180,56 @@ def score_oracle(db, P, d, hold_s): return row +def score_lean(db, d): + """Study C (maker inventory-lean, pre-registered): walk-forward day row. + Screen as-of day start (maker_lean.screen_asof — tape strictly before + the day), frozen trigger (maker_lean.day_leans), chain truth via + payouts_for. Arms: follow_small ($150-500) · fade_whale ($2k+) · mid + bucket report-only. Scored at last-print entry (stated optimism — a + PASS graduates to a real-execution paper arm, never to money).""" + import maker_lean as ml + lo, hi = day_bounds(d) + t_max = db.execute( + "SELECT max(ts) FROM aux WHERE type='orders_matched'").fetchone()[0] + hi = min(hi, t_max or 0) + if hi <= lo: + return {"skipped": "no maker-stream coverage"} + tape.build_resolved(db) + sharps = ml.screen_asof(db, lo) + if not sharps: + return {"skipped": "no screened wallets as-of day"} + leans = ml.day_leans(db, lo, hi, sharps) + pays = payouts_for(db, [t["a"] for t in leans]) + row = {"screened": len(sharps), "leans": len(leans)} + arms = {"follow_small": ("follow", lambda u: u < 500), + "mid_report": ("follow", lambda u: 500 <= u < 2000), + "fade_whale": ("fade", lambda u: u >= 2000)} + for name, (direction, sel) in arms.items(): + n = pend = wins = 0 + pnl = 0.0 + for t in leans: + if not sel(t["lean_usd"]): + continue + p = pays.get(t["a"]) + if p is None: + pend += 1 + continue + if p == 0.5: + continue + lean_pay = p if t["side"] > 0 else 1 - p + px, pay = ((t["lean_px"], lean_pay) if direction == "follow" + else (1 - t["lean_px"], 1 - lean_pay)) + if not (0.05 <= px <= 0.95): + continue + n += 1 + pnl += 100.0 / px * (pay - px) + wins += pay == 1.0 + row[name] = {"n": n, "pending": pend, "pnl": round(pnl, 2), + "ev_per_lean": round(pnl / n, 2) if n else None, + "hit": round(wins / n, 3) if n else None} + return row + + def main(): db = tape.connect() cal = json.load(open(os.path.join(HERE, "params", "sim_calibration.json"))) @@ -248,6 +298,16 @@ def main(): print(f"sub5c {d}: trig {r3['triggers']} " f"worst {r3['worst'].get('ev_per_fill')} " f"({r3['worst']['fills']} fills, {r3['worst']['pending']} pend)") + r5 = score_lean(db, d) + fh.write(json.dumps({"study": "lean", "day": d, + "computed_at": now, **r5}, + default=float) + "\n") + print(f"lean {d}: " + (r5.get("skipped") or + f"{r5['leans']} leans · follow_small " + f"{r5['follow_small'].get('ev_per_lean')} " + f"({r5['follow_small']['n']}n) · fade_whale " + f"{r5['fade_whale'].get('ev_per_lean')} " + f"({r5['fade_whale']['n']}n)")) if __name__ == "__main__": diff --git a/research/maker_lean.py b/research/maker_lean.py index 85358ad0..cadb0eae 100644 --- a/research/maker_lean.py +++ b/research/maker_lean.py @@ -17,6 +17,7 @@ FROZEN v0 params (declared before the run, not tuned after): price lean-side last print in [0.05, 0.95] at trigger score $100 at trigger print -> chain payout; follow-EV and fade-EV Kill bar: BOTH directions EV <= 0 at n>=100 leans.""" +import os import sys import time @@ -63,6 +64,43 @@ def screen_asof(db, t_cut): return out +def day_leans(db, lo, hi, sharps): + """First lean crossings for screened wallets in [lo,hi) — the frozen + trigger (used by the exploration AND forward.py's nightly scoring).""" + rows = db.execute(""" + SELECT lower(json_extract_string(payload,'$.proxyWallet')) w, + json_extract_string(payload,'$.asset') a, + json_extract_string(payload,'$.side') s, + cast(json_extract(payload,'$.price') AS DOUBLE) p, + cast(json_extract(payload,'$.size') AS DOUBLE) z, ts + FROM aux WHERE type='orders_matched' AND ts >= ? AND ts < ? + ORDER BY ts""", [lo, hi]).fetchall() + book, fired, out = {}, set(), [] + for w, a, s_, p, z, ts in rows: + if w not in sharps or (w, a) in fired: + continue + st = book.setdefault((w, a), [0.0, 0.0]) + st[0] += z if s_ == "BUY" else -z + st[1] += z + net, gross = st + if gross < 1e-9: + continue + px = db.execute("""SELECT price FROM trades WHERE asset=? + AND ts<=? ORDER BY ts DESC LIMIT 1""", [a, ts]).fetchone() + if px is None: + continue + px = float(px[0]) + lean_px = px if net > 0 else 1 - px + if (abs(net) * px >= LEAN_USD and abs(net) / gross >= NET_GROSS + and BAND[0] <= lean_px <= BAND[1]): + fired.add((w, a)) + out.append({"w": w, "a": a, "ts": ts, + "side": 1 if net > 0 else -1, + "lean_usd": abs(net) * px, + "px": px, "lean_px": lean_px}) + return out + + def main(): db = tape.connect() t_lo, t_hi = db.execute( @@ -79,43 +117,17 @@ def main(): if not sharps: print(f"{d_str}: 0 screened wallets", flush=True) continue - rows = db.execute(""" - SELECT lower(json_extract_string(payload,'$.proxyWallet')) w, - json_extract_string(payload,'$.asset') a, - json_extract_string(payload,'$.side') s, - cast(json_extract(payload,'$.price') AS DOUBLE) p, - cast(json_extract(payload,'$.size') AS DOUBLE) z, ts - FROM aux WHERE type='orders_matched' AND ts >= ? AND ts < ? - ORDER BY ts""", [lo, hi]).fetchall() - book = {} # (w,a) -> [net, gross, vwap$] - fired = set() - n_day = 0 - for w, a, s, p, z, ts in rows: - if w not in sharps or (w, a) in fired: - continue - st = book.setdefault((w, a), [0.0, 0.0]) - st[0] += z if s == "BUY" else -z - st[1] += z - net, gross = st - if gross < 1e-9: - continue - px = db.execute("""SELECT price FROM trades WHERE asset=? - AND ts<=? ORDER BY ts DESC LIMIT 1""", [a, ts]).fetchone() - if px is None: - continue - px = float(px[0]) - lean_px = px if net > 0 else 1 - px # lean-side price - if (abs(net) * px >= LEAN_USD - and abs(net) / gross >= NET_GROSS - and BAND[0] <= lean_px <= BAND[1]): - fired.add((w, a)) - n_day += 1 - triggers.append({"w": w, "a": a, "ts": ts, "day": d_str, - "side": 1 if net > 0 else -1, - "lean_usd": abs(net) * px, - "px": px, "lean_px": lean_px}) + found = day_leans(db, lo, hi, sharps) + for t in found: + t["day"] = d_str + triggers.extend(found) + n_day = len(found) print(f"{d_str}: {len(sharps)} screened · {n_day} leans", flush=True) print(f"total leans: {len(triggers)}", flush=True) + import json as _json + _json.dump(triggers, open(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + ".maker_lean_triggers.json"), "w")) pays = fwd.payouts_for(db, [t["a"] for t in triggers]) graded = [(t, pays.get(t["a"])) for t in triggers] graded = [(t, p) for t, p in graded if p is not None and p != 0.5] diff --git a/research/params/study_lean.json b/research/params/study_lean.json new file mode 100644 index 00000000..c07f315d --- /dev/null +++ b/research/params/study_lean.json @@ -0,0 +1,21 @@ +{ + "frozen_at": "2026-07-23 21:55 UTC", + "study": "C — maker inventory-lean (follow absorbed flow)", + "screen": {"source": "orders_matched maker fills, as-of day start (strictly prior tape)", + "min_z": 2.5, "min_bets": 6, "pnl_positive": true}, + "trigger": {"lean_usd_min": 150.0, "net_gross_min": 0.6, + "first_crossing_per_wallet_asset_day": true, + "lean_px_band": [0.05, 0.95]}, + "arms": { + "follow_small": {"role": "VERDICT", "range_usd": [150, 500], "direction": "follow", + "pass": "pooled forward EV >= +$2/lean AND hit >= 0.56 at n >= 1500 across >= 5 forward days", + "kill": "pooled forward EV <= 0 at n >= 1000"}, + "mid_report": {"role": "report-only", "range_usd": [500, 2000], "direction": "follow"}, + "fade_whale": {"role": "report-only — FAILED pre-declared concentration gate 2026-07-23", + "range_usd": [2000, null], "direction": "fade", + "gate_result": "top-event share 32% (>30% line); top-5 events +$7,799 vs +$5,911 total (tail net-negative); 87/224 events positive"} + }, + "scoring": "last-print entry (STATED OPTIMISM) -> chain truth via payouts_for; nightly rows study='lean' in forward_ledger.jsonl (forward.score_lean)", + "graduation": "PASS graduates to a real-execution paper arm (honest instrument), never directly to money", + "exploration": "research/maker_lean.py walk-forward 07-21..23: follow_small +$2.90-4.39/lean, 59% hit, positive all 3 days; concentration follow 14% top-event / 869 events" +}