يبدو التقريب مشكلة محلولة. لديك رقم، ولديك حجم خطوة، فتقرّب إلى أقرب قيمة صالحة. دالة مساعدة واحدة، عشرون سطراً، وانتهى الأمر إلى الأبد.
لكنها ليست دالة واحدة. في نظام يرسل الأوامر إلى عدة منصات تداول، تصبح الدقة مجالاً كاملاً، والأخطاء التي تولّدها تتشارك خاصية خبيثة: فهي لا تطلق استثناءً. بل تُنتج أمراً خاطئاً قليلاً، أو أمراً يُرفض لسبب لا يستطيع المستخدم رؤيته.
هذا ما يبدو عليه العمل فعلاً، من خلال ثلاث مشكلات حقيقية من مسار الأوامر لدينا.
أهم النقاط
- معيار IEEE-754 يجعل محاذاة الخطوة غير موثوقة: ضرب القيمة 0.29 في 100 يعطي 28.999999999999996، وهو ما يُقرّب للأسفل إلى قيمة أصغر بخطوة كاملة.
- ثلاث منصات تداول تفرض ثلاث قواعد متعارضة للحد الأدنى للأمر. واحدة تفحص القيمة بالـ USDT، وأخرى تفحص عدد العقود، وثالثة تفحص كليهما.
- أسوأ خطأ لنا في هذا المجال نشأ عن اختراع قاعدة لا وجود لها في المنصة، ثم رفض أوامر صالحة بناءً عليها.
- لا تصطنع أبداً حداً لم تمنحه لك الـ API. إذا كانت المنصة بلا حد أدنى قيمي، فحساب واحد من حقول أخرى هو تلفيق، وليس حرصاً دفاعياً.
- بيانات المنصة الوصفية المفقودة يجب أن تطلق خطأً، لا أن تلجأ إلى قيمة افتراضية. القيمة الافتراضية الخاطئة تُنتج أوامر خاطئة بصمت.
1. المشكلة الحسابية: التقريب يُزيح رقمك
ابدأ بأبسط حالة، لأنها تحديداً التي يفترض الناس أنها آمنة.
تقبل المنصة الكميات في مضاعفات حجم خطوة معين. لديك القيمة 0.29 وخطوة قدرها 0.01. هذه القيمة صالحة بالفعل، لذا يُفترض أن تقريبها لأسفل لا يغيّر شيئاً. التنفيذ المعتاد يكبّر القيمة، ثم يقرّبها لأسفل، ثم يعيدها لحجمها:
0.29 / 0.01 = 28.999999999999996 ← IEEE-754، وليس 29
floor(...) = 28
28 × 0.01 = 0.28
قيمة كانت محاذاة بالفعل انتقلت خطوة كاملة للأسفل، بصمت، في اتجاه أمر أصغر. لا استثناء ولا تحذير، وهي تحدث فقط في تركيبات معينة من القيمة والخطوة، وهذا بالضبط ما يجعل ملاحظتها صعبة.
الحل هو إبسيلون يُطبَّق أثناء التكبير، كبير بما يكفي لامتصاص ضوضاء التمثيل لكن صغير بما يكفي بحيث تظل قيمة أقل فعلاً مثل 0.289999 تُقرَّب لأسفل بشكل صحيح. كتبت عن اكتشاف هذا في ما استهنت به أثناء بناء هذه المنصة؛ وما يهم هنا هو الفئة التي ينتمي إليها.
فبمجرد أن تتقبّل أن الحساب على الكميات غير موثوق، تبدأ بالنظر إلى كل شيء آخر يمسّ الكمية. وهناك تكمن المفاجآت الحقيقية.
2. ثلاث منصات تداول، ثلاثة حدود دنيا متعارضة
كل منصة تفرض حداً أدنى للأمر. تتوقع أن تختلف القاعدة في قيمتها، لا في شكلها. لكنها تختلف في الشكل:
| المنصة | القاعدة |
|---|---|
| Binance | qty × price >= minNotional (فحص قيمة بالـ USDT) |
| Bybit | qty >= minOrderQty و notional >= minNotional (فحصان) |
| OKX perpetuals | qty >= minSz (عدد العقود، بلا أي فحص للقيمة الاسمية إطلاقاً) |
اقرأ هذا الجدول على أنه ثلاثة أسئلة مختلفة تُطرح حول الأمر نفسه. Binance تسأل عن قيمته. OKX تسأل عن عدد العقود. Bybit تسأل عن الاثنين، ويمكن لأمر أن يستوفي أحدهما وحده ومع ذلك يُرفض.
وهناك تعقيد إضافي في OKX: الكميات محسوبة بـالعقود، لا بالعملات، لذا يجب أن يحوّل حجم العقد بين الاثنين قبل أن يصبح أيٌّ من هذا ذا معنى. الكمية التي تعني "0.5 BTC" في منصة تعني "50 عقداً" في أخرى.
النتيجة المعمارية التي استقررنا عليها: كل منصة تتحقق من صحة أوامرها الخاصة. محرك الاستراتيجية لا يحوي شرطاً واحداً حول المنصة التي يتحدث إليها. فهو يسلّم الأمر إلى مُهايئ (adapter)، والمُهايئ يطبّق قواعده الخاصة. في كل مرة حاولنا فيها توحيد هذه القواعد خلف تجريد واحد، تحوّل التجريد إلى كذبة لحظة ظهور حالة رابعة.
3. الخطأ المُكلف: اختراع قاعدة لا وجود لها
هذا هو الخطأ الذي أودّ أكثر من غيره أن يتجنبه مهندس آخر، لأن الغلطة تبدو وكأنها اجتهاد وحرص.
عقود OKX الدائمة (perpetuals) ليس لها حد أدنى قيمي. ولم نكن نعلم ذلك. بالقياس على المنصات الأخرى، حسبنا واحداً: الحد الأدنى لعدد العقود مضروباً في حجم العقد مضروباً في السعر. بدا معقولاً. وأنتج رقماً محتملاً بالـ USDT. لكنه كان محض خيال.
المكان الذي انهار فيه محدد ويستحق التتبع. استراتيجية الـ DCA تضع أوامر الأمان الخاصة بها تحت سعر الدخول. حدّنا الأدنى المصطنع حُسب عند سعر واحد ثم قورن بأوامر مسعّرة بأقل منه، فالكمية نفسها التي مرّت في أعلى السلّم فشلت في نقاطه الأدنى.
النتيجة: أوامر بمقدار 0.1 عقد بالضبط، وهي تساوي الحد الأدنى للمنصة وكان سيُقبل، رُفضت من مُتحقّقنا الخاص قبل أن تغادر نظامنا أصلاً. المنصة لم ترها قط. ومن جانب المستخدم، ببساطة لم تفعل الاستراتيجية ما جرى ضبطها للقيام به.
الدرس يتعمّم إلى ما هو أبعد من التداول:
لا تصطنع قيداً لم تمنحه لك الـ API. إذا كانت المنصة تعرض حداً أدنى بالعقود، فالحد الأدنى بالعقود. اشتقاق مكافئ قيمي ليس تحرّياً للدقة، بل هو اختراع قاعدة ثم فرضها على المستخدم.
الدليل، بأثر رجعي، أن قيمتنا المحسوبة اعتمدت على السعر بينما قاعدة المنصة الفعلية لم تعتمد عليه. في أي وقت تحتوي فيه نسختك من قيد على مُدخَل يفتقر إليه القيد الحقيقي، تكون قد بنيت شيئاً مختلفاً عمّا قصدت نمذجته.
4. البيانات الوصفية المفقودة يجب أن تكون خطأً، لا قيمة افتراضية
القطعة الأخيرة سياسة أكثر منها خطأً، وهي ما يمنع الثلاثة الأولى من التكرار بصمت.
كل منصة تمنحنا بيانات وصفية لكل رمز: حجم الخطوة، حجم القفزة (tick size)، الحد الأدنى للكمية، حجم العقد. أيٌّ منها قد يكون غائباً، والخطوة المغرية هي اللجوء إلى بديل. حجم الخطوة مفقود؟ افترض 0.001. الحد الأدنى للكمية مفقود؟ افترض صفراً ودع المنصة تقرر.
نحن نفعل العكس: الحقل المطلوب المفقود يطلق استثناءً، فوراً، قبل بناء أي أمر. لا شيء لاحق تُتاح له فرصة البناء على تخمين.
المنطق هو عدم تناظر النتائج. الاستثناء المُطلَق فشل صاخب يظهر في الحال، أمام شخص يستطيع إصلاحه. أما القيمة الافتراضية فهي إجابة خاطئة صامتة تُرسل أمراً بحجم خاطئ ثم تكشف عن نفسها لاحقاً، بالمال. بين خطأ لا يمكنك تجاهله ورقم لا يمكنك التحقق منه، يكون الخطأ أرخص في كل مرة.
هذا يعني أيضاً أن طبقة البيانات الوصفية صادقة بشأن ما تعرفه. الكود الذي يقرأ حقلاً مطلوباً يحصل على قيمة حقيقية أو استثناء. ولا يحصل أبداً على قيمة نائبة ترتدي شكل بيانات حقيقية.
ما ينبغي أن تأخذه معك
- تعامل مع محاذاة الخطوة والقفزة على أنها حساب قد يُزيح قيمتك، لا مجرد تنسيق.
- لا توحّد قواعد مختلفة فعلاً. التحقق لكل منصة على حدة يبقى صحيحاً كلما أُضيفت منصات؛ أما تجريد مشترك فوق قواعد متعارضة فلا يبقى كذلك.
- لا تشتق أبداً حداً لم تصرّح به الـ API. إذا كانت نسختك من قيد تأخذ مُدخَلاً يفتقر إليه القيد الحقيقي، فهو قيد مختلف.
- افشل بصوت عالٍ عند فقدان البيانات الوصفية. القيم الافتراضية تحوّل خطأً مرئياً إلى أمر مُسعّر خطأً وغير مرئي.
العمل على الدقة غير برّاق ولا يظهر أبداً في قائمة ميزات. لكن كل واحد من هذه الأخطاء أنتج أمراً خاطئاً أو أمراً لم يوجد أصلاً، وفي نظام يتداول نيابة عن شخص ما، فإن هذه أخطاء من الفئة نفسها لخسارة أمواله مباشرة.
