أين تُشغّل مستهلك الدفق على مدار الساعة، وكيف تُبقيه فعّالًا
تعمل علامة تبويب المتصفح جيدًا لمراقبة فارق سعري وهو ينفتح، لكنها لا تستطيع التقاطه وأنت نائم. يُغلق الفارق الوسيط بين المنصتين خلال نحو 9.2 ثانية، وتنتهي صلاحية تذكرة الاتصال التي تنقلك إلى الدفق خلال نحو 120 ثانية. لا تترك هذه الحسابات مجالاً لعملية تبدأ من تلقاء نفسها حسب جدولها الخاص. أيًا كان ما يقرأ الدفق فهو يحتاج إلى أن يكون فعّالاً بالفعل عندما ينفتح الفارق، في كل مرة، بما في ذلك الساعة 3 فجرًا وبعد انقطاع كهرباء عابر.
لا شيء من هذا يلمس وضع الأوامر. هذه بيانات لمطورين لا نصيحة تداول أو مالية. ما يلي يتعلق فقط بإبقاء عملية فعّالة.
نواة المستهلك، بلغة Python
الشكل واحد أينما يعمل: اسحب تذكرة، اتصل، اقرأ الأحداث من المقبس، وأعد الاتصال عندما تنتهي صلاحية التذكرة.
import json
import os
import time
import requests
import websocket
API_KEY = os.environ["DINO_API_KEY"] # read from a 0600 env file, never hardcoded
def mint_ticket():
resp = requests.post(
"https://api.dino.markets/v1/stream/token",
headers={"Authorization": f"Bearer {API_KEY}"},
)
return resp.json()
def run():
while True:
mint = mint_ticket()
ws = websocket.create_connection(mint["ws_url"])
ws.send(json.dumps({"id": 1, "connect": {"data": {"t": mint["ticket"]}}}))
try:
while True:
# one WS frame can carry several newline-separated objects
for line in ws.recv().split("\n"):
if not line:
continue
msg = json.loads(line)
if msg == {}:
ws.send("{}") # server ping, pong straight back or the connection drops
continue
data = (msg.get("push") or {}).get("pub", {}).get("data")
if data and data.get("type") == "price":
handle_gap(data)
except Exception:
pass
finally:
ws.close()
time.sleep(1)
def handle_gap(event):
# forward each parsed event to wherever your alerting or storage lives
print(event)
if __name__ == "__main__":
run()
معالجة الـping داخل الحلقة تهم بقدر منطق إعادة الاتصال المحيط بها: يتحقق الخادم من وجود عميل حي كل 25 ثانية تقريبًا، والعملية التي لا تستجيب يُقطع اتصالها قبل وقت طويل من انتهاء صلاحية تذكرة الـ120 ثانية من تلقاء نفسها. المضيفات الثلاثة أدناه لا تغيّر إلا ما يُبقي هذه الحلقة عاملة؛ أما الحلقة نفسها فتبقى كما هي.
جهاز Mac mini تملكه بالفعل
إذا كان هناك جهاز Mac mini أو Mac قديم موجود بالفعل في مكان ما، فسيُبقي launchd هذا السكربت يعمل عبر الأعطال وإعادة التشغيل دون أي برمجيات إضافية. يكفي plist قصير بضبط KeepAlive لإنجاز المهمة (تذهب هذه المفاتيح داخل <dict> العلوي في الـplist):
<key>Label</key><string>markets.dino.stream-consumer</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>set -a; . /usr/local/etc/dino/env; set +a; exec /usr/bin/python3 /usr/local/bin/consumer.py</string>
</array>
<key>KeepAlive</key><true/>
<key>RunAtLoad</key><true/>
<key>StandardErrorPath</key><string>/tmp/dino-consumer.err</string>
لا يحمل الـplist المفتاح نفسه إطلاقًا. يستمده غلاف (wrapper) من ملف بيئة على القرص (chmod 600) قبل بدء السكربت، بحيث لا يوجد شيء حساس في ملف إعداد يحمّله launchd. عندما يسقط المقبس، تسحب حلقة إعادة الاتصال الخاصة بالعملية نفسها تذكرة جديدة وتعيد الاتصال؛ ولا يتدخل launchd إلا إذا ماتت العملية كاملة تمامًا.
خادم VPS رخيص
يعمل جهاز صغير بالطريقة نفسها، مع systemd بديلاً عن launchd. يعيد ملف وحدة (unit file) بإعداد Restart=on-failure تشغيل العملية إذا خرجت، ويمنحك journalctl السجلات دون إعداد أي شيء آخر:
[Unit]
Description=dino.markets stream consumer
After=network-online.target
[Service]
EnvironmentFile=/etc/dino/env
ExecStart=/usr/bin/python3 /opt/dino/consumer.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
يحمل /etc/dino/env القيمة DINO_API_KEY=sk_live_... وينبغي أن يكون بصلاحية chmod 600، مملوكًا للمستخدم الذي تعمل الخدمة تحته. تحدث إعادة الاتصال داخل السكربت؛ لا يعيد systemd تشغيل العملية إلا إذا خرجت من تلقاء نفسها. راقبها بأمر journalctl -u dino-consumer -f.
Cloudflare Durable Objects
يتخطى Durable Object الجهاز بالكامل. لا يوجد نظام تشغيل تحتاج إلى ترقيعه ولا plist ولا ملف وحدة، لأن Cloudflare تُشغّل العملية نيابة عنك. المقايضة هي الفوترة. تملك Durable Objects واجهة سُبات تتوقف عن فوترة كائن خامل بينما ينتظر اتصالاً واردًا، لكن ذلك يغطي فقط المقابس التي يفتحها آخرون للوصول إليه. يعمل المستهلك هنا في الاتجاه المعاكس: يُبقي كائنك اتصال WebSocket صادرًا مفتوحًا نحو دفقنا، والمقبس الصادر لا يدخل في سُبات. بالنسبة لمستهلك دائم التشغيل، يبقى الكائن نشطًا باستمرار وتُفوتَر وفق ذلك. يستحق هذا أن تعرفه قبل اختيار هذا الخيار؛ راجع صفحة تسعير Cloudflare نفسها للرقم الفعلي. تعمل إعادة الاتصال بالطريقة نفسها: اسحب تذكرة أخرى وأعد فتح المقبس عندما تنتهي صلاحيتها.
ما تملكه بالفعل مقابل ما ترغب في تشغيله
| ما تُشغّله عليه | ما تلتزم بتشغيله | |
|---|---|---|
| Mac mini | عتاد موجود بالفعل على المكتب | استمرارية طاقتك وشبكتك الخاصتين |
| VPS | فاتورة شهرية صغيرة ويمكن توقعها | ترقيع نظام التشغيل بنفسك |
| Durable Objects | لا شيء يحتاج ترقيعًا | فوترة مستمرة لمقبس مفتوح دائمًا |
لا شيء من هذه الخيارات خاطئ. إذا كان جهاز Mac mini موجودًا بالفعل على المكتب، فلا يكلّفك شيئًا إضافيًا تجربته أولاً، ويستحق VPS اللجوء إليه بمجرد أن يبدأ زمن التشغيل بالأهمية أكثر من الراحة. أما Durable Objects فيكسب جدواه بمجرد أن يتوقف ترقيع جهاز بنفسك عن استحقاق وقتك.
الدفق الذي يقرأه هذا المستهلك متاح في Basic فأعلى. يتصل مفتاح Free أيضًا، لكن فقط بعيّنة صغيرة منسَّقة من الأسواق، لا بالتغذية الكاملة متعددة الرياضات التي بُنيت هذه الحلقة من أجلها. تغطي الوثائق تدفق الاتصال الكامل.
بمجرد أن يعمل مستهلك في مكان ما، تكون القطعة الأخيرة عادةً هي تحويل فارق مؤهَّل إلى شيء يصلك مباشرة بدلاً من أن يبقى في ملف سجل. نتناول ذلك تاليًا، في إرسال تنبيه Telegram في اللحظة التي ينفتح فيها الفارق.
المزيد من المدونة
راقب Kalshi وPolymarket باستخدام Python
ابنِ مراقبًا بلغة Python لأسواق Kalshi وPolymarket المطابَقة، ثم وسّع لقطة REST المتأخرة بتحديثات في الوقت الفعلي.
خادم Kalshi و Polymarket عبر MCP في Claude و Cursor
صِل Claude أو Cursor بخادم MCP الخاص بـ Dino لأسواق Kalshi و Polymarket، وافحص الأسواق المطابَقة مع سياق التسوية.
قارِن بين Kalshi و Polymarket بأمان
قارِن عقود Kalshi و Polymarket مع مخاطر التسوية وفروق العقود المرفَقة بكل سعر ملاحَظ.