تخطي إلى المحتوى الرئيسي
الهندسةالاختبار الرجعيبوتات التداول
ما الذي استهنتُ به وأنا أبني منصة بوتات تداول بلا كود

ما الذي استهنتُ به وأنا أبني منصة بوتات تداول بلا كود

فكرة بوت التداول بلا كود بسيطة: تصف استراتيجيتك في نموذج، والبرنامج يشغّلها نيابة عنك. الجزء الصعب ليس النموذج، بل أن كل تبسيط تقدّمه للمستخدم لا بد أن يُدفع ثمنه في مكان ما داخل النظام، وعادة في مكان لم تكن تتوقعه.

هذه ثلاثة أمور أخطأت في تقديرها بشأن ذلك، وما علّمنا إياه كل واحد منها فعليًا. وقد رتّبتها بحسب مقدار سوء تقديري لها.

أهم النقاط

  • واجهات برمجة تطبيقات البورصات تفشل بصمت أكثر مما تفشل بصخب. استجابة HTTP 200 ببيانات أقل مما طلبت هي وضع الفشل الذي عليك أن تصمّم للاحتراس منه.
  • لا تثق أبدًا بنتيجة اختبار رجعي قبل أن تختبر المحاكي نفسه. منحنى رأس مال يبدو نظيفًا قد يأتي من تشغيل معطوب.
  • حساب الفاصلة العائمة على كميات الأوامر يُنتج أخطاء انزياح بمقدار خطوة واحدة، ولا تظهر إلا على أزواج بعينها.
  • قرار ألا نحتفظ أبدًا بأموال المستخدمين يُزيل صنفًا كاملًا من المشكلات ويخلق مشكلة أصغر لكن أحدّ: يجب أن يكون نظامك صحيحًا دون أن يقدر أبدًا على التراجع عن أي شيء.
  • كل تسهيل تقدّمه للمستخدم يتحوّل إلى قيد في مكان ما داخل المحرك. غالبًا ما تستحق هذه المقايضة، لكنها ليست مجانية أبدًا.

1. افترضتُ أن الطلب الفاشل سيبدو كفشل

هذه هي التي كلّفتني أكثر الوقت، وهي التي أُحذّر منها أي مطوّر أولًا.

نحن نجلب الشموع التاريخية من البورصات لتشغيل الاختبارات الرجعية. النموذج الذهني الساذج هو أن الطلب إما ينجح أو يُخطئ: إما أن تحصل على بياناتك، أو تحصل على استجابة غير 200 فتتعامل معها. لكن واجهات برمجة تطبيقات البورصات الحقيقية لا تتصرف هكذا تحت الضغط. فهي تُرجع استجابة HTTP 200 بعدد شموع أقل مما طلبت، دون أي إشارة إلى أن شيئًا ما قد اختل.

إليك حالة حقيقية من سجلّاتنا، والأرقام دقيقة. طلب اختبار رجعي طويل المدة نحو 536,000 شمعة. عند الساعة 16:34 تلقّى 58,000. الحالة 200. لا حقل خطأ. لا تحذير. الطلب نفسه، دون تغيير، عند الساعة 17:54 تلقّى الـ 535,000 كاملة.

فكّر فيما يعنيه ذلك إن لم تكن تتحقق. يقوم المحرك بسرور بمحاكاة الاستراتيجية على أحد عشر بالمئة من التاريخ المقصود، وينتج منحنى رأس مال يبدو معقولًا، ويُبلّغ عن نتيجة. يقرأ المستخدم رقمًا خاطئًا بطريقة لا يكشف عنها أي شيء في المُخرَجات. هذا أسوأ من الانهيار. الانهيار صادق.

الحل مملّ من الناحية المفاهيمية وكان من السهل تخطّيه: بعد الجلب، تحقّق من أن البيانات التي تلقّيتها تغطّي فعلًا المدة التي طلبتها، وعامِل أي نقص باعتباره فشلًا حتى وإن قالت طبقة النقل إن كل شيء على ما يرام. التغطية خاصية عليك أن تؤكّدها لا أن تفترضها.

