11 سبتمبر 2026 · قابلية إعادة الإنتاج

الكود نفسه، والبيانات نفسها، وقراءتا Sharpe مختلفتان: تدقيق العشوائية الذي يحتاجه اختبارك التاريخي

الكود نفسه، والبيانات نفسها، وقراءتا Sharpe مختلفتان: تدقيق العشوائية الذي يحتاجه اختبارك التاريخي

الالتزام نفسه، وملفات parquet نفسها، والجهاز نفسه، لكن تشغيلين يفصل بينهما ساعة. Sharpe بقيمتي 1.34 و1.19. وعائد إجمالي 41.2% و37.8%. وعدد صفقات 1,418 و1,421. وظل منحنيَا حقوق الملكية متطابقين في كل منزلة عشرية مطبوعة على مدى 11,205 شمعة متتالية، قبل أن يفترقا.

0.15فرق Sharpe، ومدخلات متطابقة
3 / 1,418صفقات اختلفت
11,205شمعة متطابقة قبل الافتراق

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

الاستراتيجية: زخم مقطعي عرضي على العقود الدائمة USDⓈ-M الأعلى حجمًا، وعددها 30، مع إعادة موازنة كل 4 ساعات، وشراء أعلى خمسة أصول بحسب عائد 12 ساعة، وبيع أدنى خمسة، على مدى 18 شهرًا من البيانات، مع احتساب الرسوم والتمويل لكل مركز.

العثور على الشمعة التي افترق عندها التشغيلان

إذا سجلتم حقوق الملكية لكل شمعة بدقتها الكاملة، فلن يستغرق ذلك سوى نحو 4 دقائق. صدّروا التشغيلين إلى ملفي CSV من (bar_index, equity, open_positions_hash)، وحمّلوهما، واعثروا على أول فهرس يختلفان عنده. كنا قد كتبنا ذلك النص البرمجي لمعالجة خطأ آخر، وهذا وحده ما أنقذني من قضاء صباح كامل في البحث.

الشمعة 11,206، عند 2025-03-14T08:00 UTC. كانت حقوق الملكية متطابقة عند الشمعة السابقة حتى 13 رقمًا معنويًا. عند 11,206 اختلفت بصمات المراكز: التشغيل A شراء SOL، والتشغيل B شراء AVAX. الاتجاه نفسه، والقيمة الاسمية نفسها، لكن الرمز مختلف. وكل ما حدث بعد ذلك نتج عن هذا الاختلاف.

لذا طبعتُ مدخلات الترتيب لتلك الشمعة في كلا التشغيلين. وكانت متطابقة. بايتًا ببايت، مع الرموز الـ30 نفسها والدرجات الـ30 نفسها. كانت درجتان من الدرجات 0.0. صفرًا بالضبط، كلتاهما، لأن الرمزين سجّلا شمعة 4 ساعات بلا صفقات داخل نافذة الاسترجاع، فكانت نسبة الإغلاق إلى الإغلاق واحدًا، واللوغاريتم صفرًا. لم يكن الأمر تقريبًا إلى الصفر. بل صفرًا.

تعادل رمزان على المرتبة الخامسة. وكان الاختيار يأخذ أعلى خمسة. أما أيّهما يدخل، فكان يتوقف على الترتيب الذي أبقته فيه عملية الفرز، وهذه العملية لم تكن تعمل كما افترضت.

تعادل، وفرز غير مستقر، وأمر تجاهلته لعامين

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

مثل هذه التعادلات ليست حدثًا نادرًا. إنها بنيوية. فكلما تشبعت ميزة أو قُصّت قيمها، أنتجتم مساواة تامة: شموع حجم التداول الصفري تعطي عوائد صفرية تمامًا، ودرجة z مقصوصة تثبت عند ±3.0، وتحويل الرتب مع قلة القيم المميزة ينتج عشرات التعادلات، والمرشح المنطقي يعطي كل ما يمرّ منه الدرجة 1.0. خلال 18 شهرًا من شموع 4 ساعات، شهد هذا التشغيل 47 شمعة فيها تعادل عند حد الاختيار. غيّر 3 منها السلة المختارة. أما البقية فكان التعادل بين رمزين داخلين أصلًا، أو خارجين أصلًا.

لمدة يوم تقريبًا، كنت مقتنعًا بأن محمّل البيانات غير حتمي، لأن هذا هو التفسير المثير. لكنه لم يكن كذلك. لا يكون أبدًا. إنها مجموعة، وتعادل، وافتراض بشأن استقرار الفرز لم يدوّنه أحد.

لماذا تستحق 3 صفقات فرق Sharpe قدره 0.15

هذا هو الجزء الذي يعترض عليه الناس، والجواب أن الاختبار التاريخي الذي يحدد حجم المراكز كنسبة من حقوق الملكية نظام يعتمد على مساره. عندما يكون حجم كل مركز 8% من حقوق الملكية الحالية، فإن اختلاف حقوق الملكية عند الشمعة n يعني اختلاف القيمة الاسمية لكل مركز ابتداءً من الشمعة n فصاعدًا.

