Polymarket Bot Tutorial · 32 bölümün 29'u
Canlıya geçmeden önce bir Polymarket paper trading engine oluşturun: emirleri gerçek fiyatlara karşı simüle edin, P&L takibi yapın, herhangi bir live capital kullanmadan önce 30-trade gate'i (>=55% win rate, +PnL) uygulayın ve code skeleton hazırlayın.
Bu bölüm neleri kapsıyor
Paper trading, strategy idea ile live deployment arasındaki pazarlık kabul etmez adımdır. Bu bölüm, canlıya çıkardığımız her botu kapıdan geçiren basit paper engine'dir - 200 satırın altında Python, her trade'i bir JSONL diary'de takip eder, live path ile aynı fees/slippage uygular.
- Neden live öncesi paper (her zaman)
- 30-trade gate (doğrulanmış +55% WR + pozitif PnL)
- Basit bir paper engine oluşturma
- Paper diary'yi live diary ile birlikte takip etme
- Paper live'dan ne zaman sapar (ve neden)
- Live'a geçiş: ilk küçük deposit
- Code: minimal paper engine
Neden live öncesi paper (her zaman)
30-trade paper gate, kârlı Polymarket traders'ın %7.6'sını kaybeden %84.1'den ayıran tek disiplindir. Çoğu builder bunu atlar ve tuition öder. İşe yaradığına dair dürüst neden şu: paper trading, signal ile luck'ı ayırt edecek kadar yeterli örneklem üzerinde strategy'nin gerçek win rate'ini ortaya çıkarır.
Paper'ı atlamak, sağladığından daha fazlasına mal olur. Backtest'te kârlı görünen ama aslında coin flip olan bir strategy, 30 örneklem büyüklüğünde live data üretmeden önce $200-500 live capital yakar. Aynı 30 trade'i paper'da yapmak $0'a mal olur.
Paper engine'in sofistike olması gerekmez. Dürüst olması gerekir - aynı fees, aynı slippage, live path ile aynı fill latency. Ne kadar basitse o kadar iyi, çünkü opsiyonel olan her şey kesilir ve bot olması gerekenden daha erken live'a çıkar.
30-trade gate (doğrulanmış +55% WR + pozitif PnL)
Gate binary'dir: 30 closed paper trade, önceden yazılmış başarı kriterleri (genellikle positive-EV bir strategy için WR ≥ 55%), ya da live deployment yok.
30, gerçek win rate için %95 confidence interval'ın sinyali gürültüden ayıracak kadar dar olduğu minimum örneklem büyüklüğüdür. 30'un altında, gözlenen %60 oranı gerçek %45-75 aralığına karşılık gelebilir. 30+ seviyesinde interval yaklaşık %50-70'e daralır - hâlâ geniş, ama "strategy coin flip" değil demek için yeterli.
Başarı kriterleri paper run başlamadan ÖNCE belirlenmelidir. Sonradan belirlemek post-hoc rationalization üretir (her 30 trade'i "yeterince iyi" diye yorumlamanın bir yolunu bulursunuz).
Basit bir paper engine oluşturma
Paper engine, aslında order-placement function'ın simulated fill ile değiştirildiği live trading code'dur. Simülasyon:
- Live order book oku: live bot'un yapacağı aynı çağrı.
- Fill'i simüle et: FOK ile alım yaparken fiyat best ask'ten büyük/eşitse, emir ask'lerin tüketilen volume-weighted average'ında doldurulur; fill paper diary'ye kaydedilir.
- Fees uygula: live path'in ödeyeceği aynı fees düşülür.
- Inventory takip et: paralel bir paper-balance ve paper-positions dictionary tutulur.
Tüm engine 100-200 satır Python'a sığar. Temel disiplin şudur: live path'in yaptığı her varsayım (fill rate, latency, fee) paper'da da yeniden üretilmelidir, gerçeklikten biraz daha kötü olsa bile - paper floor olmalı, ceiling değil.
Paper diary'yi live diary ile birlikte takip etme
Paper trading run, bot'un daha sonra yazacağı live diary ile yapı olarak ayırt edilemez bir JSONL diary üretir. Aynı alanlar: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
Aynı formatı kullanmak için iki neden var. Birincisi, live trade'leri okuyan analysis tools (PnL reports, win-rate calculators) paper üzerinde değişiklik yapmadan çalışır. İkincisi, ileride paper ile live'ı karşılaştırmak bug'lara işaret eden sapmaları yakalar.
Production ipucu: paper engine'in per_trade_paper.jsonl dosyasına, live per_trade.jsonl ile aynı dizinde yazmasını sağlayın. Tek komutla ikisini karşılaştırın: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
Paper live'dan ne zaman sapar (ve neden)
Paper ile live arasında kaçınılmaz sapmalar olur. Üç yaygın olanı:
- Slippage: paper, ask snapshot'ında fill olur; live book boyunca yürür ve ince markets'ta 1-2c daha kötü fill alabilir. Çözüm: paper'da slippage'ı spread'in yarısına eşit trade başı ceza ekleyerek simüle edin.
- Fill latency: paper anında fill olur; live ise 200-500ms sürer ve bu sırada price hareket edebilir. Çözüm: paper'da "fill" etmeden önce bekleyip book'u yeniden okuyarak simüle edin.
- Adverse selection: paper en iyi ask'i aldığınızı varsayar; live ise o ask'i çoktan kaldırmış olabilecek diğer bot'larla yarışır. Çözüm: bunu simüle etmek daha zordur; paper'ın fazla iyimser olduğunu kendinize dürüstçe kabul edin.
Paper +5%/month derken live -2%/month çalışıyorsa, fark genellikle bunlardan biridir. Strategy'nin kendisinin yanlış olduğunu varsaymak yerine, bunları tek tek audit edin.
Live'a geçiş: ilk küçük deposit
Paper 30 trade geçti. Live deployment planı:
- Smoke-test capital olarak $25-50 deposit edin. Bunu tuition olarak görün; kaybederseniz, ders buna değmiştir.
- Bot'u live mode'da 5-10 trade boyunca minimum size (5 share) ile çalıştırın.
- Her fill'in paper beklentileriyle 2c içinde eşleştiğini doğrulayın. Devam etmeden önce daha büyük herhangi bir farkı inceleyin.
- 5-10 live trade paper ile eşleşiyorsa, $200-500 deposit edin ve normal boyutlu position'larla çalıştırın.
- Eşleşmiyorsa durdurun, debug edin, düzeltin, 1. adımdan yeniden başlayın.
İlk deployment'ta en yaygın live-paper farkı eksik bir fee veya yanlış slippage tahminidir. Bunları düzeltmek basittir; disiplin, capital ölçeklemeden önce farkı yakalamaktır.
Code: minimal paper engine
Reference: live book'u okuyup FOK fill'i simüle eden basit paper engine.
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 (buy'ın aynası), paper GTC simulation (fiyatta book'a post et, mid price o fiyata geldiğinde fill'i simüle et), paper diary ile "would-have-been" live diary arasında reconciliation.





