מדריך Polymarket Bot · פרק 29 מתוך 32
בנו מנוע paper trading ל־Polymarket לפני שעולים לאוויר: הדמיית פקודות מול מחירים אמיתיים, מעקב אחרי P&L, אכיפת שער ה־30 trades (>=55% win rate, +PnL) לפני כל הון חי, ומבנה קוד בסיסי.
מה מכסה הפרק הזה
Paper trading הוא השלב הלא־מתפשר בין רעיון אסטרטגי לפריסה חיה. הפרק הזה מציג את מנוע ה-paper הפשוט שחסם כל bot חי ששלחנו - פחות מ־200 שורות Python, עוקב אחרי כל עסקה ביומן JSONL, ומיישם את אותן עמלות/Slippage כמו במסלול החי.
- למה paper לפני live (תמיד)
- שער 30 ה־trades (מאומת +55% WR + PnL חיובי)
- בניית מנוע paper פשוט
- מעקב אחר יומן paper לצד יומן live
- מתי paper חורג מ־live (ולמה)
- מעבר ל־live: הפקדה ראשונה קטנה
- קוד: מנוע paper מינימלי
למה paper לפני live (תמיד)
שער ה־paper של 30 trades הוא המשמעת היחידה שמפרידה בין 7.6% מה־Polymarket traders הרווחיים לבין 84.1% שמפסידים. רוב הבונים מדלגים עליו ומשלמים שכר לימוד. הסיבה הכנה שזה עובד: paper trading חושף את ה־win rate האמיתי של האסטרטגיה אחרי מספיק דגימות כדי להבדיל בין signal ל־luck.
דילוג על paper עולה יותר ממה שהוא חוסך. אסטרטגיה שנראית רווחית ב־backtest אבל בפועל היא הטלת מטבע תשרוף 200-500 דולר מהון חי לפני שתייצר sample size של 30 דגימות של data חי. לבצע paper על אותן 30 עסקאות עולה 0 דולר.
מנוע ה־paper לא צריך להיות מתוחכם. הוא צריך להיות כן - אותן עמלות, אותו Slippage, ואותה fill latency כמו במסלול החי. ככל שזה פשוט יותר, כך טוב יותר, כי כל דבר אופציונלי נחתך וה־bot עולה לאוויר מוקדם מדי.
שער 30 ה־trades (מאומת +55% WR + positive PnL)
השער הוא בינארי: 30 עסקאות paper סגורות, קריטריוני הצלחה שנקבעו מראש בכתב (בדרך כלל WR ≥ 55% באסטרטגיה עם positive EV), או שאין פריסה חיה.
30 הוא sample size מינימלי שבו רווח הסמך של 95% על ה־win rate האמיתי צר מספיק כדי להבדיל בין signal ל־noise. מתחת ל־30, שיעור נצפה של 60% יכול להתאים לשיעור אמיתי של 45-75%. ב־30 ומעלה, הטווח מצטמצם לכ־50-70% - עדיין רחב, אבל מספיק כדי לשלול "האסטרטגיה היא הטלת מטבע."
קריטריוני ההצלחה חייבים להיקבע לפני שמתחילים את ריצת ה־paper. קביעה שלהם אחרי כן מייצרת רציונליזציה בדיעבד (תמצאו דרך לפרש כל 30 עסקאות כ"מספיק טוב").
בניית מנוע paper פשוט
מנוע ה־paper הוא למעשה קוד המסחר החי, רק עם פונקציית הצבת הפקודה מוחלפת ב־simulated fill. ההדמיה:
- קריאת live order book: אותה קריאה שה־bot החי היה מבצע.
- הדמיית fill: אם קונים ב־FOK במחיר ≥ best ask, הפקודה מתמלאת לפי הממוצע המשוקלל־לפי־נפח של ה־asks שנצרכו; רושמים את המילוי ביומן ה־paper.
- יישום עמלות: מפחיתים את אותן עמלות שהמסלול החי היה משלם.
- מעקב inventory: מתחזקים מילון paper-balance ו־paper-positions מקביל.
כל המנוע נכנס ל־100-200 שורות Python. המשמעת המרכזית: כל הנחה שהמסלול החי עושה (fill rate, latency, fee) חייבת להיות משוחזרת ב־paper, אפילו אם היא מעט גרועה יותר מהמציאות - paper צריך להיות הרצפה, לא התקרה.
מעקב אחר יומן paper לצד יומן live
ריצת ה־paper trading מייצרת יומן JSONL שאינו ניתן להבחנה במבנה שלו מהיומן החי שה־bot יכתוב מאוחר יותר. אותם שדות: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
יש שתי סיבות להשתמש באותו פורמט. ראשית, כלי הניתוח שקוראים עסקאות live (דוחות PnL, מחשבי win rate) עובדים על paper בלי שינוי. שנית, השוואה בין paper ל־live בהמשך חושפת סטיות שמצביעות על באגים.
טיפ פרודקשן: גרמו למנוע ה־paper לכתוב אל per_trade_paper.jsonl באותה תיקייה כמו per_trade.jsonl החי. פקודה אחת משווה את שניהם: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
מתי paper חורג מ־live (ולמה)
סטיות בין paper ל־live הן בלתי נמנעות. שלוש נפוצות.
- Slippage: ב־paper המילוי מתבצע לפי snapshot של ה־ask; ב־live ה־book נבלע ויכול למלא 1-2 סנט גרוע יותר בשווקים דלילים. פתרון: לדמות Slippage ב־paper על ידי הוספת קנס לכל עסקה השווה למחצית ה־spread.
- Fill latency: ב־paper המילוי מיידי; ב־live זה לוקח 200-500ms שבמהלכם המחיר יכול לזוז. פתרון: לדמות זאת על ידי המתנה וקריאה מחדש של ה־book לפני "מילוי" ב־paper.
- Adverse selection: ב־paper מניחים שאתם מקבלים את ה־best ask; ב־live אתם מתחרים ב־bots אחרים שאולי כבר הרימו את ה־ask הזה. פתרון: קשה יותר לדמות; צריך להודות ביושר בפני עצמכם ש־paper נוטה להעריך־יתר.
כש־paper אומר +5%/month ו־live רץ ב־-2%/month, הפער הוא בדרך כלל אחת מהסיבות האלה. בדקו אותן אחת־אחת במקום להניח שהאסטרטגיה עצמה הייתה שגויה.
מעבר ל־live: הפקדה ראשונה קטנה
ה־paper עבר 30 trades. תכנית הפריסה ל־live:
- להפקיד 25-50 דולר כהון smoke-test. להתייחס לזה כשכר לימוד; אם תפסידו אותו, הלקח היה שווה את זה.
- להריץ את ה־bot במצב live במשך 5-10 trades עם פוזיציות בגודל מינימלי (5 shares).
- לאמת שכל fill תואם לציפיות ה־paper בתוך 2 סנט. לחקור כל פער גדול יותר לפני שממשיכים.
- אם 5-10 עסקאות live תואמות ל־paper, להפקיד 200-500 דולר ולהריץ פוזיציות בגודל רגיל.
- אם הן לא תואמות, לעצור, לדבג, לתקן, ולהתחיל מחדש משלב 1.
הפער הנפוץ ביותר בין live ל־paper בפריסה ראשונה הוא עמלה חסרה או הערכת־חסר של Slippage. התיקון שלהם פשוט; המשמעת היא לתפוס את הפער לפני שמגדילים הון.
קוד: מנוע paper מינימלי
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}
תוספות פרודקשן: פונקציית paper sell (מראה של buy), סימולציית paper GTC (הצבה על ה־book במחיר, סימולציית fill כשה־mid מגיע למחיר), התאמה בין יומן ה־paper לבין יומן ה־"היה יכול להיות" החי.





