Polymarket Bot Tutorial · Bab 29 dari 32
Bangun paper trading engine Polymarket sebelum live: simulasi order terhadap harga real, lacak P&L, terapkan 30-trade gate (>=55% win rate, +PnL) sebelum modal live apa pun, dan code skeleton.
Apa yang dibahas dalam bab ini
Paper trading adalah langkah wajib antara ide strategy dan live deployment. Bab ini adalah paper engine sederhana yang menjadi gate untuk setiap live bot yang kami rilis - kurang dari 200 baris Python, melacak setiap trade dalam diary JSONL, menerapkan fee/slippage yang sama seperti jalur live.
- Mengapa paper sebelum live (selalu)
- 30-trade gate (verified +55% WR + positive PnL)
- Membangun simple paper engine
- Melacak paper diary berdampingan dengan live diary
- Kapan paper berbeda dari live (dan mengapa)
- Naik ke live: deposit kecil pertama
- Code: minimal paper engine
Mengapa paper sebelum live (selalu)
30-trade paper gate adalah disiplin tunggal yang memisahkan 7,6% trader Polymarket yang profitable dari 84,1% yang rugi. Sebagian besar builder melewatkannya dan membayar biaya belajar. Alasan jujur mengapa ini berhasil: paper trading mengungkap true win rate strategy dalam sampel yang cukup untuk membedakan signal dari luck.
Melewatkan paper justru lebih mahal daripada yang dihemat. Strategy yang terlihat profitable di backtest tetapi sebenarnya hanya coin flip akan menghabiskan $200-500 live capital sebelum menghasilkan 30-sample size data live. Men-paper-trade 30 trade yang sama biayanya $0.
Paper engine tidak perlu sophisticated. Yang penting jujur - fee yang sama, slippage yang sama, fill latency yang sama seperti jalur live. Semakin sederhana semakin baik, karena apa pun yang opsional akan dipangkas dan bot akan dirilis live lebih cepat dari seharusnya.
30-trade gate (verified +55% WR + positive PnL)
Gate ini bersifat binary: 30 closed paper trades, success criteria yang ditetapkan sebelumnya (biasanya WR ≥ 55% untuk strategy dengan positive EV), atau tidak ada live deployment.
30 adalah minimum sample size di mana 95% confidence interval pada true win rate cukup sempit untuk membedakan signal dari noise. Di bawah 30, observed rate 60% bisa berarti true rate 45-75%. Pada 30+, interval menyempit menjadi sekitar 50-70% - masih lebar, tetapi cukup untuk menyingkirkan "strategy ini coin flip."
Success criteria harus ditetapkan SEBELUM paper run dimulai. Menetapkannya setelahnya menghasilkan post-hoc rationalization (Anda akan menemukan cara untuk menafsirkan trade 30 apa pun sebagai "cukup bagus").
Membangun simple paper engine
Paper engine pada dasarnya adalah live trading code dengan fungsi order-placement diganti simulasi fill. Simulasinya:
- Read live order book: call yang sama seperti yang akan dilakukan live bot.
- Simulate fill: jika membeli pada FOK dengan price ≥ best ask, isi order pada volume-weighted average dari asks yang dikonsumsi; catat fill di paper diary.
- Apply fees: kurangi fee yang sama seperti yang akan dibayar jalur live.
- Track inventory: pertahankan paper-balance dan paper-positions dictionary secara paralel.
Seluruh engine muat dalam 100-200 baris Python. Disiplin kuncinya: setiap asumsi yang dibuat jalur live (fill rate, latency, fee) harus direproduksi di paper, bahkan jika sedikit lebih buruk dari kenyataan - paper harus menjadi floor, bukan ceiling.
Melacak paper diary berdampingan dengan live diary
Paper trading run menghasilkan diary JSONL yang tidak bisa dibedakan strukturnya dari live diary yang nanti akan ditulis bot. Field-nya sama: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
Ada dua alasan memakai format yang sama. Pertama, tools analisis yang membaca live trade (PnL reports, win-rate calculators) bisa bekerja pada paper tanpa modifikasi. Kedua, membandingkan paper dengan live di kemudian hari akan menangkap divergensi yang menunjukkan bug.
Production tip: biarkan paper engine menulis ke per_trade_paper.jsonl di direktori yang sama dengan live per_trade.jsonl. Satu command untuk membandingkan keduanya: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
Kapan paper berbeda dari live (dan mengapa)
Divergensi antara paper dan live tidak bisa dihindari. Tiga yang paling umum.
- Slippage: paper fill pada snapshot ask; live berjalan menyusuri book dan bisa fill 1-2c lebih buruk di market yang tipis. Solusi: simulasi slippage di paper dengan menambahkan penalty per trade sebesar setengah spread.
- Fill latency: paper fill instan; live butuh 200-500ms, selama itu price bisa bergerak. Solusi: simulasi dengan menunggu dan membaca ulang book sebelum "filling" di paper.
- Adverse selection: paper mengasumsikan Anda mendapatkan best ask; live bersaing dengan bot lain yang mungkin sudah lebih dulu mengambil ask itu. Solusi: lebih sulit disimulasikan; akui secara jujur kepada diri sendiri bahwa paper cenderung melebihkan hasil.
Saat paper bilang +5%/bulan dan live berjalan -2%/bulan, selisihnya biasanya salah satu dari ini. Audit satu per satu daripada menganggap strategy-nya sendiri yang salah.
Naik ke live: deposit kecil pertama
Paper lolos 30 trade. Rencana deployment live:
- Deposit $25-50 sebagai smoke-test capital. Anggap sebagai biaya belajar; kalau hilang, pelajarannya sepadan.
- Jalankan bot dalam live mode selama 5-10 trade dengan position di ukuran minimum (5 shares).
- Verifikasi setiap fill cocok dengan ekspektasi paper dalam selisih 2c. Selidiki gap yang lebih besar sebelum lanjut.
- Jika 5-10 live trade cocok dengan paper, deposit $200-500 dan jalankan position ukuran normal.
- Jika tidak cocok, hentikan, debug, perbaiki, mulai ulang dari langkah 1.
Gap live-paper yang paling umum saat deployment pertama adalah fee yang hilang atau estimasi slippage yang meleset. Memperbaikinya cukup langsung; disiplin utamanya adalah menangkap gap sebelum scaling capital.
Code: minimal paper engine
Reference: simple paper engine yang membaca live book + mensimulasikan fill FOK.
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}
Addition production: paper sell function (mirror dari buy), paper GTC simulation (post on book pada price, simulate fill ketika mid mencapai price), reconciliation antara paper diary dan "would-have-been" live diary.





