Polymarket Bot Tutorial · Chapter 29 of 32

Build a Polymarket paper trading engine before going live: simulate orders against real prices, track P&L, enforce the 30-trade gate (>=55% win rate, +PnL) before any live capital, and code skeleton.

What this chapter covers

Paper trading is the non-negotiable step between strategy idea and live deployment. This chapter is the simple paper engine that has gated every live bot we've shipped - under 200 lines of Python, tracks every trade in a JSONL diary, applies the same fees/slippage as the live path.

  • Why paper before live (always)
  • The 30-trade gate (verified +55% WR + positive PnL)
  • Building a simple paper engine
  • Tracking paper diary alongside live diary
  • When paper diverges from live (and why)
  • Graduating to live: small first deposit
  • Code: minimal paper engine

Why paper before live (always)

The 30-trade paper gate is the single discipline that separates the 15.9% of profitable Polymarket traders from the 84.1% who lose. Most builders skip it and pay tuition. The honest reason it works: paper trading reveals the strategy's true win rate over enough samples to distinguish signal from luck.

Skipping paper costs more than it saves. A strategy that looks profitable in backtest but is actually a coin flip will burn through $200-500 of live capital before producing a 30-sample size of live data. Paper-trading the same 30 trades costs $0.

The paper engine does not need to be sophisticated. It needs to be honest - same fees, same slippage, same fill latency as the live path. The simpler the better, because anything optional gets cut and the bot ships live earlier than it should.

The 30-trade gate (verified +55% WR + positive PnL)

The gate is binary: 30 closed paper trades, written-in-advance success criteria (typically WR ≥ 55% on a positive-EV strategy), or no live deployment.

30 is the minimum sample size where the 95% confidence interval on the true win rate is narrow enough to distinguish signal from noise. Below 30, a 60% observed rate could correspond to a true rate of 45-75%. At 30+, the interval narrows to ~50-70% - still wide, but enough to rule out "the strategy is a coin flip."

The success criteria must be set BEFORE the paper run starts. Setting them after produces post-hoc rationalization (you'll find a way to interpret any 30 trades as "good enough").

Building a simple paper engine

The paper engine is essentially the live trading code with the order-placement function swapped for a simulated fill. The simulation:

  • Read live order book: same call as the live bot would make.
  • Simulate fill: if buying at FOK with price ≥ best ask, fill the order at the volume-weighted average of asks consumed; record the fill in the paper diary.
  • Apply fees: subtract the same fees the live path would pay.
  • Track inventory: maintain a parallel paper-balance and paper-positions dictionary.

The whole engine fits in 100-200 lines of Python. The key discipline: every assumption the live path makes (fill rate, latency, fee) must be reproduced in paper, even if slightly worse than reality - paper should be the floor, not the ceiling.

Tracking paper diary alongside live diary

The paper trading run produces a JSONL diary indistinguishable in structure from the live diary the bot will write later. Same fields: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.

Two reasons to use the same format. First, the analysis tools that read live trades (PnL reports, win-rate calculators) work on paper without modification. Second, comparing paper to live later catches divergences that indicate bugs.

Production tip: have the paper engine write to per_trade_paper.jsonl in the same directory as the live per_trade.jsonl. Single command compares both: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).

When paper diverges from live (and why)

Inevitable divergences between paper and live. Three common ones.

  • Slippage: paper fills at the ask snapshot; live walks the book and may fill 1-2c worse on thin markets. Solution: simulate slippage in paper by adding a per-trade penalty equal to half the spread.
  • Fill latency: paper fills instantly; live takes 200-500ms during which the price may move. Solution: simulate by waiting and re-reading the book before "filling" in paper.
  • Adverse selection: paper assumes you get the best ask; live competes with other bots who may have already lifted that ask. Solution: harder to simulate; honest disclosure to yourself that paper overestimates.

When paper says +5%/month and live runs at -2%/month, the gap is usually one of these. Audit them one by one rather than assuming the strategy itself was wrong.

Graduating to live: small first deposit

Paper passes 30 trades. Live deployment plan:

  1. Deposit $25-50 as smoke-test capital. Treat as tuition; if you lose it, the lesson was worth it.
  2. Run the bot in live mode for 5-10 trades with positions at minimum size (5 shares).
  3. Verify each fill matches paper expectations within 2c. Investigate any larger gap before continuing.
  4. If 5-10 live trades match paper, deposit $200-500 and run normal-size positions.
  5. If they don't match, halt, debug, fix, restart from step 1.

The most common live-paper gap on first deployment is a missing fee or a slippage misestimate. Fixing those is straightforward; the discipline is catching the gap before scaling capital.

Code: minimal paper engine

Reference: simple paper engine that reads live book + simulates FOK fill.

import json, time
PAPER_BAL = 10_000.0   # USD starting
positions = {}         # token_id -> shares

def paper_fok_buy(token_id, max_price, size):
    book = fetch_book(token_id)
    # Walk asks, fill what we can within max_price
    filled = 0; cost = 0
    for level in book.asks:
        px = float(level["price"])
        if px > max_price: break
        avail = float(level["size"])
        take = min(avail, size - filled)
        filled += take
        cost += take * px
        if filled >= size: break
    if filled < size:
        return {"status":"rejected","filled":0}  # FOK semantics

    global PAPER_BAL
    PAPER_BAL -= cost
    positions[token_id] = positions.get(token_id, 0) + filled

    log_paper({"ts": int(time.time()), "action":"buy",
               "token": token_id, "size": filled, "price": cost/filled})
    return {"status":"matched","filled":filled,"cost":cost}

Production additions: paper sell function (mirror of buy), paper GTC simulation (post on book at price, simulate fill when mid reaches price), reconciliation between paper diary and "would-have-been" live diary.

Frequently asked questions

What is the 30-trade gate?
Our internal gating rule for going from paper to live: at least 30 closed paper trades, win rate >= 55%, and net PnL positive net of slippage. Failing any of these means you stay paper. We invented this rule after several premature go-live attempts wiped accounts in 2025.
Why 30 trades and not 100?
Statistical power. With 30 trades, a 55% win rate has roughly 70% probability of being real edge (not noise). With 100 trades, that confidence grows to 90%+. We chose 30 as the minimum bar because longer paper periods often induce overfitting - traders tweak the strategy too long instead of testing it.
Can I skip paper trading if I am confident?
Confidence is exactly when you should not skip it. The bots that do best on Polymarket are run by people who have been wrong before. The 30-trade gate exists to catch the strategies that look right but aren't. Most of our own strategies failed paper at first - that is the value.
Do paper results match live results?
Usually yes for slow-moving strategies (politics, weather), no for fast ones (5-min crypto, sports microstructure). The gap is "paper trading does not pay slippage" - real fills are worse than the price you saw. We discount paper returns by 30-50% before believing them for live, especially for fast strategies.
How do I implement a paper engine in Python?
Subscribe to the real CLOB WebSocket for the markets you trade. When your strategy decides to "place an order," log it to a JSONL file with the would-be fill price (current bid for buy, current ask for sell). Track positions virtually. Mark-to-market against the live price. The whole engine is ~200 lines of Python.
How long should I paper trade before going live?
Until the 30-trade gate is met, or 2-4 weeks whichever is longer. If you are reaching the gate too fast, you are overfitting; slow down and verify your win rate is robust to small parameter changes. If you cannot reach the gate after months, the strategy probably has no edge and you should kill it.