آموزش Polymarket Bot · فصل 29 از 32
قبل از going live یک Polymarket paper trading engine بسازید: سفارشها را در برابر قیمتهای واقعی simulate کنید، P&L را track کنید، gate 30-trade را اعمال کنید (>=55% win rate، +PnL) قبل از هرگونه live capital، و code skeleton را پیادهسازی کنید.
این فصل چه چیزهایی را پوشش میدهد
Paper trading گام غیرقابلچشمپوشی بین ایده strategy و live deployment است. این فصل همان paper engine سادهای است که روی هر bot زندهای که منتشر کردهایم gate گذاشته - با کمتر از 200 خط Python، همه tradeها را در یک diary از نوع JSONL track میکند و همان fees/slippage مسیر live را اعمال میکند.
- چرا قبل از live، paper لازم است (همیشه)
- gate 30-trade (تأییدشده با +55% WR و مثبت بودن PnL)
- ساخت یک paper engine ساده
- Track کردن paper diary در کنار live diary
- وقتی paper با live diverge میشود (و چرا)
- ارتقا به live: واریز اولیه کوچک
- Code: minimal paper engine
چرا قبل از live، paper لازم است (همیشه)
gate 30-trade در paper تنها disciplineای است که 7.6% از Polymarket traders سودده را از 84.1% زیانده جدا میکند. بیشتر builders این مرحله را رد میکنند و هزینهاش را میپردازند. دلیل صادقانهای که این روش جواب میدهد این است: paper trading win rate واقعی strategy را روی تعداد sample کافی آشکار میکند تا signal را از luck جدا کند.
رد کردن paper هزینهای بیشتر از صرفهجوییاش دارد. یک strategy که در backtest سودده به نظر میرسد اما در واقع coin flip است، پیش از آنکه 30 نمونه از live data تولید کند، 200 تا 500 دلار از live capital را میسوزاند. paper-trading همان 30 trade، 0 دلار هزینه دارد.
paper engine لازم نیست پیچیده باشد. باید صادق باشد - همان fees، همان slippage، همان fill latency مسیر live. هرچه سادهتر بهتر، چون هر چیزی که optional باشد حذف میشود و bot زودتر از موعد وارد live میشود.
gate 30-trade (تأییدشده با +55% WR و مثبت بودن PnL)
این gate دوحالته است: 30 paper trade بستهشده، success criteria از پیش نوشتهشده (معمولاً WR ≥ 55% برای یک strategy با positive EV)، وگرنه هیچ live deploymentی انجام نمیشود.
عدد 30 حداقل sample size است که در آن بازه اطمینان 95% برای win rate واقعی آنقدر باریک میشود که signal را از noise جدا کند. زیر 30، یک نرخ مشاهدهشده 60% میتواند معادل نرخ واقعی 45 تا 75% باشد. از 30 به بالا، این بازه به حدود 50 تا 70% میرسد - هنوز پهن است، اما برای رد کردن این فرض که «این strategy فقط coin flip است» کافی است.
success criteria باید قبل از شروع paper run تعیین شوند. تعیین آنها بعد از شروع، post-hoc rationalization تولید میکند (برای هر 30 trade راهی پیدا میکنید که آنها را «بهاندازه کافی خوب» تفسیر کنید).
ساخت یک paper engine ساده
paper engine در اصل همان live trading code است که تابع order-placement آن با یک simulated fill جایگزین شده است. این simulation شامل موارد زیر است:
- Read کردن live order book: همان callی که live bot انجام میدهد.
- Simulate fill: اگر هنگام خرید با FOK قیمت ≥ best ask باشد، سفارش را با volume-weighted average of asks consumed پر میکند؛ fill را در paper diary ثبت کنید.
- اعمال fees: همان feesی را کم کنید که مسیر live پرداخت میکند.
- Track کردن inventory: یک paper-balance و paper-positions dictionary موازی نگه دارید.
کل engine در 100 تا 200 خط Python جا میگیرد. discipline اصلی این است: هر فرضی که مسیر live میسازد (نرخ fill، latency، fee) باید در paper بازتولید شود، حتی اگر کمی بدتر از واقعیت باشد - paper باید floor باشد، نه ceiling.
Track کردن paper diary در کنار live diary
paper trading run یک JSONL diary تولید میکند که از نظر ساختار با live diaryی که bot بعداً مینویسد، قابلتشخیص نیست. همان fields: timestamp، action، market_slug، side، size، price، expected_fill_price، simulated_pnl_at_exit.
دو دلیل برای استفاده از همان format وجود دارد. اول، ابزارهای analysis که tradeهای live را میخوانند (PnL reports، win-rate calculators) بدون هیچ تغییری روی paper هم کار میکنند. دوم، مقایسه paper و live در آینده divergenceهایی را نشان میدهد که نشانه bug هستند.
Production tip: اجازه دهید paper engine در همان directoryی که live per_trade.jsonl قرار دارد، در per_trade_paper.jsonl بنویسد. با یک command هر دو را مقایسه کنید: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
وقتی paper با live diverge میشود (و چرا)
divergence بین paper و live اجتنابناپذیر است. سه مورد رایج:
- Slippage: paper در snapshotِ ask fill میشود؛ live book را walk میکند و ممکن است در بازارهای thin، 1 تا 2 سنت بدتر fill شود. راهحل: slippage را در paper با افزودن یک penalty برای هر trade معادل نصف spread simulate کنید.
- Fill latency: paper فوری fill میشود؛ live بین 200 تا 500 میلیثانیه زمان میبرد و در این فاصله price ممکن است حرکت کند. راهحل: با waiting و re-reading book قبل از «fill» کردن در paper آن را simulate کنید.
- Adverse selection: paper فرض میکند بهترین ask را میگیرید؛ live با botهای دیگر رقابت میکند که ممکن است آن ask را قبلاً برداشته باشند. راهحل: simulate کردنش سختتر است؛ صادقانه به خودتان بگویید که paper overestimate میکند.
وقتی paper میگوید +5%/month و live در -2%/month اجرا میشود، شکاف معمولاً یکی از همینهاست. آنها را یکبهیک audit کنید، نه اینکه فرض کنید خود strategy اشتباه بوده است.
ارتقا به live: واریز اولیه کوچک
paper از gate 30 trade عبور میکند. برنامه deployment به live:
- 25 تا 50 دلار واریز کنید بهعنوان smoke-test capital. آن را tuition در نظر بگیرید؛ اگر از دست رفت، درسش ارزشش را داشته است.
- bot را در حالت live برای 5 تا 10 trade با positionهای حداقل اندازه (5 سهم) اجرا کنید.
- تأیید کنید که هر fill در محدوده 2 سنت با انتظار paper مطابقت دارد. هر gap بزرگتر را قبل از ادامه بررسی کنید.
- اگر 5 تا 10 live trade با paper مطابقت داشت، 200 تا 500 دلار واریز کنید و positionهای با اندازه عادی را اجرا کنید.
- اگر مطابقت نداشتند، متوقف شوید، debug کنید، fix کنید، و از مرحله 1 دوباره شروع کنید.
رایجترین gap بین live و paper در نخستین deployment، fee فراموششده یا برآورد نادرست slippage است. درستکردن آنها ساده است؛ discipline اصلی این است که gap را قبل از scaling capital پیدا کنید.
Code: minimal paper engine
Reference: simple paper engine که live book را میخواند + FOK fill را simulate میکند.
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 بین paper diary و "would-have-been" live diary.





