دليل Polymarket Bot · الفصل 29 من 32
ابنِ محرك paper trading لـ Polymarket قبل الانتقال إلى التشغيل الفعلي: حاكِ الأوامر مقابل الأسعار الحقيقية، وتتبّع P&L، وطبّق بوابة 30 صفقة (معدل فوز >=55%، وPnL إيجابي) قبل تخصيص أي رأس مال حقيقي، مع skeleton للكود.
ما الذي يغطيه هذا الفصل
paper trading هو الخطوة غير القابلة للتفاوض بين فكرة الاستراتيجية والنشر الفعلي. هذا الفصل يقدّم محرك paper بسيطًا كان يمرّ عبره كل bot فعلي أطلقناه - بأقل من 200 سطر من Python، يتتبّع كل صفقة في دفتر يوميات JSONL، ويطبّق الرسوم/الانزلاق نفسها الموجودة في المسار الفعلي.
- لماذا paper قبل التشغيل الفعلي (دائمًا)
- بوابة 30 صفقة (تحقّق من +55% WR وPnL إيجابي)
- بناء محرك paper بسيط
- تتبّع دفتر paper بالتوازي مع دفتر live
- متى ينحرف paper عن live (ولماذا)
- الانتقال إلى live: إيداع أولي صغير
- الكود: محرك paper minimal
لماذا paper قبل التشغيل الفعلي (دائمًا)
بوابة 30 صفقة في paper هي الانضباط الوحيد الذي يفصل 7.6% من متداولي Polymarket المربحين عن 84.1% الذين يخسرون. يتجاوزها معظم البنّائين ويدفعون الثمن. السبب الصادق لنجاحها: paper trading يكشف معدل فوز الاستراتيجية الحقيقي عبر عدد كافٍ من العينات للتمييز بين الإشارة والحظ.
تجاوز paper يكلف أكثر مما يوفّر. الاستراتيجية التي تبدو مربحة في backtest لكنها في الواقع لا تزيد عن رمية عملة ستستهلك 200-500 دولار من رأس المال الفعلي قبل أن تنتج حجم عينة من 30 صفقة live. أما paper لنفس 30 صفقة فتكلفته 0 دولار.
محرك paper لا يحتاج أن يكون معقدًا. ما يحتاجه هو الصدق - نفس الرسوم، نفس slippage، ونفس fill latency كما في المسار الفعلي. وكلما كان أبسط كان أفضل، لأن أي شيء اختياري سيتم حذفه وسينتقل bot إلى live أسرع مما ينبغي.
بوابة 30 صفقة (تحقّق من +55% WR وPnL إيجابي)
البوابة ثنائية: 30 صفقة paper مغلقة، ومعايير نجاح محددة مسبقًا كتابةً (عادةً WR ≥ 55% على استراتيجية ذات EV إيجابي)، أو لا يوجد نشر فعلي.
30 هو الحد الأدنى لحجم العينة الذي يصبح عنده فاصل الثقة 95% لمعدل الفوز الحقيقي ضيقًا بما يكفي لتمييز الإشارة من الضجيج. أقل من 30، قد يقابل معدل مشاهده 60% معدل حقيقي بين 45-75%. وعند 30+، يضيق الفاصل إلى نحو 50-70% - لا يزال واسعًا، لكنه كافٍ لاستبعاد فكرة "الاستراتيجية مجرد رمية عملة".
يجب تحديد معايير النجاح قبل بدء تشغيل paper. تحديدها بعد ذلك يؤدي إلى rationalization بعدي (ستجد طريقة لتفسير أي 30 صفقة على أنها "جيدة بما يكفي").
بناء محرك paper بسيط
محرك paper هو في الأساس كود التداول الفعلي نفسه، لكن مع استبدال دالة تنفيذ الأمر بملء simulated. المحاكاة:
- قراءة دفتر الأوامر الحي: نفس الاستدعاء الذي سيستخدمه live bot.
- محاكاة fill: إذا كان الشراء بـ FOK وسعره ≥ best ask، يتم ملء الأمر عند المتوسط المرجح حجميًا للـ asks المستهلكة؛ وتُسجَّل العملية في دفتر paper.
- تطبيق الرسوم: اطرح نفس الرسوم التي سيدفعها المسار الفعلي.
- تتبّع inventory: حافظ على paper-balance وpaper-positions dictionary موازيتين.
يمكن أن يقع المحرك بالكامل في 100-200 سطر من Python. الانضباط الأساسي: يجب إعادة إنتاج كل افتراض يعتمد عليه المسار الفعلي (معدل التنفيذ، latency، الرسوم) في paper، حتى لو كان أسوأ قليلًا من الواقع - يجب أن يكون paper هو الحد الأدنى، لا السقف.
تتبّع دفتر paper بالتوازي مع دفتر live
تشغيل paper trading ينتج دفتر يوميات JSONL لا يختلف في البنية عن دفتر اليوميات live الذي سيكتبه bot لاحقًا. نفس الحقول: timestamp، action، market_slug، side، size، price، expected_fill_price، simulated_pnl_at_exit.
سببان لاستخدام الصيغة نفسها. أولًا، أدوات التحليل التي تقرأ الصفقات live (تقارير PnL، حاسبات معدل الفوز) تعمل على paper من دون تعديل. ثانيًا، مقارنة paper مع live لاحقًا تكشف الانحرافات التي تشير إلى bugs.
نصيحة إنتاجية: اجعل محرك paper يكتب إلى per_trade_paper.jsonl في المجلد نفسه الذي يوجد فيه per_trade.jsonl الخاص بـ live. أمر واحد يقارن كليهما: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
متى ينحرف paper عن live (ولماذا)
هناك انحرافات حتمية بين paper وlive. ثلاثة شائعة منها:
- Slippage: في paper يتم الملء عند لقطة الـ ask؛ أما في live فيتم اجتياح الدفتر وقد يكون التنفيذ أسوأ بـ 1-2c في الأسواق الضعيفة. الحل: حاكِ slippage في paper بإضافة عقوبة لكل صفقة تساوي نصف السبريد.
- Fill latency: في paper يتم الملء فورًا؛ أما في live فيستغرق 200-500ms وخلالها قد يتحرك السعر. الحل: حاكِ ذلك بالانتظار وإعادة قراءة الدفتر قبل "الملء" في paper.
- Adverse selection: يفترض paper أنك حصلت على أفضل ask؛ بينما live ينافسك bots أخرى ربما رفعت ذلك ask بالفعل. الحل: من الأصعب محاكاته؛ والاعتراف الصريح لنفسك بأن paper يبالغ في التقدير.
عندما يقول paper +5%/شهر بينما يعمل live عند -2%/شهر، فعادةً يكون الفارق واحدًا من هذه العوامل. راجعها واحدًا تلو الآخر بدلًا من افتراض أن الاستراتيجية نفسها كانت خاطئة.
الانتقال إلى live: إيداع أولي صغير
نجحت paper بعد 30 صفقة. خطة النشر الفعلي:
- أودِع 25-50 دولارًا كرأس مال smoke-test. تعامل معه كرسوم تعليم؛ إذا خسرته، فالعِبرة كانت تستحق ذلك.
- شغّل bot في live mode لمدة 5-10 صفقات مع مراكز بالحجم الأدنى (5 shares).
- تحقّق من أن كل fill يطابق توقعات paper ضمن 2c. افحص أي فجوة أكبر قبل المتابعة.
- إذا طابقت 5-10 صفقات live نتائج paper، فأودِع 200-500 دولار وشغّل مراكز بالحجم المعتاد.
- إذا لم تتطابق، فأوقف التشغيل، وابدأ debugging، ثم أصلح المشكلة وأعد البدء من الخطوة 1.
أكثر فجوة شائعة بين live وpaper في أول نشر هي رسوم مفقودة أو تقدير خاطئ للـ slippage. إصلاح ذلك مباشر؛ والانضباط الحقيقي هو اكتشاف الفجوة قبل توسيع رأس المال.
الكود: محرك paper minimal
مرجع: محرك paper بسيط يقرأ دفتر السوق الحي + يحاكي 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 (مرآة للشراء)، محاكاة GTC في paper (نشر على الدفتر بالسعر، ومحاكاة fill عندما يصل mid إلى السعر)، والتسوية بين دفتر paper ودفتر live "المفترض أنه كان سيحدث".





