مشروع دفتر بحث كامل من البيانات إلى التقرير، مع قائمة تحقق لمراجعة الكود ضد انحياز الاستباق والإفراط في الملاءمة
نظّم دراسة كاملة لاستراتيجية في صورة مشروع قابل لإعادة الإنتاج، وشغّلها من البيانات الخام إلى نتيجة مكتوبة، وراجع الكود بقائمة تحقق تلتقط الأخطاء التي تجمّل نتائج الاختبارات الرجعية.
ما الذي ستتعلمه
- بنية مجلدات ودفاتر تجعل الدراسة قابلة لإعادة الإنتاج من قِبلك بعد ستة أشهر أو من قِبل أي شخص آخر.
- ترتيب العمل: البيانات، والتنظيف، والسمات، والإشارات، والاختبار الرجعي، والمقاييس، وفحص خارج العينة، والتقرير.
- قائمة تحقق لمراجعة الكود سطرًا بسطر بحثًا عن انحياز الاستباق وانحياز البقاء والإفراط في الملاءمة.
- كيف تكتب النتيجة بنزاهة، بما في ذلك ما جُرِّب ولم ينجح.
الشرح
جرى تناول الاختبار خارج العينة وهضاب المعاملات وتكلفة كثرة التجارب في دورة «التداول المنهجي والاختبار الرجعي» (الدرسان 6 و7). أما هذا الدرس فيتناول الهندسة التي تجعل تلك الأفكار صامدة: بنية مشروع، وسير عمل ثابت، ومراجعة يمكن لشخص آخر تكرارها.
بنية المشروع
project/data/raw/ # التنزيلات، لا تُعدَّل يدويًا أبدًاdata/clean/ # ملفات parquet التي يُنتجها 01_data.ipynbnotebooks/ # 01_data, 02_features, 03_backtest, 04_reportsrc/ # indicators.py, engine.py, metrics.py (الدروس 6 و8 و9)tests/ # test_metrics.py, test_engine.pyconfig.yaml # الرموز والتواريخ والمعاملات وافتراضات التكلفةrequirements.txt # إصدارات مثبّتة: pandas==2.x, numpy==…
يعيش الكود المعاد استخدامه في src/؛ ولا تفعل الدفاتر سوى استدعاء الدوال وعرض النتائج. ثبّت إصدارات الحزم، لأن تحديثًا لـ pandas قد يغيّر إعدادًا افتراضيًا (سلاسل التواتر، وتسميات إعادة التشكيل) فيحرّك أرقامك. ثبّت بذرة أي عنصر عشوائي، وسجّل تاريخ تنزيل البيانات في التقرير.
سير العمل بالترتيب
- الفرضية أولًا. جملة واحدة من المنطق الاقتصادي والقواعد الدقيقة قبل لمس البيانات (دورة «التداول المنهجي والاختبار الرجعي»، الدرس 2). ثبّت الآن التقسيم بين داخل العينة وخارجها، ولا تفتح ملف خارج العينة حتى الخطوة 7.
- البيانات. نزّلها، وخزّنها خامًا، ووثّق المصدر وطريقة التعديل (الدرس 4). واختبار قائمة «أعضاء المؤشر اليوم» على عشر سنوات ماضية هو انحياز البقاء.
- التنظيف والتحقق. الفجوات، والتكرارات، والأسعار الصفرية، والمناطق الزمنية؛ ثم الحفظ في
data/clean/. - السمات والإشارات باستخدام الدوال المُختبَرة من
src/؛ وكل سمة عند الشمعة t تستخدم البيانات حتى t فقط. - الاختبار الرجعي باستخدام محرّك الدرس 8، بما في ذلك نموذج التكلفة (دورة «التداول المنهجي والاختبار الرجعي»، الدرس 8).
- المقاييس والرسوم البيانية (الدرسان 9 و10)، على فترة داخل العينة، مع شبكة معاملات صغيرة، لا بحثًا عن أفضل خلية.
- تشغيل خارج العينة مرة واحدة، مع تجميد المعاملات. سجّل النتيجة أيًّا كانت.
- التقرير: الفرضية، والبيانات، والقواعد، والتكاليف، وجدولا النتائج كلاهما، والرسوم البيانية، وعدد المتغيرات المجرَّبة، والقرار.
قائمة تحقق لمراجعة الكود
راجع دفترك كما لو كان دفتر شخص آخر؛ فكل سطر سؤال إجابته نعم أو لا.
| المجال | الفحص | الإصلاح المعتاد |
|---|---|---|
| التوقيت | هل أُزيحت كل إشارة بشمعة واحدة قبل أن تحدّد مركزًا؟ | pos = signal.shift(1) |
| التوقيت | هل تستخدم النوافذ المتحركة center=False (القيمة الافتراضية) ودون bfill()؟ |
أزل التوسيط والملء الخلفي |
| التوقيت | هل تُضبط عمليات التطبيع (درجات z، والرتب، وأدوات التحجيم) على البيانات الماضية فقط؟ | ضبط على نوافذ متوسّعة أو متحركة |
| البيانات | هل يُعرَّف نطاق الأدوات كما كان معروفًا في كل تاريخ؟ | قوائم مكوّنات بحسب النقطة الزمنية أو تنبيه صريح |
| البيانات | هل تُستخدم الأسعار المعدّلة للعوائد وغير المعدّلة لمسافات الوقف عند الاقتضاء؟ | احتفظ بالعمودين كليهما |
| التنفيذ | هل تُنفَّذ الفجوات السعرية عند الافتتاح، لا عند سعر الوقف أو سعر الإشارة؟ | min(open, stop) |
| التكاليف | هل تُطبَّق العمولات والفارق السعري والانزلاق السعري لكل صفقة ولكل جانب؟ | نموذج التكلفة في ملف الإعدادات |
| الإفراط في الملاءمة | كم مجموعة معاملات وقواعد ونطاقات أدوات جُرِّبت؟ وهل العدد مذكور في التقرير؟ | سجّل كل تشغيل |
| الإفراط في الملاءمة | هل تعطي المعاملات المجاورة نتائج متشابهة (هضبة)؟ | خريطة حرارية للشبكة |
| الإفراط في الملاءمة | هل لُمست بيانات خارج العينة أكثر من مرة؟ | إن كان الجواب نعم، فقد صارت الآن داخل العينة؛ ابحث عن بيانات جديدة |
| قابلية إعادة الإنتاج | هل يعيد «إعادة تشغيل النواة وتشغيل الكل» إنتاج الأرقام الواردة في التقرير؟ | أصلح الحالة الخفية وترتيب الخلايا |
قاعدة تقريبية للدلالة الإحصائية: إحصائية t لنسبة شارب السنوية تساوي تقريبًا نسبة شارب × √عدد السنوات. نسبة شارب قدرها 0.8 على أربع سنوات تعطي 1.6، أي دون العتبة المعتادة البالغة نحو 2، وذلك قبل مراعاة عدد المتغيرات المجرَّبة؛ فمن بين عشرين تجربة يُتوقع أن يبدو أفضلها جيدًا بمحض الصدفة.
مثال محلول
مثال افتراضي، وجميع الأرقام مختلَقة. يختبر متداول قاعدة «الشراء عند اختراق 100 يوم، والخروج بوقف متحرك عند 3 × ATR» على 30 سهمًا من الشركات الكبيرة، للفترة 2014–2025، بتكاليف قدرها 0.1 % لكل جانب.
- التقسيم. داخل العينة 2014–2021 (8 سنوات)، وخارج العينة 2022–2025 (4 سنوات)، وقد تقرّر ذلك قبل التشغيل الأول.
- الشبكة. أطوال الاختراق 50 و100 و150 × مضاعفات ATR 2 و3 و4 = 9 متغيرات، مسجّلة. تتراوح نسبة شارب داخل العينة بين 0.6 و1.1، وأفضل خلية عند (100، 3) = 1.1؛ وجارتاها 0.9 و1.0، أي هضبة، لذا يُحتفظ بالاختيار.
- المراجعة. تكشف قائمة التحقق أن عمود ATR استخدم
rolling(14).mean()على المدى الحقيقي لليوم من أجل وقف اليوم. فيُعدَّل المحرّك ليستخدم ATR للشمعة السابقة (الدرس 8). تنخفض نسبة شارب داخل العينة من 1.1 إلى 1.0؛ ويبقى أقصى تراجع دون تغيير عند −22 %. - خارج العينة. تشغيل مرة واحدة: نسبة شارب 0.6، وCAGR 7.2 %، وأقصى تراجع −19 %، و118 صفقة. الدلالة: 0.6 × √4 = 1.2؛ بينما أعطى رقم داخل العينة 1.0 × √8 = 2.8. نتيجة خارج العينة إيجابية، لكنها ضعيفة إحصائيًا بمفردها.
- القرار في التقرير. «احتفظت القاعدة بنحو 60 % من نسبة شارب داخل العينة عند الانتقال إلى خارج العينة (0.6 / 1.0)، على هضبة، مع تجربة 9 متغيرات وإصلاح خلل استباق واحد. الأدلة متواضعة؛ والخطوة التالية ستة أشهر من التداول الورقي باستخدام سكربت الدرس 12، لا رأس مال حقيقي.» الرسوم مُنمذَجة؛ والضرائب والتمويل مستبعدة.
خطأ شائع
نشاط تطبيقي
على الورق: راجع مقطع الدفتر هذا والنتيجة التي تلته.
df['z'] = (df.close - df.close.mean()) / df.close.std()df['signal'] = df.z < -1.5df['pos'] = df.signal.astype(int)df['ret'] = df.close.pct_change() * df.pos
نسبة شارب داخل العينة 2.4 على 6 سنوات؛ ونسبة شارب خارج العينة −0.3 على سنتين. (أ) حدّد مشكلتَي استباق في الكود. (ب) احسب إحصائيتَي t التقريبيتين لنسبتَي شارب داخل العينة وخارجها. (ج) أعد كتابة الأسطر الأربعة بحيث تخلو من الاستباق (أو صِف التغييرات نثرًا إن شئت).
عرض الحل
(أ) أولًا، يستخدم df.close.mean() وdf.close.std() العيّنة كلها، بما فيها الأسعار المستقبلية، لذا فإن درجة z في أي تاريخ «تعرف» المتوسط المستقبلي. ثانيًا، pos غير مُزاح: إذ يُكسَب عائد اليوم بناءً على إشارة محسوبة من إغلاق اليوم.
(ب) داخل العينة: 2.4 × √6 = 5.9؛ وخارج العينة: −0.3 × √2 = −0.4. القيمة الكبيرة داخل العينة أنتجتها الأخطاء البرمجية، ولهذا انهارت.
(ج) استخدم نافذة متحركة أو متوسّعة وإزاحة:
m = df.close.rolling(250).mean(); s = df.close.rolling(250).std()df['z'] = (df.close - m) / sdf['signal'] = df.z < -1.5df['pos'] = df.signal.shift(1).fillna(False).astype(int)df['ret'] = df.close.pct_change() * df.pos
القيود: إزالة الاستباق تجعل الاختبار نزيهًا لا مربحًا؛ ولا تزال القاعدة المصحّحة بحاجة إلى التكاليف ومحرّك بأوامر وقف وفترة جديدة خارج العينة.
أهم النقاط
- احتفظ بالكود القابل لإعادة الاستخدام في
src/مع الاختبارات، وثبّت الإصدارات، واجعل «تشغيل الكل» يعيد إنتاج التقرير. - ثبّت الفرضية والقواعد والتقسيم بين داخل العينة وخارجها قبل الاختبار الرجعي الأول؛ وشغّل خارج العينة مرة واحدة.
- راجع التوقيت والبيانات والتنفيذ والتكاليف وأعداد التجارب بقائمة تحقق؛ وسجّل كل متغير جرّبته.
- اذكر الدلالة تقريبيًا بوصفها نسبة شارب × √عدد السنوات، وعدد المتغيرات، والقرار، بما في ذلك «الأدلة غير كافية».
الخطوة التالية
بهذا تكتمل دورة «بايثون للمتداولين والمستثمرين»؛ وتتناول الدورة التالية، «شركات التداول الممولة والحسابات الممولة والتداول كعمل تجاري»، قواعد التقييم وأنواع التراجع والجانب العملي والضريبي الألماني للتداول بوصفه عملًا تجاريًا، حيث تكون عملية مُختبَرة ومبرمجة بسكربتات كالتي بنيتها هنا نقطة الانطلاق.
تقييم ذاتي
أجب عن الأسئلة، ثم تحقّق من إجاباتك. يمكنك إعادة المحاولة بقدر ما تشاء.
تقييم ذاتي تلقائي لمراجعتك الشخصية. تُفحَص إجاباتك في متصفحك ولا نطّلع عليها ولا نقيّمها. هذا ليس امتحانًا ولا مؤهّلًا.
تقدّمك
«مكتمل» يعني فقط أنك علّمت الدرس كمنتهٍ. وهو ليس اختبارًا لفهمك. يُحفظ التقدّم والملاحظات فقط في هذا المتصفح على هذا الجهاز — وليس في حساب.