arb_anchor
arb_anchor lets a bot buy a Kalshi market only when the identical event is trading more expensively on Polymarket. Two exchanges, the same real-world question, two different prices — one of them is wrong, and this signal makes your bot wait for the cheap one. It is the closest thing in our library to an entry reason that needs no forecast at all. It is also, as you'll see below, not quite the thing its name says it is.
- Bots using it
- 3 (2 live, 1 never traded)
- Data source
- Kalshi + Polymarket public APIs
- Refresh
- Kalshi 1 min · Poly 2 min
- Markets it can see
- 26 of 1,276 open — a snapshot, it moves
- Backtest-replayable
- No — live engine only
- Anchor module
- quants/arb_anchor.py
Where the data comes from
Two exchanges, both public, both polled by us on cron into the same SQLite file. Nothing here is bought and nothing is scraped off a third party — these are the books' own market feeds.
Chain 1 — Kalshi, every minute
predictive/kalshi_poller.py runs from the wrapper predictive/run_poller.sh on cron
* * * * * — once a minute, guarded by flock so a slow run is skipped rather than
stacked on top of itself. It writes kalshi_markets in predictive.db: ticker,
yes_ask, yes_bid, volume, close_time. That table held 514,202
rows when this page was written, of which 1,276 were markets still open. Kalshi is the book your bot
actually fills on.
Chain 2 — Polymarket, every two minutes
predictive/polymarket_poller.py runs from predictive/run_poller_poly.sh on
*/2 * * * *, also flock-guarded. Its wrapper explains the slower cadence in one line:
Polymarket data moves more slowly than Kalshi brackets, and the API is rate-limited. It writes
polymarket_markets — 132,159 rows — with the title, prices, end_date and the
raw JSON, which is where the outcome list and the event slug live. Polymarket is the book we
price-check against. We never trade it.
The matcher — quants/arb.py
This is the part that matters, and it is stricter than you'd expect. Two markets are paired only when a rule proves they are the same bet. The module's docstring is blunt about why: a pair is emitted only when both legs are found by rule, never by fuzzy title-matching — because a fabricated match turns an ordinary price difference into a fake edge.
- Fed decisions. The Kalshi ticker
KXFEDDECISION-<YYMON>-<OUTCOME>decomposes into a meeting month and one of five outcome buckets — hold, cut 25, cut more than 25, hike 25, hike more than 25. The Polymarket side is found by parsing its question text for the same month and the same bucket, and explicitly excluding the Bank of Japan and Bank of England, which phrase their questions almost identically. - MLB games. Both team names are reduced to a canonical form and the game start time is recovered from each side, so a pair is made only when both books mean the same game. Settled and in-progress games are dropped; only genuine pre-game disagreements survive.
arb.ticker_index() then flattens those pairs into a lookup keyed by Kalshi ticker, holding the
gap in cents, which book is cheaper, and a blended fair value. It is cached for 120 seconds. That
index is the only thing quants/arb_anchor.py ever reads.
One oddity worth explaining, because you'll spot it on the
index table: our signal registry lists no poller and no table
for arb_anchor. That's honest rather than broken — the anchor module names neither, because it
reaches the data second-hand through arb.py. The two pollers above are the real answer.
What it means for your bot
You add arb_anchor to your entry rules with a click, and it takes two options:
min_gap_cents(default 3) — how far apart the two books must be before your bot is interested. The gap is the difference between the two YES ask prices, rounded to whole cents.cheaper_only(default true) — fire only when Kalshi is the cheaper of the two books to buy YES, so the fill your bot gets is the good side of the disagreement. Read catch #4 before you trust this one on a NO bot.
The practical consequence is that this signal is a brake, not an accelerator — the most restrictive
thing in our library. On the day this page was written, of 1,276 open Kalshi markets, 26 had a confirmed
cross-platform twin, and at the setting our own live bots use — min_gap_cents: 4 with
cheaper_only on — exactly 5 markets qualified. A bot with this signal attached spends
nearly all of its life doing nothing, on purpose.
That is the trade you are making. In exchange for near-total inactivity you get an entry reason that doesn't require you to predict anything. You are not saying the Fed will hold. You are saying two exchanges disagree about whether the Fed will hold, and you would like the cheaper of the two prices.
The catch
There are five, and the first one is the name.
1. It is line-shopping, not arbitrage
Real arbitrage means covering both outcomes — buy YES on the cheap book, buy NO on the expensive
one, pay less than a dollar for the pair, collect a dollar whichever way the event goes. The risk is gone.
That is not what this does. arb_anchor returns a plain yes-or-no, the engine then takes
one position, and it takes it on Kalshi. There is no second leg and no hedge. All 182 trades our
arb bots have recorded are single Kalshi fills.
So you keep the full event risk. If the Fed surprises everyone you lose — you simply lost a little less
than someone who paid the Polymarket price. This is line-shopping, the thing sports bettors do when they
take the better number across books, and it is a real expected-value edge. It is not free money, and a
signal called arb_anchor invites you to think it is. Our
arbitrage explainer makes the same point at more length.
2. The universe is two market series — and in practice it has been one
ticker_index() is built from exactly two functions: Fed decision pairs and MLB game pairs.
Nothing else on either exchange has a matching rule written for it, so every other market fails closed.
Crypto, weather, politics, awards — the gate cannot see them, and won't until someone writes another
matcher. Twenty-six of 1,276 was the whole board at the moment we measured it; the count moves as games
and Fed meetings open and close, but the two series are the hard limit.
It is narrower than that in practice. Across both live arb bots, over two months, every single trade
was an MLB game — ticker prefix KXMLBGAME, 182 of 182. Not one Fed trade, even though Fed
pairs are the majority of the matched index.
Three things quietly narrow it further, and none of them announce themselves:
- The Polymarket poller ingests only the top 1,500 markets by 24-hour volume. If the Polymarket leg of a genuine pair sits below that cut, the pair never exists and your bot skips a market that really is listed on both books.
- For MLB, a game is represented by one ticker only — the team with the bigger disagreement. The other side of the same game is not in the index at all.
- The index is keyed by Kalshi ticker, so if your bot's
platformslist includespoly, those markets can never pass this gate — there is no key for them to match against.
And consensus_anchor is built on the same matcher, so it inherits every one of these limits.
3. The backtester cannot replay it — and it will still hand you a curve
We don't store historical matched-pair gaps, so a replay has nothing to read. arb_anchor sits
on the backtester's unsupported list with that exact reason attached, dated 2026-06-10.
Here is the part to be careful about. The backtest does not refuse to run. It runs without the arb
gate, so every market that clears your other rules trades, and you get a P&L curve for a strategy you
did not build. The result does name arb_anchor in its unsupported_rules list, so
the site can tell you — but skim past that line and you are reading the backtest of a different bot
entirely. Judge an arb bot on forward results only.
4. cheaper_only only ever checks the YES side — which points a NO bot the wrong way
Read the check literally: it passes when the cheapest place to buy YES is Kalshi. NO costs roughly a
dollar minus YES, so if Kalshi's YES is the bargain then Kalshi's NO is the expensive one. A bot with
side: "NO" and cheaper_only: true is therefore being steered into precisely the
markets where Kalshi is the worse book for the trade it is about to place.
We know because we did it. Arb — Cheap NO is configured that way and has been live since June. It and its YES twin share one gate, so they fire on the same markets in the same instant on opposite sides, which makes them an unusually clean read on whether the direction check matters. Over 2026-06-11 to 2026-08-09, 88 closed trades each:
| Bot | Side | Closed | Wins | P&L |
|---|---|---|---|---|
| Arb — Cheap YES | YES | 88 | 47 | +$74.18 |
| Arb — Cheap NO | NO | 88 | 41 | −$96.46 |
| FOMC Arb Window | YES | 0 | — | — |
Eighty-eight trades is a small sample and we are not going to dress it up as proof. But the mechanism is
right there in the source, the split runs the way the mechanism predicts, and the honest summary is that
the leg the gate was pointed at made money while the leg it was pointed away from lost more. If you fork a
NO-side arb bot, turn cheaper_only off and think it through yourself.
The third bot deserves a line too. FOMC Arb Window stacks arb_anchor on top of
eco_anchor — an arb gap and being within two hours of an FOMC or CPI release — and
has never taken a single trade. Two fail-closed gates in series make for a very quiet bot.
5. The gap is a screen price, not a fill
The number the gate compares is each book's quoted YES ask, rounded to the cent. Three things it does not account for:
- Fees. Neither platform's costs are modelled. A gap of a few cents can be worth nothing once you have actually paid to be there.
- Depth. A 5¢ gap on one contract is not a 5¢ gap on a hundred. The gate looks at the top of the book and nothing behind it.
- Staleness. Polymarket is polled every 2 minutes and the matched-pair index is cached for a further 120 seconds, so worst case the gap your bot acts on is around four minutes old on the Polymarket side. Small gaps close in less time than that.
- For MLB, the Polymarket "price" isn't a quote at all. The Fed matcher compares real bids and asks from both books. The MLB matcher reads Polymarket's single outcome price out of the raw JSON and uses that same number as both the bid and the ask — so the one series our bots have actually traded is the series with the least reliable price on the far side of the comparison.
And a gap can be real for a boring reason — different resolution wording, a different settlement source, a different close time — in which case the disagreement is not an error to harvest, it is two contracts that are not quite the same bet. Guarding against that is exactly why the matcher only knows two series. Our bots trade $5 flat in simulated money, so none of this has ever cost us anything; all of it would cost real money.
Where to go next
The live version of this data is the arbitrage scanner — the same matched pairs, the same gaps, with a deep link into both books. If the idea is new, start with how prediction-market arbitrage works, or the glossary. The other profile written so far is wx_anchor, the most-used signal we have.
Every claim on this page traces to one of three things: the output of
quants/signal_registry.py, the source of quants/arb_anchor.py,
quants/arb.py, quants/backtest.py and the two pollers, or a read-only query against
our own database with the window stated. Cron schedules were read from the production crontab. Market counts
and the matched-pair index were read at 4:26 PM UTC on 2026-08-10 and will have moved since. P&L is
simulated — TinyCorp bots trade paper money only. Verified 2026-08-10.