كان هناك درس من الدرجة الثانية مختبئ خلف الأول. حين بدأنا نكتشف الاقتطاع، كانت الخطوة البديهية أن نُفشِل التشغيل. لكن الأرقام أعلاه تُبيّن أن هذه الاقتطاعات عابرة: الطلب نفسه بعد ساعة أعاد بيانات كاملة. الإفشال المباشر كان سيحرق حصّة المستخدم مقابل ما هو في جوهره خلل مؤقت من جانب البورصة. لذا فالسلوك الصحيح هو إعادة المحاولة مرة واحدة قبل الإفشال، وهو ما استلزم إعادة صياغة مسار الفشل حتى لا تُوسَم الحالة القابلة لإعادة المحاولة على أنها فشل دائم أثناء الخروج.

خرجت من هذا قاعدتان، وكنت لآخذ كلتيهما إلى أي مشروع يتعامل مع واجهة برمجة تطبيقات تابعة لطرف ثالث:

  • تحقّق من شكل الاستجابة الناجحة، لا من رمز حالتها فقط. رمز الحالة يخبرك أن الطلب اكتمل، لكنه لا يخبرك أن الاستجابة هي ما طلبته.
  • ميّز الأعطال العابرة عن الدائمة قبل أن تقرر ماذا تفعل. فهي تبدو متطابقة لحظة حدوثها لكنها تستحق استجابتين متعاكستين.

2. افترضتُ أن نتيجة الاختبار الرجعي نتيجة

خطأ مرتبط، لكنه متميّز، وهو أعمق من مشكلة الاقتطاع.

حين تبني محاكيًا، تصرف جهدك الاختباري على الاستراتيجيات. هل يتصرف منطق DCA بشكل صحيح؟ هل تُطلَق عمليات الخروج حيث ينبغي؟ هذا هو السؤال المثير، ولذلك يتّجه الانتباه إليه بطبيعة الحال.

المشكلة أن المحاكي المعطوب يُنتج مخرجات تبدو تمامًا كمخرجات المحاكي السليم. التشغيل الذي عالج بصمت جزءًا يسيرًا من البيانات لا يزال يُنتج رقمًا. والتشغيل الذي لم تُقيَّم فيه شروط الخروج قطّ لا يزال يُنتج رقمًا، ومسطّحًا على نحو مريب. رأينا نتائج ترجع بالضبط عند +0.00% بصفر صفقة، وهي أعلى إشارة ممكنة على أن خطبًا ما في الآلية، وأسهل إشارة يُساء قراءتها على أنها «هذه الاستراتيجية لا تفعل شيئًا».

التحوّل الذي أصلح هذا كان تغيير ما نختبره. اختبِر المحاكي، لا الاستراتيجية. عمليًا، يعني ذلك التأكيد على أشياء لا ينبغي أن تقدر الاستراتيجية على تغييرها: أن عدد الشموع المعالَجة يطابق عدد الشموع المجلوبة، وأن تشغيلًا على مدة معلومة يمتدّ فعلًا على تلك المدة، وأن تهيئة مضمونة الإطلاق تُنتج صفقات. حين تصمد هذه الثوابت، يكون أي نتيجة غريبة المظهر اكتشافًا حقيقيًا عن الاستراتيجية. وحين لا تصمد، تكون النتيجة ضجيجًا يرتدي بذلة.

الصورة العامة لهذا، لأي شيء يُحاكي: المحاكي الذي لا يقدر على الفشل بصخب سيفشل بصمت، والفشل الصامت في نظام يُنتج أرقامًا هو أسوأ أوضاع الفشل على الإطلاق. الناس يتصرفون بناءً على الأرقام، ولا يتصرفون بناءً على آثار المكدّس.

3. استهنتُ بمقدار عمل الدقة الذي يتطلبه تحديد حجم الأوامر فعلًا

هذه ليست صعبة فكريًا، لكنها أكبر بكثير مما تبدو، وتختبئ في مكان تكون فيه الأخطاء باهظة.

البورصات لا تقبل كميات اعتباطية. لكل زوج تداول حجم خطوة للكمية وحجم تجزئة للسعر، والأوامر التي لا تتحاذى معهما تُرفَض. لذا يجب تنزيل كل كمية يحسبها النظام إلى خطوة صالحة قبل إرسالها. يبدو ذلك كأنه دالة أداة واحدة.

ثم تأتي الفاصلة العائمة. تأمّل تنزيل 0.29 إلى خطوة مقدارها 0.01. اضربها في المقياس، فيعطيك IEEE-754 القيمة 28.999999999999996، التي تُنزَّل إلى 28، فتعود بالقسمة إلى 0.28. كانت القيمة أصلًا متحاذية مع الخطوة، لكن العملية الحسابية أنزلتها خطوة كاملة، وفعلت ذلك بصمت، في اتجاه أمر أصغر. يتطلب إصلاحها إبسيلون أثناء خطوة الضرب في المقياس، صغيرًا بما يكفي كي تظل قيمة أدنى فعلًا مثل 0.289999 تُنزَّل بشكل صحيح، وكبيرًا بما يكفي لامتصاص ضجيج التمثيل.

