Plain-English status doc. Read before you run anything against real money.
The bot is code-complete for Phase 1 (paper trading) and has a functional-but-unused skeleton for Phase 2 (live trading). It has never connected to the live Polymarket firehose yet, and it has never placed a real order. Everything that exists has been unit-tested against fake data — not yet validated against the real market.
| Piece | State | What that means |
|---|---|---|
| SQLite database layer | done | Tables for events, outcomes, opportunities, baskets, fills, resolutions. |
| Gamma REST discovery | done | Pulls the public list of Polymarket events and filters to the "negRisk" categoricals we can arb. |
| WebSocket L2 order book | done, never live-tested | Parses Polymarket's published message format. Hasn't shaken hands with the real server yet. |
| Opportunity engine (math) | done | For every event, walks the depth on every outcome, computes the best basket size, and reports the net edge after fees + gas. |
| Paper executor | done | Pretends to buy, models a 250 ms "someone beat me" penalty, records the outcome. No keys needed. |
| Live executor | wired, gated off | Will sign and submit orders through py-clob-client. Stays in dry-run mode unless you explicitly flip it. |
| Risk gate | done | Hard caps on basket size, open-basket count per event, daily loss, and a kill-switch file. |
| Dashboard | done | One HTML page, auto-refreshing. Shows opportunities, baskets, paper PnL, mode, and a kill button. |
| Resolution watcher | manual only | You can mark an event as resolved with arb resolve. There is no automatic poller yet — that's Phase 3. |
| Tests | 60 passing | ~5 seconds to run. Core math is ≥90% covered. HTTP/socket code is thinner (~55%). |
Bottom line: you can run it today in paper mode against live Polymarket data and watch what the engine would have done. You cannot yet point it at a funded wallet and walk away.
Polymarket hosts "categorical" markets — say, "Who wins the 2028 US Election?" with one YES token per candidate. Because exactly one candidate wins, the fair prices of every YES token must sum to exactly $1. When someone in the order book is selling those YES tokens cheaply enough that the sum of best offers adds up to less than $1, you can buy one of each and hold a guaranteed $1 per basket. The gap between that sum and $1 is the profit.
cd C:\Users\matt\Dropbox\projects\arbitrage .venv\Scripts\activate pip install -e . copy .env.example .env # defaults are fine for paper mode arb init # create the SQLite database
Open three terminals (or use a background process per step). In paper mode nothing you do here touches real money.
# Terminal 1 — keep the event list fresh arb discover --loop # Terminal 2 — start the scanner (WS + engine + paper executor) arb scan # Terminal 3 — dashboard arb web # open http://127.0.0.1:8000
Walk away for a few hours. Opportunities will log to the dashboard and into SQLite. Paper baskets will move from pending_resolution → redeemed (or invalid) as the underlying Polymarket events close out. At that point your paper realised-PnL column tells you whether the strategy would have been worth running.
Click the red kill button on the dashboard, or
touch KILL in the project folder. The executor refuses to open
any new baskets while that file exists.
arb resolve <negRiskMarketID> --winner <winning_token_id> arb resolve <negRiskMarketID> --winner invalid # UMA voided it
Edit .env:
ARB_MODE=live ARB_PRIVATE_KEY=0x... # dedicated wallet, small balance ARB_FUNDER_ADDRESS=0x... # same as wallet for EOA ARB_MAX_BASKET_USD=25 # keep it tiny for first real runs ARB_MAX_OPEN_BASKETS=1
Even after you do this, the live executor defaults to dry_run=True
in code — meaning it still logs what it would have signed rather than
broadcasting. To actually broadcast orders you have to change the
dry_run flag in arbitrage/cli.py. This is an
intentional safety belt.
Honest answer: the code will run. Whether it will make money is an open empirical question — and the default expectation should be "no, not yet."
Paper realised PnL of roughly +0.5% per basket, averaged across ≥50 baskets, with failure rate under a third. Below that, there isn't enough margin to survive the gap between simulated and live execution.
Above that, you still have to check that the opportunities it claims are scattered across many events (rather than concentrated in one lucky market that resolved favourably), and that the PnL is stable week-over-week rather than driven by one big win. Arbitrage should look boring and small on the balance curve — a straight line tilted gently up. If paper mode produces a hockey stick, something is wrong with the simulation and it will not replicate live.
Generated alongside commit for Phase 1 MVP, 60 passing tests, 71% total coverage (94% on the core math). Everything in this doc reflects the code as of this moment. If you change the strategy, the rebalancing of sections 1 and 2 is on you — section 4 will always age faster than the others.