research: event_leadlag (T9) — POSITIVE: same-outcome siblings reprice slowly after leader bursts

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 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-23 15:16:48 -04:00
parent 0b12820eeb
commit e6287c788d
+124
View File
@@ -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()