Polymarket Bot Tutorial · Rozdział 29 z 32
Zbuduj Polymarket paper trading engine zanim przejdziesz na live: symuluj orders względem realnych cen, śledź P&L, wymuś 30-trade gate (>=55% win rate, +PnL) przed jakimkolwiek live capital, i przygotuj skeleton code.
Co obejmuje ten rozdział
Paper trading to obowiązkowy krok między pomysłem na strategy a live deployment. Ten rozdział przedstawia prosty paper engine, który blokował każdą live bot, jaką wysłaliśmy — poniżej 200 linii Python, śledzi każdą transakcję w JSONL diary i stosuje te same fees/slippage co live path.
To jest rozdział 29 z naszej 32-częściowej serii o budowie Polymarket trading bot. Poniżej omawiamy temat szczegółowo w kolejnych sekcjach. Treści główne dla każdej sekcji są pisane i publikowane rozdział po rozdziale; odpowiedzi FAQ i references są już kompletne i odzwierciedlają production experience z uruchamiania naszego własnego trader.
- Dlaczego paper przed live (zawsze)
- 30-trade gate (verified +55% WR + positive PnL)
- Budowa prostego paper engine
- Śledzenie paper diary obok live diary
- Kiedy paper odbiega od live (i dlaczego)
- Przejście do live: mały pierwszy deposit
- Code: minimal paper engine
Dlaczego paper przed live (zawsze)
30-trade paper gate to jedna dyscyplina, która oddziela 7.6% profitable Polymarket traders od 84.1% przegrywających. Większość builderów to pomija i płaci tuition. Szczera причина, dlaczego to działa: paper trading ujawnia prawdziwy win rate strategy przy wystarczającej liczbie samples, by odróżnić signal od luck.
Pomijanie paper kosztuje więcej, niż oszczędza. Strategy, która wygląda na profitable w backtest, ale w rzeczywistości jest coin flip, spali $200-500 live capital zanim wygeneruje 30-sample size live data. Przehandlowanie tych samych 30 transakcji w paper kosztuje $0.
Paper engine nie musi być sophisticated. Musi być honest — te same fees, to samo slippage, ta sama fill latency co live path. Im prościej, tym lepiej, bo wszystko, co opcjonalne, zostaje wycięte i bot trafia na live wcześniej, niż powinien.
30-trade gate (verified +55% WR + positive PnL)
Gate jest binarny: 30 zamkniętych paper trades, wcześniej zapisane kryteria sukcesu (zwykle WR ≥ 55% dla strategy o positive-EV), albo brak live deployment.
30 to minimalny sample size, przy którym 95% confidence interval dla prawdziwego win rate jest już na tyle wąski, by odróżnić signal od noise. Poniżej 30, obserwowany rate 60% może odpowiadać prawdziwemu rate 45-75%. Przy 30+ przedział zawęża się do ~50-70% — nadal szeroko, ale wystarczająco, by wykluczyć „strategy to coin flip”.
Kryteria sukcesu muszą zostać ustalone PRZED rozpoczęciem paper run. Ustalanie ich po fakcie prowadzi do post-hoc rationalization (znajdziesz sposób, by zinterpretować dowolne 30 trades jako „wystarczająco dobre”).
Budowa prostego paper engine
Paper engine to w zasadzie live trading code z podmienioną funkcją order-placement na simulated fill. Symulacja:
- Read live order book: to samo wywołanie, które wykonałby live bot.
- Simulate fill: jeśli kupujesz przy FOK i price ≥ best ask, order jest wypełniany po volume-weighted average asks consumed; zapisz fill w paper diary.
- Apply fees: odejmij te same fees, które zapłaciłby live path.
- Track inventory: utrzymuj równoległy paper-balance i paper-positions dictionary.
Cały engine mieści się w 100-200 liniach Python. Kluczowa dyscyplina: każde założenie, które przyjmuje live path (fill rate, latency, fee), musi zostać odtworzone w paper, nawet jeśli nieco gorzej niż w rzeczywistości — paper should be the floor, not the ceiling.
Śledzenie paper diary obok live diary
Paper trading run generuje JSONL diary nieodróżnialny strukturalnie od live diary, które bot zapisze później. Te same pola: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
Dwa powody, by używać tego samego formatu. Po pierwsze, analysis tools, które czytają live trades (PnL reports, win-rate calculators), działają na paper bez modyfikacji. Po drugie, późniejsze porównanie paper do live wychwytuje divergences, które wskazują na bugs.
Production tip: każ paper engine zapisywać do per_trade_paper.jsonl w tym samym katalogu co live per_trade.jsonl. Jedno polecenie porównuje oba: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
Kiedy paper odbiega od live (i dlaczego)
Nieuniknione divergences między paper i live. Trzy najczęstsze.
- Slippage: paper fills po snapshot ask; live przechodzi przez book i może wypełnić się 1-2c gorzej na thin markets. Rozwiązanie: symuluj slippage w paper, dodając per-trade penalty równy połowie spread.
- Fill latency: paper wypełnia się natychmiast; live potrzebuje 200-500ms, w trakcie których price może się zmienić. Rozwiązanie: symuluj, czekając i ponownie czytając book przed „wypełnieniem” w paper.
- Adverse selection: paper zakłada, że dostajesz najlepszy ask; live konkuruje z innymi botami, które mogły już podnieść ten ask. Rozwiązanie: trudniejsze do zasymulowania; uczciwie przyznaj sobie, że paper zawyża wyniki.
Gdy paper pokazuje +5%/miesiąc, a live działa na -2%/miesiąc, luka zwykle wynika z jednej z tych rzeczy. Audytuj je po kolei, zamiast zakładać, że sama strategy była błędna.
Przejście do live: mały pierwszy deposit
Paper przechodzi 30 trades. Plan wdrożenia live:
- Wpłać $25-50 jako smoke-test capital. Traktuj to jako tuition; jeśli to stracisz, lekcja była warta swojej ceny.
- Uruchom bota w trybie live przez 5-10 trades z pozycjami o minimalnym size (5 shares).
- Zweryfikuj, że każdy fill zgadza się z paper expectations w granicach 2c. Każdą większą różnicę zbadaj przed kontynuacją.
- Jeśli 5-10 live trades zgadza się z paper, wpłać $200-500 i używaj pozycji normal size.
- Jeśli nie pasują, zatrzymaj się, debuguj, napraw i zacznij od kroku 1.
Najczęstsza luka live-paper przy pierwszym wdrożeniu to brakująca fee albo błędne oszacowanie slippage. Naprawa jest prosta; dyscyplina polega na wykryciu luki zanim zwiększysz capital.
Code: minimal paper engine
Reference: prosty paper engine, który czyta live book + symuluje 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 między paper diary a „would-have-been” live diary.