هذا إصلاح من سطر واحد استلزم تنقيحًا حقيقيًا لاكتشافه، لأن العرَض ليس استثناءً. إنه أمر أصغر قليلًا من المقصود، على بعض الأزواج دون غيرها، تبعًا لموضع حجم الخطوة نسبةً إلى التمثيل الثنائي.

النقطة الأوسع، وسبب إدراجي هذا ضمن ما استهنتُ به: الدقة في نظام تداول ليست دالة أداة، بل صنف من العمل. أحجام الخطوة وأحجام التجزئة تختلف من زوج لآخر، ومن بورصة لأخرى، وبين السبوت والفيوتشرز. والقيم الدنيا للأوامر ليست رقمًا واحدًا تبحث عنه فتُثبّته في الكود، بل تأتي من البورصة لكل رمز، وعلى بعض المنصات تعتمد على السعر الحالي. وفي أي مرة تجد نفسك تكتب فيها ثابتًا مفردًا لشيء تُعرّفه البورصة لكل زوج، فأنت تكتب علّة ذات فتيل مؤجل.

نحن نُظهِر هذا للمستخدمين بدل إخفائه، لأن البديل هو أمر يُرفَض لأسباب لا يرونها.

4. القيد الذي لم أتوقع أن يعجبني

الأخير ليس خطأً، بل قرار استهنتُ بعواقبه على نحو حسن.

Freya لا تحتفظ أبدًا بأموال تداول المستخدمين. تتداول البوتات عبر مفاتيح API للبورصات بصلاحية القراءة والتداول، دون صلاحية السحب أبدًا. تبقى الأموال في حساب المستخدم على البورصة طوال الوقت.

توقّعتُ أن يكون هذا نقطة تسويقية، لكنه تبيّن نقطة معمارية، وجعل النظام أصغر. لا يوجد دفتر حفظ، ولا رصيد داخلي يُطابَق مع رصيد خارجي، ولا طابور سحب، ولا سياسة محفظة باردة لرأس مال التداول. صنف كامل من أصعب المشكلات في برمجيات المال غائب ببساطة، لأننا لم نأخذ على عاتقنا قطّ الشيء الذي يخلقها.

ما يحلّ محلّه أضيق وأحدّ: يجب أن يكون نظامك صحيحًا في الزمن الحقيقي، لأنه لا يقدر على التراجع عن أي شيء. مع الحفظ يمكنك من حيث المبدأ عكس تحويل داخلي، أما وضع أمر على حساب بورصة شخص آخر فهو نهائي لحظة تنفيذه. هذا يدفع بكل الثقل الهندسي نحو الصحّة قبل خروج الطلب لا نحو المطابقة بعده، وهي مقايضة كنت لأقبلها من جديد.

ما كنت لأقوله لنفسي في البداية

  • افترض أن كل واجهة برمجة تطبيقات تابعة لطرف ثالث تنجح أحيانًا نجاحًا خاطئًا، وصمّم كشفًا لذلك قبل أن تحتاجه.
  • اكتب اختبارات الثوابت لمحاكيك قبل أن تكتب تأكيدات عن الاستراتيجيات. اختبارات الاستراتيجية بلا قيمة حتى تنجح اختبارات المحاكي.
  • عامِل الدقة كمجال، لا كدالة مساعِدة.
  • اختَر قيودك مبكرًا. القيود التي تقبلها سلفًا أرخص بكثير من التي تكتشفها لاحقًا.

لم يكن أي من هذه مشكلات غريبة. كانت مشكلات هندسية عادية صنّفتها على أنها «صغيرة» أثناء التخطيط، وهذا بالضبط كيف صارت باهظة.


قراءات ذات صلة

أنشئ حساباً

ضع معرفتك موضع التطبيق بفتح حساب Freya اليوم.

سجِّل الآن
هذا المحتوى لأغراض تعليمية فقط ولا يُعد نصيحة مالية. ينطوي تداول العملات الرقمية على مخاطر كبيرة. لا تستثمر أبداً أكثر مما يمكنك تحمُّل خسارته.