تطبيع بيانات السوق التنبؤية: Polymarket و Kalshi
تطبيع البيانات Polymarket و Kalshi دون خلط النتائج أو الأسعار أو قواعد الحل. برنامج تعليمي لـ Node.js يحتوي على مثال قابل للتشغيل وقائمة التحقق من الصحة.
Gizmolab الفريق · تم النشر · 16 دقائق للقراءة
في هذا البرنامج التعليمي
- 1. تطبيع المعنى قبل مقارنة الأسعار
- 2. قم بتعيين معرفات المكان واقتبس دلالاته بشكل صريح
- 3. تحديد مخطط قانوني ضيق
- 4. تحليل الأسعار العشرية دون التقريب الصامت
- 5. رفض المطابقات الخاطئة عبر قواعد الحل
- 6. قم بتشغيل مثال التسوية دون اتصال
- 7. تعامل مع النضارة واستعادة النقل كفحصين منفصلين
- 8. التحقق من صحة حالات الحافة قبل توصيل لوحة القيادة
- 9. قم بتمديد المحول دون فقدان المصدر
- مصدر كامل قابل للتشغيل
1. تطبيع المعنى قبل مقارنة الأسعار
تعمل تسوية بيانات سوق التنبؤ على تحويل السجلات الخاصة بالمكان إلى نموذج مشترك مع الاحتفاظ بالمعلومات اللازمة لتفسيرها. الجزء الصعب هو عدم إعادة تسمية الحقول. إنه يضمن أن يشير سجلان إلى نفس الحالة واتجاه النتيجة ووقت المراقبة وشروط الحل قبل أن تضع لوحة المعلومات أسعارهما جنبًا إلى جنب.
يقوم هذا البرنامج التعليمي بإنشاء مثال Node.js دون اتصال بالإنترنت لللاستيعاب والتحقق من الصحة. يستخدم أسواقًا خيالية وسجلات محول محددة بوضوح، وليس استجابات Polymarket أو Kalshi مباشرة. لا توجد مفاتيح API أو طلبات شبكة أو طلبات أو عمليات محفظة. الهدف هو حدود صغيرة قابلة للفحص يمكنك اختبارها قبل توصيل مصدر حقيقي.
احتفظ بحمولة المصدر الأصلية أو مرجع أرشيفي معتمد إلى جانب السجل الذي تمت تسويته. يحتوي المسار المفيد على ثلاث طبقات: استجابة المكان الأولية، ومحول المكان الذي تم إصداره، والنموذج المشترك الذي تم التحقق من صحته. عندما يقوم موفر بتغيير حقل أو قاعدة، يمكنك تحديد المحول الذي أنتج سجلاً وإعادة معالجته دون تخمين معنى القيمة القديمة.
2. قم بتعيين معرفات المكان واقتبس دلالاته بشكل صريح
Polymarket يميز بين الأحداث والأسواق القابلة للحل ومعرفات الرموز المميزة للنتائج. حافظ على تلك الهويات بدلاً من اختصار عنوان الحدث إلى مفتاح السوق العالمي. كما تميز وثائق الأسعار الخاصة بها الأسعار المعروضة عن عروض الأسعار والطلبات المتوفرة في دفتر الطلبات. يجب أن تحتفظ التجارة الأخيرة ونقطة المنتصف والاقتباس القابل للتنفيذ بتسميات مختلفة في النموذج الخاص بك.
تصف وثائق دفتر الطلبات الحالية لـ Kalshi أزواج السلاسل ذات النقاط الثابتة ضمن orderbook_fp، بما في ذلك Yes_dollars وno_dollars. يحدد مرجع النقطة الثابتة الوحدات المستخدمة بواسطة حقول السعر والكمية. احتفظ بسلاسل المصدر من خلال التحليل. لا تقم بنسخ افتراض عدد صحيح قديم إلى محول لحقل سلسلة الدولار الحالي.
Kalshi يوثق كيف يمكن لجوانب العطاء التكميلية أن تشير ضمنًا إلى طلبات دفتر الطلبات الثنائي الخاص بها. إذا كان المحول الخاص بك يستمد سعرًا بهذه الطريقة، فقم بتسجيل الاشتقاق وجانب المصدر. لا تقم بتطبيق هذا التحول على سوق تعسفي متعدد النتائج أو تخلط بين الطلب المشتق وعرض الأسعار المقدم مباشرة. يقبل المثال أدناه نوع عرض الأسعار المحدد بالفعل ولا يعيد إنشاء دفتر الطلبات.
| قلق | مصدر خاص بالمكان | قاعدة النموذج المشترك |
|---|---|---|
| الهوية | Polymarket معرفات الحدث والحالة والرمز المميز للنتيجة | سوق مساحة الاسم ومعرفات النتائج حسب المكان؛ الحفاظ على مراجع المصدر |
| الهوية | Kalshi مؤشرات الأحداث والسوق | أبقِ تجميع الأحداث منفصلاً عن السوق القابلة للتداول |
| السعر | Polymarket عرض/طلب كتاب الطلب أو السعر المرتبط بالعرض | نوع عرض أسعار المتجر واتجاه النتائج بشكل صريح |
| السعر | Kalshi سلاسل الدولار ذات النقطة الثابتة | تحليل السلاسل العشرية باستخدام الوحدات المعلنة؛ لا تستنتج سنتا |
| دورة الحياة | حالة كل مكان وحقول التوقيت | استخدام الخرائط التي تمت مراجعتها؛ الاحتفاظ بالحالة الأولية والقيم غير المعروفة |
3. تحديد مخطط قانوني ضيق
تتوقع الوحدة القابلة للتنزيل عقد محول موثق. حقول الهوية هي سلاسل غير فارغة، بما في ذلك معرفات كبيرة ذات مظهر رقمي. المفتاح هو مجموعة متسلسلة من المكان والسوق والنتيجة، والتي تتجنب الاصطدامات الناجمة عن ربط المعرفات التي لا يمكن الهروب منها مع علامات الترقيم. لا تقم مطلقًا بتحويل معرف الرمز المميز إلى رقم JavaScript لمجرد أنه يحتوي على أرقام.
قم بتعيين كل نتيجة مصدر إلى condition_true أو condition_false من خلال قاعدة صريحة. موضع الصفيف صفر ليس مرادفًا عالميًا لـ نعم. احتفظ بالمزايدة والسؤال والنقطة الأخيرة والوسطى كأنواع عروض أسعار منفصلة. يتعامل هذا المثال مع العقود الثنائية ذات القيمة الاسمية المعلنة $1؛ ولا يدعم السلال أو الدفعات المتغيرة أو التحويلات متعددة النتائج.
فصل sourceAt وreceiveAt وCloseAt وملاحظة الشروط وResolveAt. يصف وقت المصدر الاقتباس؛ يصف وقت الاستلام عملية الابتلاع؛ الوقت القريب يصف حدود التداول؛ يصف وقت المراقبة الحدث الذي يتم قياسه. يمكن للسوق أن يتوقف عن التداول قبل معرفة نتائجه. ينتمي وقت الحل المتوقع، إذا كان منتجك يحتاج إليه، إلى حقل آخر لاغٍ بدلاً من استبدال وقت الحل الفعلي.
- قم بتعيين قيم دورة حياة المكان غير المعروفة إلى حالة غير معروفة بشكل صريح وتنبيه للمراجعة.
- تتطلب الطوابع الزمنية UTC ISO الأساسية عند هذه الحدود؛ تحويل تنسيقات المصدر والمناطق الزمنية في محول المكان.
- يُحفظ فارغًا عند غياب الاقتباس أو الطابع الزمني للمصدر. لا تحول البيانات المفقودة إلى سعر صفر.
- قم بتخزين معرفات الأماكن الأولية ومراجع القواعد خارج نموذج المثال المضغوط عند إنشاء المحول الحقيقي.
4. تحليل الأسعار العشرية دون التقريب الصامت
يقوم المثال بقياس سعر الدولار إلى عدد صحيح من المليون باستخدام حساب BigInt، ثم يقوم بإرجاع سلسلة JSON-safe. تصبح السلاسل "0.615" و"0.6150" كلاهما "615000". إن إدخال السنتات القديمة أو الاصطناعية المعلن عنها بشكل منفصل بقيمة "61.5" يؤدي إلى نفس النتيجة. يوضح خيار السنتات وحدات واضحة؛ لم يتم تقديمه كتنسيق الاستجابة الحالي لـ Kalshi.
يتم قبول السلاسل العشرية غير الموقعة أو الفارغة فقط. يتم رفض تدوين الأس وأرقام الفاصلة العائمة والأسعار خارج النطاق والدقة التي لا يمكن تمثيلها بدقة. يتم قبول الأصفار الزائدة الإضافية عندما تظل القيمة دقيقة. تجبر هذه السياسة مالك المحول على اتخاذ قرار دقيق متعمد بدلاً من تغيير عرض الأسعار بهدوء.
المقياس هو اختيار تعليمي، وليس ادعاء بأن كل مكان أو مخطط مستقبلي يستخدم ستة منازل عشرية. التحقق من صحة فترات السعر المسموح بها للمزود الحالي ودقة الكمية ودلالات المجال بشكل منفصل. السعر والكمية والحجم والفائدة النظرية والمفتوحة هي مقاييس مختلفة؛ لا تقم بتشغيلها جميعًا من خلال نفس الوظيفة أو تصنيف السعر المتداول على أنه احتمال تمت معايرته بشكل مستقل.
5. رفض المطابقات الخاطئة عبر قواعد الحل
العناوين المماثلة لا تثبت العقود المتساوية. توضح وثائق دقة Polymarket أن قواعد الدقة تحدد مصدر حالات الحافة ومعالجتها. احتفظ بالإشارة إلى تلك القواعد وراجع التعديلات. يمكن أن يختلف السوق الذي يقيس ملاحظة رسمية منشورة ماديًا عن الذي يقيس قراءة أولية أو مصدرًا آخر، حتى لو كان كلا العنوانين يذكران نفس الحدث.
يقارن السجل الاصطناعي الخاص بنا الموضوع الذي تمت مراجعته، والمسند، ووقت المراقبة، ومصدر الحل، وقاعدة الباطلة، وشروط الدفع، بالإضافة إلى اتجاه النتيجة المتعارف عليه. يجب أن يتضمن المسند عامل التشغيل الخاص به: "أكبر من 100" يختلف عن "100 على الأقل". تؤدي الاختلافات إلى أسباب الرفض بدلاً من درجة ثقة غامضة قد تخطئ فيها التعليمات البرمجية النهائية للموافقة.
العلامة التي تمت مراجعتها ليست سوى عنصر نائب للتدريس. في الإنتاج، اقبل التعيينات التي تمت مراجعتها من سجل تم إصداره ومصادقته مع هوية المراجع ومراجع قاعدة المصدر ومحفوظات التغيير. إن مساواة النص ليست دليلاً دلاليًا: حيث يتحقق المقارن من القيم المنظمة التي قام شخص ما بمراجعتها بالفعل. يجب عدم السماح لمحول ضار أو خاطئ بالإعلان عن معادلته للأسواق بشكل تعسفي.
يرفض المثال أيضًا أوقات الإغلاق المختلفة أو المفقودة كبوابة مراجعة متحفظة. يمكن لعقدين قياس نفس الحدث أثناء الإغلاق في أوقات مختلفة، لذلك يؤدي هذا عن عمد إلى إنشاء بعض السلبيات الكاذبة. احتفظ بالتكافؤ الدلالي ونوافذ التداول المماثلة كقرارات إنتاج منفصلة. حتى المباراة المعتمدة لا تقول شيئًا عن توفر التنفيذ أو الرسوم أو الضمانات أو السيولة أو التجارة المربحة.
6. قم بتشغيل مثال التسوية دون اتصال
قم بتنزيل Predict-Market-normalization.mjs باستخدام الرابط أدناه، واحفظه محليًا وقم بتشغيله باستخدام إصدار Node.js المدعوم حاليًا. ليس لديها تبعيات الحزمة. المصدر الكامل المطبوع على هذه الصفحة هو نفس الملف الذي يقدمه رابط التنزيل، لذا يظل الشرح والعنصر القابل للتشغيل متوافقين.
يقدم المكانان الخياليان أسعار عطاءات متساوية ولكنهما يحددان مصادر دقة مختلفة. تنجح التسوية لأن كل سجل صالح من الناحية الهيكلية. تقوم المقارنة بعد ذلك برفض الزوج لأنه لا يستوفي معايير المطابقة التي تمت مراجعتها. يعد هذا الفصل مفيدًا: قد لا يزال السجل الصالح غير مناسب للمقارنة بين الأماكن.
node prediction-market-normalization.mjs{
"prices": ["615000", "615000"],
"comparison": {
"equivalent": false,
"reasons": ["Different resolutionSource"]
}
}7. تعامل مع النضارة واستعادة النقل كفحصين منفصلين
الرسالة المستلمة ليست بالضرورة اقتباسًا حديثًا. تستخدم وظيفة الحداثة في المثال وقت تقييم واضحًا والحد الأقصى للعمر، والإشارات التي تفتقد وقت المصدر، وتتطلب مراجعة الساعة عندما تكون الطوابع الزمنية في المستقبل أو غير متسقة. ولا يستنتج أن التجارة الأخيرة الجديدة هي طلب قابل للتنفيذ. قم بتطبيق بوابات نوع الاقتباس وحالة السوق ومراجعة القواعد بشكل منفصل.
يحتاج تدفق الإنتاج أيضًا إلى سلوك إعادة الاتصال: احتفظ بآخر تسلسل مقبول أو نقطة تفتيش المصدر عندما تكون متاحة، واكتشف الثغرات، واحصل على لقطة جديدة، وقم بتطبيق التحديثات وفقًا لعقد الطلب الخاص بالمكان. إلغاء التكرار من خلال هوية مصدر موثقة؛ لا تفترض أن الطوابع الزمنية فريدة من نوعها. قم بوضع علامة على الخلاصة بأنها قديمة بينما الاسترداد غير مكتمل.
احتفظ بالسجلات الأولية المقيدة لتصحيح الأخطاء وفقًا لأذونات الموفر ومتطلبات الاحتفاظ. إصدار المحول المستمر، وتلقي الوقت، وفشل التحقق من الصحة، وإصدار مراجعة القواعد. إذا أدى تغيير المخطط إلى إسقاط حقل مطلوب، فقم بعزله ورفع إشارة تشغيلية. سيؤدي ملء الحقل المفقود بالصفر إلى إخفاء خط أنابيب مكسور خلف مخطط معقول المظهر.
8. التحقق من صحة حالات الحافة قبل توصيل لوحة القيادة
يتم تنفيذ الكود القابل للتنزيل من خلال الاختبارات الآلية للموقع. تعد هذه الحالات بمثابة اختبارات قبول مفيدة للمحول الخاص بك أيضًا. أضف تركيبات موفر حقيقية ومرخصة واختبارات النقل عند الاتصال المباشر APIs؛ لا تستطيع الوحدة الاصطناعية وحدها التحقق من توفر المصدر أو الوصول إلى نقطة النهاية أو تفسير قواعد السوق.
| الإدخال أو التغيير | السلوك المتوقع | مشكلة يمنعها |
|---|---|---|
| اقتباس فارغ وصفر حقيقي | الحفاظ على قيمتين مختلفتين | البيانات المفقودة تظهر كسعر |
| السلاسل العشرية في الوحدات المعلنة | سلسلة عددية صحيحة تمت تسويتها بالضبط | السنتات/الدولارات وأخطاء الفاصلة العائمة |
| الدقة الزائدة أو الوحدات غير المعروفة | الرفض بدلاً من التقريب أو التخمين | الفساد الاقتباس الصامت |
| معرف سوق كبير أو رمز مميز | الحفاظ على هوية السلسلة | فقدان دقة الرقم |
| عكس اتجاه النتيجة | رفض المباراة | نعم/لا انعكاس |
| تم تغيير المصدر أو المسند أو الوقت أو قاعدة باطلة | رفض التعيين الذي تمت مراجعته في انتظار المراجعة | معادلة خاطئة عبر المكان |
| مغلقة ولكن لم يتم حلها | حافظ على حلها عند null | مطالبات التسوية المبكرة |
| وقت المصدر قديم أو مستقبلي أو غائب | ضع علامة على قديمة أو مراجعة الساعة أو مفقودة | نضارة مضللة |
9. قم بتمديد المحول دون فقدان المصدر
ابدأ التكامل الحقيقي عن طريق اختيار إصدار API لمكان محدد ومجموعة صغيرة من السوق. اكتب مستند تعيين يحدد المصدر لكل حقل أساسي ووحداته وسلوكه الفارغ وقاعدة التحقق من الصحة. احتفظ بكميات المصدر وعمق دفتر الطلبات في هياكلها العشرية الدقيقة بدلاً من إضافة أرقام غير مؤكدة إلى سجل الأسعار.
بعد ذلك، قم بإضافة اختبارات دورة الحياة، والتركيبات التاريخية المسموح بها، ومعالجة تغيير القواعد. قرر كيفية إلغاء التعيين الذي تمت مراجعته عندما تتغير قواعد المصدر وكيف يعلم المستهلكون أن المقارنة السابقة الخاصة بهم لم تعد معتمدة. لدى دليل السوق وموجز الأسعار وسجل القواعد احتياجات تحديث مختلفة؛ اجعل تلك المسؤوليات مرئية في التصميم.
بالنسبة إلى لوحة المعلومات أو منتج التنبيه، اعرض المكان ونوع عرض الأسعار ووقت المصدر والحداثة وحالة مراجعة القواعد إلى جانب السعر. إذا ساهمت الواجهة في تقليص هذه الفروق، فمن الممكن أن تؤدي الواجهة الخلفية الصحيحة إلى مقارنة مضللة. أنشئ تنسيق العرض والتصدير من نفس النموذج الذي تم التحقق من صحته.
مصدر كامل قابل للتشغيل
// Offline teaching example. These are synthetic adapter records, NOT venue API responses.
// Run with Node.js: node prediction-market-normalization.mjs
import { pathToFileURL } from 'node:url'
function nonempty(value, name) {
if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${name} must be a nonempty string`)
return value
}
function timestamp(value, name, nullable = false) {
if (nullable && value === null) return null
nonempty(value, name)
const parsed = new Date(value)
if (!Number.isFinite(parsed.valueOf()) || parsed.toISOString() !== value) {
throw new TypeError(`${name} must be canonical UTC ISO, including milliseconds`)
}
return value
}
// Price per $1-face-value contract, scaled to millionths; no binary float arithmetic.
// 'cents' is an explicit legacy/synthetic adapter unit, not an assumed live API format.
export function normalizePrice(value, unit) {
if (!['dollars', 'cents'].includes(unit)) throw new TypeError('Unknown price unit')
if (value === null) return null // Missing quote is different from a real zero price.
if (typeof value !== 'string' || !/^\d+(\.\d+)?$/.test(value) || value.length > 80) {
throw new TypeError('Price must be a bounded unsigned decimal string or null')
}
const [whole, fraction = ''] = value.split('.')
const numerator = BigInt(whole + fraction) * 1_000_000n
const denominator = 10n ** BigInt(fraction.length) * (unit === 'cents' ? 100n : 1n)
if (numerator > denominator * 1_000_000n) throw new RangeError('Price exceeds $1 face value')
if (numerator % denominator !== 0n) throw new RangeError('Price needs more than six decimal places')
return (numerator / denominator).toString()
}
export function normalizeRecord(input) {
const venue = nonempty(input.venue, 'venue')
const marketId = nonempty(input.marketId, 'marketId')
const outcomeId = nonempty(input.outcomeId, 'outcomeId')
if (!['condition_true', 'condition_false'].includes(input.canonicalOutcome)) throw new TypeError('Explicit outcome mapping required')
if (!['bid', 'ask', 'last', 'midpoint'].includes(input.quoteKind)) throw new TypeError('Explicit quote kind required')
if (!['open', 'closed', 'resolved', 'unknown'].includes(input.status)) throw new TypeError('Explicit adapter status required')
return {
key: JSON.stringify([venue, marketId, outcomeId]), // No collisions from punctuation in IDs.
venue, marketId, outcomeId,
canonicalOutcome: input.canonicalOutcome,
quoteKind: input.quoteKind,
priceMicros: normalizePrice(input.price, input.priceUnit),
sourceAt: timestamp(input.sourceAt, 'sourceAt', true),
receivedAt: timestamp(input.receivedAt, 'receivedAt'),
closeAt: timestamp(input.closeAt, 'closeAt', true),
resolvedAt: timestamp(input.resolvedAt, 'resolvedAt', true),
status: input.status,
rawStatus: nonempty(input.rawStatus, 'rawStatus'),
// A teaching flag cannot replace an authenticated, versioned rule-review registry.
terms: input.terms ? { ...input.terms } : null,
}
}
const TERM_FIELDS = ['subject', 'predicate', 'observationAt', 'resolutionSource', 'voidRule', 'payoutTerms']
export function compareContracts(left, right) {
const reasons = []
if (left.canonicalOutcome !== right.canonicalOutcome) reasons.push('Different outcome mapping')
for (const [label, record] of [['left', left], ['right', right]]) {
if (record.terms?.reviewed !== true) reasons.push(`${label}: terms have not been reviewed`)
for (const field of TERM_FIELDS) {
if (typeof record.terms?.[field] !== 'string' || !record.terms[field].trim()) reasons.push(`${label}: missing ${field}`)
}
}
for (const field of TERM_FIELDS) {
if (left.terms?.[field] !== right.terms?.[field]) reasons.push(`Different ${field}`)
}
// Conservative gate: differing close times require review even if the event is the same.
if (left.closeAt === null || right.closeAt === null || left.closeAt !== right.closeAt) reasons.push('Close times require review')
return { equivalent: reasons.length === 0, reasons }
}
export function quoteFreshness(record, now, maxAgeMs) {
timestamp(now, 'now')
if (!Number.isSafeInteger(maxAgeMs) || maxAgeMs < 0) throw new TypeError('Invalid maximum age')
if (record.priceMicros === null || record.sourceAt === null) return 'missing'
const received = Date.parse(record.receivedAt)
const source = Date.parse(record.sourceAt)
const current = Date.parse(now)
if (source > current || received > current || source > received) return 'clock-review'
return current - source > maxAgeMs ? 'stale' : 'fresh'
}
export function demoRecords() {
const common = {
marketId: 'demo-market-900719925474099312345', outcomeId: 'demo-yes',
canonicalOutcome: 'condition_true', quoteKind: 'bid', priceUnit: 'dollars',
sourceAt: '2026-10-01T12:00:00.000Z', receivedAt: '2026-10-01T12:00:01.000Z',
closeAt: '2026-10-30T23:00:00.000Z', resolvedAt: null, status: 'open', rawStatus: 'adapter-demo-open',
terms: { reviewed: true, subject: 'Fictional Example Index', predicate: 'index > 100',
observationAt: '2026-10-31T00:00:00.000Z', resolutionSource: 'Example Authority A',
voidRule: 'void if official observation is unavailable', payoutTerms: 'USD 1 per winning contract' },
}
return [
normalizeRecord({ ...common, venue: 'demo-venue-a', price: '0.615' }),
normalizeRecord({ ...common, venue: 'demo-venue-b', price: '0.6150',
terms: { ...common.terms, resolutionSource: 'Example Authority B' } }),
]
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [left, right] = demoRecords()
console.log(JSON.stringify({ prices: [left.priceMicros, right.priceMicros], comparison: compareContracts(left, right) }, null, 2))
}
بناء تكامل بيانات السوق التنبؤية
Gizmolab يساعد الفرق على تحديد نطاق محولات المكان والمخططات الأساسية واسترداد الخلاصات وواجهات المنتج. ابدأ بالأماكن المطلوبة وحقوق البيانات ومعايير القبول.
استكشف توقع تكامل API →