لم يكلفنا الافتراق الأول الكثير بحد ذاته. خسر مركز AVAX في التشغيل B نسبة 2.1% خلال 9 ساعات، بينما ربح مركز SOL في التشغيل A نسبة 0.4%. بلغ فرق حقوق الملكية بعد تلك الصفقة 0.21%. أمر تافه. لكن منذ تلك اللحظة، لم يعد التشغيلان يطبّقان الاستراتيجية نفسها. فهما يحتفظان بأحجام مختلفة قليلًا، ولذلك تتراكم عليهما معدلات تمويل مختلفة قليلًا، كما حُسم تعادلان لاحقان عند الحدود بطريقة مختلفة مجددًا، لأن الدرجات التي غذتهما أصبحت ناتجة عن مراكز محتفظ بها تختلف هامشيًا. وقع أحدهما في 2025-03-27، قبل يوم من موجة اتجاه استمرت 6 أيام وأسهمت بنحو ثلث إجمالي ربح التشغيل. شارك التشغيل A في الحركة كاملة، بينما دخل التشغيل B بعد إعادة موازنة واحدة.

فرق العائد: 3.4 نقاط. وكان فرق Sharpe أكبر مما يوحي به فرق العائد، لأن إعادة ترتيب صفقات التشغيل B تزامنت مع فترة أشد تقلبًا، فارتفع المقام بينما انخفض البسط. سبب صغير، ومضخّمان للأثر.

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

المواضع الخمسة التي يظهر فيها فعليًا

المصدرالعَرَضالحل
بذرة PYTHONHASHSEED غير مثبتة مع تغذية الفرز بترتيب المرور على مجموعة أو dictيتغير حسم التعادل بين التشغيلات؛ ويظهر أول اختلاف عند شمعة بعينهاثبّتوا البذرة، وفرزوا بمفتاح ثانوي صريح (الرمز) لضمان حسم التعادلات بصورة حتمية
فرز غير مستقر لمفتاح متعادل (quicksort الافتراضي في NumPy/pandas)العَرَض نفسه أعلاه، ويستمر حتى بعد تثبيت البذرةkind="stable"، أو اجعلوا المفتاح شاملًا لكل الحالات
مولّد أرقام عشوائية RNG بلا بذرة مثبتة في bootstrap، أو تبديلات التدريب/الاختبار، أو التذبذب الاصطناعي لسعر التنفيذانحراف على امتداد التشغيل كله، بلا نقطة افتراق واضحةبذرة صريحة لكل مكوّن، مع تسجيلها في بيان التشغيل
تقليل متوازٍ للقيم العائمة (يتغير ترتيب الجمع حسب عدد الخيوط)اختلافات في البتات الأخيرة القليلة، غير مؤذية عادةً حتى تتجاوز حدًا في المقارنةثبّتوا عدد الخيوط في تشغيلات البحث؛ ولا تقارنوا القيم العائمة باستخدام == عند حدّ اتخاذ القرار
إصدارات المكتبات غير المثبتةقابل لإعادة الإنتاج اليوم، لا في نوفمبرسجّلوا بصمة ملف القفل إلى جانب بصمة لقطة البيانات في البيان

الصف الرابع هو الأقل أهمية مما يخشاه الناس، والصف الأول هو الذي يوقعهم في المشاكل باستمرار.

التطابق على مستوى البتات أداة، لا غاية بحد ذاته

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

لكن ما إن تتمكنوا من إعادة الإنتاج، فاكسروا الحتمية عمدًا. شغّلوا الأمر 64 مرة باستخدام 64 بذرة، وانظروا إلى التوزيع:

نطاق التذبذب. الاستراتيجية نفسها والبيانات نفسها، مع 64 تبديلًا لبذور حسم التعادل وترتيب التنفيذ. قيمة Sharpe عند p5 تساوي 1.12، والوسيط 1.27، وعند p95 تساوي 1.41. عرض النطاق 0.29.

والآن عودوا إلى مسح المعلمات. حقق أفضل إعداد 1.46. أما الإعداد الذي حلّ في المرتبة 40 من أصل 96، فحقق 1.31. الفرق بينهما 0.15، أي نصف النطاق. لم يرتّب المسح هذين الإعدادين؛ بل أخذ عينة واحدة من توزيع كل منهما، ثم رتّب العينتين.

غيّرت إعادة صياغة المسألة هذه طريقة اختيارنا. لا تمثل نتيجة المسح ترتيبًا إلا إذا تجاوزت الفروق بين الإعدادات نطاق تذبذب إعداد واحد، وفي استراتيجية يعتمد أداؤها على مسارها وتضم 1,400 صفقة، يكون التذبذب عادةً كافيًا لتحويل الثلث الأعلى من لوحة الصدارة إلى تعادل. عندها، اختاروا بناءً على أمور لا يستطيع النطاق إخفاءها: معدل تداول أقل، ومعلمات أقل، وافتراض بشأن التكاليف يمكنكم الدفاع عنه أمام المتشككين، وأداء أفضل في طية الاختبار المتدرج التي تفضلونها أقل من غيرها. تلك عوامل حقيقية لحسم التعادل. أما أفضلية Sharpe بمقدار 0.15 فليست كذلك.

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

الحتمية، الاختبار التاريخي، هندسة البيانات، فرط الملاءمة، python
→ جميع المقالات