# research/ — the edge factory (SILO)

Standing rules (user directive 2026-07-20: "built in a silo to not affect
anything on the live bot"):

- **Nothing here is imported by, or imports, the bot** (`copybot.py`,
  `copytrade.py`, their configs). The Fly workers run pinned entrypoints;
  this directory is inert to them.
- **Tape access is read-only** (`duckdb.connect(..., read_only=True)`).
- The ONE shared write: `live/cache.duckdb::resolutions` via `live/payouts.py`
  — append-only immutable chain facts, the same store the daily pipeline
  already feeds. Nothing else in `live/` is touched.
- The existing paper bot is the live test's CONTROL — graduated edges get
  their own paper harness here, never that one.
- Studies are pre-registered (GitHub issue per study: hypothesis, params,
  verdict + kill criteria) BEFORE their forward window opens. Exploration
  happens on already-collected tape; **verdicts only come from
  `forward_ledger.jsonl` rows dated after the params freeze commit.**
- **SCORER LAW (2026-07-22, FINDINGS "round three"): every scorer resolves
  outcomes through `forward.payouts_for()` — tape proxy first, CTF chain
  overlay for the remainder. A "pending" bucket is NEVER ignorable on
  Polymarket: market liveness encodes the outcome (a losing bet's winning
  sibling keeps trading, so losses hide in pending while wins score).
  This bug inflated Study A from a true −$6/fill to +$46/fill.**
- **Every study gets one instrument that does not share the scorer's
  assumptions** — the surge paper harness (chain-graded from day one) is
  what caught the bug. Divergence between instruments is signal, not noise.
- **DEFINITION LAW (2026-07-28, from #26): a tape scorer and its harness
  must be shown to name the SAME EVENTS before any tape verdict is
  trusted.** Study C scored +$2.17/lean on tape and −$2.26/fill at a real
  book with entry price ruled out (median ask premium +1c, 72% fill).
  The crossing diagnostic (lean_crossing_diag.py) found only 51% of the
  harness's attempts had a matching tape crossing, and 84% of the tape's
  crossings were invisible to a live bot — the two were measuring
  different signals under one name. Exposure comes from triggers that
  depend on RECONSTRUCTED STATE (running inventory, rolling windows,
  cumulative flow): the scorer sees a whole day, the bot sees from boot.
  Point-in-time triggers (one print, one burst) are not exposed, which is
  why Study D's harness matched its Stage-1 definition and C's did not.
  For any state-dependent trigger the crossing diagnostic — not the P&L —
  is the FIRST check, and a harness should warm-start its state from tape
  rather than from zero.
- **DATA LAW (2026-07-22): tests are additive, never subtractive.** Raw
  streams (tape, attempts, markouts, settles, meta snapshots) are
  append-only; analysis opens them read-only; no script rewrites or
  rotates a raw stream. Bot-state trims exist ONLY where an append-only
  log on the volume carries the same rows durably (settles) or the nightly
  pull archives them (attempts/markouts). A study that would need to
  mutate a stream records a NEW stream instead.

Layout:
  tape.py     read-only loaders · tape proxy-resolution (terminal-VWAP +
              sibling veto — exact per-bet, but resolution TIMING is
              win-biased; see SCORER LAW) · title parsers · chain_overlay()
  sim.py      execution replayer calibrated on OUR live fills ledger
              (lag, FAK no-match, protected band, 3% taker fee)
  study_flow.py    Study A — surge momentum (#16 CLOSED 2026-07-23; A2
                   chain grade −$7.54/fill × 1,344 independently confirms)
  study_oracle.py  Study B — crypto oracle fair value (#17: E0.04 killed;
                   E≥0.07/0.1 ledger-positive but the harness chain grade
                   2026-07-23 reads −$8.51/−$5.90 per fill at real latency
                   — taker arm evidence-dead, tiers accrue to their formal
                   bars, maker pivot is the successor hypothesis)
  copy_edge_slices.py   T5 — parity-era copy edge by niche/lag/band/wallet
                        (esports carries it; feeds #14)
  copy_maker_entry.py   T3 — resting-bid copy entries beat taker FAKs
                        (+$17.45 vs +$12.86/signal; basis of #20)
  sell_mirror_study.py  sharp exits are bankroll ops, not signal
                        (basis of #21 hold-through)
  maker_sharps.py       T2 — 673 improbably-winning MAKERS in
                        orders_matched (86% invisible to taker screens);
                        follow-on: inventory-lean signal
  sibling_sum_scan.py   T4 — print-substrate sum-arb scan (artifact-
                        dominated; needs standing-book data; parked)
  maker_lean.py         T6 — WALK-FORWARD POSITIVE: follow small maker
                        inventory leans (+$2.51/lean, 59% hit, positive
                        all 3 days) → STUDY C #22 PRE-REGISTERED
                        (params/study_lean.json frozen 052eda0; follow
                        arm verdict-gated, fade $2k+ FAILED its
                        event-concentration gate — report-only; nightly
                        rows via forward.py score_lean, study:"lean")
  event_leadlag.py      T9 — POSITIVE: same-outcome siblings reprice
                        slowly after leader bursts (+$9.73/$100 chain,
                        n=2,028; stale-print entry optimism stated) →
                        STUDY D #23: wwf-lagbot harness (below) is the
                        Stage-2 execution-realism instrument
  settle_discount.py    T7 — settlement-discount niche: industrialized
                        by incumbents (+2-3%/hold for the good ones);
                        residual unproven (63% of vol outside
                        tape-resolution); parked at our scale
  miss_audit.py         T8 — guard counterfactual: guards earn their
                        keep; crater-rejects were GOOD misses (retry EV
                        follow-up queued)
  copy_age_slices.py    T10 — market-age gradient inconclusive at
                        n<=15/cell (6-24h positive both books;
                        age~market-type confound); reruns as fills accrue
  maker_quote_sim.py    T1 — crypto maker quoting at fair−m: STAGE-1
                        KILL 2026-07-23 (best cell +$0.37/fill at
                        m=4c/R=1s under queue-front optimism; every
                        R=4s cell −$7-9/fill — staleness IS the
                        adverse selection; ~31-50k graded fills/cell)
  copy_maker_patience.py  T11 — #20's 60s maker TTL is ALREADY OPTIMAL:
                        touches land in 1-2s, fill rate flat after 5m,
                        and the marginal late fills lower EV (loser
                        fill-rate 100% at 5m+ — patience past a minute
                        only harvests adverse selection)
  maker_unwind.py       T12 — maker-sharp unwinds are NOT exit signals:
                        STAY +$5.95/unwind (n=265, 2.6x the bar) vs
                        FADE -$5.22 — #21 one species over; Study C
                        keeps NO exit rule (concentration caveat in
                        the header; --grade-only re-grades the dump)
  depth_imbalance.py    T14 — book imbalance at fill is a BAND: extremes
                        adverse both ways, balanced 0.25-0.47 least-bad
                        in BOTH harnesses (portable #23 guard
                        candidate); slope "gate" = quantile-tie
                        artifact — check feature variance first
  sharp_halflife.py     T15 — published-set churn is violent: 49% of
                        detected sharps gone in 1 day, ~10% at 28d
                        (real as-of git history of watch_sharps.json);
                        EV-by-age = churn's meaning — pending its
                        81k-cond chain-ensure
  requote.py       crater→requote timing (feeds the bots' per-niche retry)
  forward.py       scores frozen studies on new tape days → forward_ledger
                   (payouts_for chain overlay mandatory; controls + sub5c
                   exploratory arm — 0-for-38, dead)
  informed_set.py  publishes params/informed_set.json nightly (surgebot input)
  surgebot.py      A2 MEASUREMENT harness (wwf-surgebot → jaxperro.com/test):
                   every cooldown-passed trigger paper-FAK'd at $100 against
                   the live asks; v1's cash-gated $100/5% book (halted at its
                   −50% line 2026-07-22) is replayed offline instead —
                   surge_book_replay.py → surge_book.json. v1 artifacts
                   frozen: surge_paper_ledger.jsonl + /data/surge_state.json.
  oraclebot.py     Study B real-time PAPER harness (wwf-oraclebot →
                   jaxperro.com/test): fair value tick-by-tick on the venue's
                   own settlement feed, all E-tiers tracked, three settle
                   layers (own-feed tick → CLOB flags → nightly chain truth)
  lagbot.py        Study D real-time PAPER harness (wwf-lagbot →
                   jaxperro.com/test, deployed 2026-07-23, SEM_VER l1):
                   leader bursts ≥10¢/120s → paper-FAK $100 on ≤2 lagging
                   same-outcome siblings at ≤ stale+4¢; DOWN-moves buy the
                   sibling's COMPLEMENT token; cooldown 600s/event, 30-min
                   warmup (leader maps built live from the stream). Every
                   attempt logs the ask premium over the stale print — the
                   observational kill-switch (median ≥+8¢ over 3 days =
                   mirage) can kill #23 without a paper sample
  grade_surge.py / grade_oracle.py / grade_lag.py   nightly chain-truth
                   re-grades → surge_meas / oracle_paper / lag_paper
                   ledgers (grade_lag also prints EV per episode AND
                   %-of-staked — partial fills can't flatter the verdict —
                   plus the median-premium kill-switch read); all three
                   pull the raw volume streams to local .pull copies
  markout_flow.py  exploratory markout-exit curve (chain-truth verdict: NO
                   scalp inside the dead surge signal — losers bleed from
                   minute one; v0's res_tok version was round-3-biased)
  meta_snap.py     nightly gzipped snapshot of ALL active markets (~12k/day,
                   research/meta/, local-only) — τ knowable at trigger for
                   every tape trigger; token→outcome maps close the
                   label-gap artifact class
  params/          frozen study parameters (committed = frozen)
  nightly.sh       launchd runner (fires 09:15, then WAITS for fresh tape —
                   Stage 0 keeps the tape ~15-min fresh whenever the Mac is
                   awake; deadline 8h, then scores anyway and logs staleness)

Data streams (the moat — all append-only, per DATA LAW):
  Fly volumes (accrue 24/7, Mac-independent; daily Fly snapshots ×5):
    wwf-recorder /data   full firehose → Parquet folds (25GB, ~3+ weeks of
                         Mac-offline headroom before the 85% disk guard)
    wwf-surgebot /data   surge_attempts.jsonl (every attempt + top-5 asks +
                         top-3 bids + latency) · surge_markouts.jsonl (book
                         re-reads +60/300/1800s per fill) · surge_settles
                         .jsonl (durable settles) · surge2_state.json
    wwf-oraclebot /data  oracle_attempts / oracle_markouts / oracle_settles
                         .jsonl + oracle_state.json (same shapes)
    wwf-lagbot /data     lag_attempts.jsonl (every attempt + top-5 asks +
                         premium vs stale + latency) · lag_settles.jsonl
                         (durable settles) · lag_state.json
  Mac (nightly pulls + git): ledgers + params committed; raw .pull copies
    and meta/ gzips local. forward.py backfills any tape-covered day the
    ledger has never seen — a Mac gap > RESCORE_DAYS leaves no holes.
