From e6287c788dadc0409ce091abd7f21ff4a15ae4cc Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 23 Jul 2026 15:16:48 -0400 Subject: [PATCH] =?UTF-8?q?research:=20event=5Fleadlag=20(T9)=20=E2=80=94?= =?UTF-8?q?=20POSITIVE:=20same-outcome=20siblings=20reprice=20slowly=20aft?= =?UTF-8?q?er=20leader=20bursts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2,191 episodes (400 highest-volume event/outcome groups, name-matched semantics only): follower drift +4.12c mean in leader direction at +5m (43% >+2c vs 17% adverse; p50=0 — thin siblings often don't print). Tradable leg chain-true: n=2,028 · EV +$9.73/$100 buying the follower at its last print in the leader's direction. STATED OPTIMISM: stale-print entry (the T4 lesson — prints are not books); resting asks may have repriced without printing. Stage-2 = execution realism (live book reads or a paper scanner leg). Theme with T6: edge lives where repricing is SLOW — the far side of the requote wall. Co-Authored-By: Claude Fable 5 --- research/event_leadlag.py | 124 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 research/event_leadlag.py diff --git a/research/event_leadlag.py b/research/event_leadlag.py new file mode 100644 index 00000000..9a7894b8 --- /dev/null +++ b/research/event_leadlag.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""T9 EXPLORATORY (2026-07-23) — same-event lead-lag: when an event's most +-traded market moves hard in-play, do sibling markets carrying THE SAME +OUTCOME NAME reprice with a fillable lag? + +Semantic mapping problem solved narrowly: direction is only claimed where +the follower has an outcome with the exact same (lowercased) name as the +leader's moved outcome (team/player name) — moneyline vs map/set/half +winner vs series markets. No claim on O/Us or unrelated props. + +v0 method: cond→event + cond→{outcome→asset} from orders_matched. Leader +per event = most prints. Burst = leader outcome's print moving >= 10c +within 120s (in-play), cooldown 600s/event. Follower read at burst t: +last print p0; drift = p(t+300s) − p0 in the leader-move direction; +tradable leg = buy follower at p0, grade to chain (payouts_for). +Kill: drift <= fees (~2c) at n>=300 episodes, or chain EV <= 0.""" +import sys +import time + +sys.path.insert(0, "/Users/jaxmakielski/polymarket-smart-money/research") +import tape # noqa: E402 +import forward as fwd # noqa: E402 + +MOVE_C = 0.10 +MOVE_WIN = 120 +DRIFT_WIN = 300 +COOLDOWN = 600 +BAND = (0.05, 0.95) + + +def main(): + db = tape.connect() + tape.build_resolved(db) + print("building event/outcome maps…", flush=True) + db.execute(""" + CREATE TEMP TABLE om AS + SELECT json_extract_string(payload,'$.eventSlug') ev, + json_extract_string(payload,'$.conditionId') cond, + lower(json_extract_string(payload,'$.outcome')) outc, + json_extract_string(payload,'$.asset') asset, + count(*) n + FROM aux WHERE type='orders_matched' + AND json_extract_string(payload,'$.eventSlug') IS NOT NULL + GROUP BY 1,2,3,4""") + # events with >=2 conds sharing an outcome name (the mappable set) + pairs = db.execute(""" + WITH x AS (SELECT ev, outc, count(DISTINCT cond) nc, sum(n) vol + FROM om WHERE outc NOT IN ('yes','no','over','under','') + GROUP BY 1,2 HAVING count(DISTINCT cond) >= 2) + SELECT ev, outc FROM x ORDER BY vol DESC LIMIT 400""").fetchall() + print(f"mappable (event, outcome) groups: {len(pairs)}", flush=True) + episodes = [] + for gi, (ev, outc) in enumerate(pairs): + toks = db.execute("""SELECT cond, asset, n FROM om + WHERE ev=? AND outc=?""", [ev, outc]).fetchall() + if len(toks) < 2: + continue + toks.sort(key=lambda r: -r[2]) + lead_asset = toks[0][1] + followers = [r[1] for r in toks[1:3]] # top-2 followers + prints = db.execute("""SELECT ts, price::DOUBLE FROM trades + WHERE asset=? ORDER BY ts""", [lead_asset]).fetchall() + last_ep = 0.0 + for i in range(1, len(prints)): + ts, p = prints[i] + if ts - last_ep < COOLDOWN: + continue + j = i - 1 + while j >= 0 and ts - prints[j][0] <= MOVE_WIN: + j -= 1 + if j < 0 or j == i - 1: + base = prints[max(j, 0)][1] + else: + base = prints[j + 1][1] + mv = p - base + if abs(mv) < MOVE_C: + continue + last_ep = ts + for fa in followers: + r0 = db.execute("""SELECT price::DOUBLE FROM trades + WHERE asset=? AND ts<=? ORDER BY ts DESC LIMIT 1""", + [fa, ts]).fetchone() + r1 = db.execute("""SELECT price::DOUBLE FROM trades + WHERE asset=? AND ts<=? ORDER BY ts DESC LIMIT 1""", + [fa, ts + DRIFT_WIN]).fetchone() + if not r0 or not r1: + continue + p0, p1 = r0[0], r1[0] + if not (BAND[0] <= p0 <= BAND[1]): + continue + sgn = 1 if mv > 0 else -1 + episodes.append({"ev": ev, "a": fa, "ts": ts, "sgn": sgn, + "p0": p0, "drift": (p1 - p0) * sgn}) + if (gi + 1) % 100 == 0: + print(f" … {gi+1}/{len(pairs)} groups · " + f"{len(episodes)} episodes", flush=True) + print(f"episodes: {len(episodes)}", flush=True) + if not episodes: + return + d = sorted(e["drift"] for e in episodes) + n = len(d) + print(f"follower drift(+{DRIFT_WIN}s, leader direction): " + f"mean {sum(d)/n*100:+.2f}c · p50 {d[n//2]*100:+.2f}c · " + f"frac>+2c {sum(x > 0.02 for x in d)/n:.0%} · " + f"frac<-2c {sum(x < -0.02 for x in d)/n:.0%}", flush=True) + pays = fwd.payouts_for(db, [e["a"] for e in episodes]) + graded = [] + for e in episodes: + p = pays.get(e["a"]) + if p is None or p == 0.5: + continue + side_px = e["p0"] if e["sgn"] > 0 else 1 - e["p0"] + side_pay = p if e["sgn"] > 0 else 1 - p + if not (BAND[0] <= side_px <= BAND[1]): + continue + graded.append(100.0 / side_px * (side_pay - side_px)) + if graded: + print(f"tradable leg (buy follower in leader direction, chain): " + f"n={len(graded)} · EV/$100 " + f"{sum(graded)/len(graded):+.2f}", flush=True) + + +if __name__ == "__main__": + main()