اختبار خلفي قابل لإعادة الإنتاج في Python بلا API key
شغّل اختباراً خلفياً قابلاً لإعادة الإنتاج في Python بلا API key، مع تثبيت الإصدار 0.12.4 وبيانات حتمية، واكتشف ما تخفيه منحنى أسهم واحد.
الاختبار الخلفي القابل لإعادة الإنتاج في Python هو اختبار يستطيع شخص آخر تشغيله من جديد على جهاز نظيف والحصول على أرقامك نفسها تماماً، من دون أن يعترض طريقه حساب أو API key. تفشل معظم الشروحات في هذا الاختبار منذ السطر الأول، إذ يزوّد التنزيل المباشر القارئ التالي بسجل أسعار يختلف قليلاً عن السجل الذي حصل عليه الكاتب. يثبّت هذا الدليل محركاً مفتوح المصدر واحداً، quantjourney-bt، عند الإصدار 0.12.4، ويشغّل المثال المرفق معه من دون أي بيانات اعتماد، ثم يستخدم بيانات سوق فعلية ليوضح ما لا يستطيع حتى تشغيل واحد منظم إخبارك به.
ما الذي يجعل الاختبار التاريخي قابلاً لإعادة الإنتاج؟
إعادة الإنتاج لها معنى محدد وقابل للاختبار هنا: يشغّل شخص آخر أمراً واحداً على جهاز نظيف، ويحصل على أرقامك نفسها حتى المنازل العشرية. هناك أمران شائعان يفسدان ذلك.
الأول هو الكود. فالمكتبة التي تحمل إصداراً من نمط 0.x لا تضمن التوافق بين الإصدارات الفرعية. وقد يؤدي تغيير اسم أو قيمة افتراضية أو ترتيب الأعمدة إلى استمرار تشغيل النص البرمجي، مع إظهار نتائج مختلفة دون تنبيه.
الثاني هو البيانات. فالشرح التعليمي الذي يبدأ بتنزيل مباشر من مصدر حي لم يكن قابلاً لإعادة الإنتاج قط. يراجع مزودو البيانات السجل التاريخي، ويعدّلونه لأغراض التجزئة، ويستكملون الفجوات. لذلك يعرض النص البرمجي نفسه أرقاماً مختلفة بعد شهر. وبعد وقوع التغيير، لا يمكنك فصل أثر تغيير الكود عن أثر تغيير البيانات.
النمط الجدير بالاقتداء في هذا المشروع هو الجمع بين إصدار ثابت للمحرّك ومجموعة بيانات صغيرة مضمّنة تُوزَّع داخل الحزمة نفسها.
ثبّت الإصدار: pip install quantjourney-bt==0.12.4
quantjourney-bt هو محرك الاختبار الخلفي QuantJourney، ويُوزَّع بموجب ترخيص Apache 2.0، ويتطلب Python 3.11 أو إصداراً أحدث. نُشر الإصدار 0.12.4 في 21 يوليو 2026، وهو الإصدار الذي تشير إليه كل الأوامر أدناه، وفقاً للتوثيق الصادر في أغسطس 2026.
اعمل داخل بيئة معزولة. ينشئ python3 -m venv .venv هذه البيئة، ويدخلك إليها source .venv/bin/activate، ويحدّث python -m pip install -U pip برنامج التثبيت داخلها. ثم ثبّت الإصدار المحدد تماماً باستخدام: pip install quantjourney-bt==0.12.4.
يوثّق المشروع الصيغة غير المقيدة، pip install quantjourney-bt. أما الجزء ==0.12.4 فهو مسؤوليتك، وتظهر أهميته خصوصاً في إصدارات 0.x. اكتب تثبيت الإصدار في مكان يستطيع الشخص التالي العثور عليه فيه: يسجّل pip freeze > requirements.txt كل تبعية جرى حلّها، بما في ذلك التبعيات التي لم تذكرها صراحةً.
توجد إضافتان اختياريتان. تضيف pip install "quantjourney-bt[wf]" مكتبة Optuna إلى أمثلة الاختبار المتدرج والتحسين. وتضيف pip install "quantjourney-bt[data]" خياراً احتياطياً يعتمد على yfinance ويُستخدم في المقارنات المرجعية.
ترخيص Apache-2.0 ترخيص متساهل. يمكنك استخدام الشيفرة وتعديلها لأغراض تجارية. ويجب أن تُبقي ملفات الترخيص والإشعارات مع أي إعادة توزيع. كما يمنح المساهمون حقوق براءات الاختراع صراحةً.
كيفية تشغيل مثال SMA المضمّن من دون مفتاح API
يأتي المستودع مزوّداً ببرنامج تشغيل إلى جانب خمسين استراتيجية نموذجية قابلة للتشغيل، موزعة بين مسار قائم على الأوزان ومسار قائم على الأوامر، وتتضمن خمس عمليات سير عمل للاختبار المتدرج. يعرض ./strategy.sh --list الفهرس. ويستورد ./strategy.sh example_weights_01_sma_daily --check استراتيجية واحدة من دون الوصول إلى أي بيانات، وهو أسرع اختبار للتأكد من سلامة التثبيت.
يتطلب التشغيل التجريبي سطراً واحداً: ./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample
تحمل الراية --sample-data الفكرة الأساسية كاملة. ويصف المشروع مجموعة البيانات التي تستخدمها على النحو الآتي:
مجموعة البيانات النموذجية صغيرة وقابلة لإعادة الإنتاج عمداً. وهي مفيدة للتحقق من التثبيت، وإنشاء التقارير، وقراءة مسار عمل المحرك من دون إنشاء حساب.
المصدر: ملف README لمشروع quantjourney-bt، الإصدار 0.12.4، تمت قراءته في 6 أغسطس 2026.
ينشئ التشغيل مجلداً بدلاً من عرض نتيجة على وحدة التحكم: summary.txt وsummary.json، وmetrics.csv، وequity_curve.csv إلى جانب equity_curve.png، وdashboard.html، ومجلد plots/، وrun_metadata.json يسجل كيفية إعداد التشغيل. وهذا الملف الأخير هو أكثر ما يتجاهله المستخدمون، مع أنه ما يجعل النتيجة قابلة للتدقيق بعد عام.
اقرأ المقاييس الناتجة بموضوعية. مجموعة البيانات المضمّنة صغيرة وتوضيحية، لذلك فإن نسبة Sharpe والحد الأقصى للتراجع المعروضتين في summary.txt تصفان ملفاً نموذجياً. ولا تقدمان دليلاً على أداء استراتيجية، واعتبارهما نتيجة فعلية هو أول خطأ يمكن أن ترتكبه.
ما يثبته التشغيل فعلياً مهم: التثبيت يعمل، ومسار المحرك الكامل، من الإشارة إلى الأوزان المستهدفة ثم إلى قيمة المحفظة المعاد بناؤها، ينشئ مخرجاته على جهازك من دون أي بيانات اعتماد. ويوجد مسار يتطلب بيانات اعتماد للوصول إلى خدمة البيانات الخاصة بالمشروع وإجراء اختبارات تاريخية على بيانات فعلية. هذا المسار موثق، ويتوقف هذا الشرح هنا، عند الجزء الذي لا يتطلب شيئاً من أي جهة.
ما الذي لا يخبرك به منحنى واحد للأداء داخل العينة
يعرض التشغيل النموذجي منحنى أداء واحداً. لكن هذا المنحنى لا يستطيع تقديم الصورة الكاملة عند قياسه على بيانات سوق حقيقية بدلاً من ملف تجريبي.
يأخذ الجدول أدناه الفكرة نفسها التي تستخدمها الاستراتيجية النموذجية: تقاطع متوسط متحرك لـ20 جلسة مع متوسط متحرك لـ50 جلسة. ويطبقها على SPY، ثم يعرض النتائج لكل سنة تقويمية على حدة من 2017 إلى 2025. يُحدَّد المركز في كل جلسة استناداً إلى إغلاق الجلسة السابقة، ولذلك لا تتداول القاعدة أبداً بناءً على رقم لم يكن متاحاً في ذلك الوقت.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2016-01-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY d
),
averaged AS
(
SELECT
d,
px,
avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
d,
px,
if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
toYear(d) AS year,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
round((exp(sum(log(px / px_prior))) - 1) * 100, 1) AS hold_pct,
countIf(long_today != long_prior) AS crossover_count
FROM positioned
WHERE px_prior > 0
AND toYear(d) >= 2017
GROUP BY year
ORDER BY yearقاعدة واحدة لم تتغير، جرى قياسها في 9 فترات منفصلة. اقرأ عمودي النسب المئوية أولاً قبل أن تقرأ أي شيء آخر. في 2017، أنهت القاعدة السنة عند 16%، مقابل 19.4% للاحتفاظ بـ SPY خلال الفترة نفسها. وفي 2025، بلغ الرقمان في العمودين نفسيهما 10.4% و16.4%. الكود متطابق في الصفين. التغيير الوحيد كان في الفترة الزمنية.
يوضح عمود التقاطعات مدى ضآلة الأدلة الأساسية. فحدوث 4 تغييراً في المركز خلال 2025 يعني أن منحنى الأداء لسنة كاملة يستند إلى عدد قليل من القرارات، وهي عينة صغيرة جداً لاعتبارها نتيجة موثوقة.
هل تتصرف القاعدة نفسها بالطريقة ذاتها مع أوراق مالية أخرى؟
يُعد تغيير النافذة الزمنية إحدى طرق فحص منحنى واحد. أما الطريقة الأخرى فهي تغيير مجموعة الأوراق المالية. يثبت الجدول أدناه المعلمات ويطبّق القاعدة نفسها على خمسة أسماء عالية السيولة خلال السنوات التقويمية الخمس من 2021 إلى 2025.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'QQQ', 'AAPL', 'MSFT', 'KO')
AND window_start >= '2020-07-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY ticker, d
),
averaged AS
(
SELECT
ticker,
d,
px,
avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (PARTITION BY ticker ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
ticker,
d,
px,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
ticker AS symbol,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
round((exp(sum(log(px / px_prior))) - 1) * 100, 1) AS hold_pct,
round(avg(long_prior) * 100, 0) AS days_long_pct
FROM positioned
WHERE px_prior > 0
AND d >= toDate('2021-01-01')
GROUP BY symbol
ORDER BY rule_pct DESCتتصدر QQQ الجدول عند 40.2%، بينما يبلغ الصف الأخير، KO، 3.8%. يوضح عمود days_long_pct النسبة التي قضتها كل نسخة من القاعدة وهي تحتفظ بأي مركز على الإطلاق خلال النافذة، وتبلغ 67% للصف العلوي. مجموعة واحدة من المعلمات، وخمس مجموعات من الأوراق المالية، وفارق واسع إلى حد يجعل اختيار الفائز بعد ظهور النتائج لا يخبرك بشيء عن الاختبار الذي لم تُجرِه بعد.
لا يُعد هذا توصية بتداول استراتيجية تقاطع. فالتقاطع أداة قياس للاختبار الرجعي، والاختبار الرجعي هو ما نقيسه.
كيف يتسلل انحياز النظر إلى المستقبل إلى الاختبار الرجعي للأوزان
يحوّل محرّك قائم على الأوزان الإشارة إلى أوزان مستهدفة، ويحاكي تنفيذ الأوامر وفقاً لهذه الأوزان، ثم يعيد بناء قيمة المحفظة من المراكز الناتجة. ويكمن الخلل في عملية الربط بين الإشارة والوزن. فإذا كان وزن اليوم مستنداً إلى سعر إغلاق اليوم، ثم حقق عائد اليوم، فهذا يعني أن الاختبار الرجعي تداول بناءً على معلومات لم تكن متاحة عند إدخال الأمر. ذلك هو انحياز النظر إلى المستقبل، ولا ينتج عنه أي خطأ. بل يجعل أداء كل شيء يبدو أفضل.
يعرض الجدول أدناه نسختين من القاعدة نفسها على سجل الأسعار التاريخي نفسه لـ SPY.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2016-01-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY d
),
averaged AS
(
SELECT
d,
px,
avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
d,
px,
if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
toYear(d) AS year,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS next_bar_pct,
round((exp(sum(log(if(long_today = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS same_bar_pct,
round(abs(exp(sum(log(if(long_today = 1, px / px_prior, 1.0))))
- exp(sum(log(if(long_prior = 1, px / px_prior, 1.0))))) * 100, 1) AS gap_pp
FROM positioned
WHERE px_prior > 0
AND toYear(d) >= 2017
GROUP BY year
ORDER BY yearفي 2017، أظهرت نسخة الجلسة السابقة 16%، بينما أظهرت نسخة الجلسة نفسها 17.1%، بفارق قدره 1.1 نقطة مئوية. وفي 2025، بلغ الفارق بين القياسين 1.6 نقطة مئوية. لا يمكن أن ينتج أحد العمودين إلا عن آلة لم تكن تعرف مسبقاً أين ستغلق الجلسة. أما الفارق بينهما فهو محاسبي بحت، ولا يستند إلى فكرة أو مهارة أو صفقة.
يحدد هذا المحرك بنفسه موقفه من مسألة التوقيت، وهو أمر أكثر قيمة من مجرد الوعد:
بالنسبة إلى عمليات التنفيذ عند الافتتاح، يعتمد الانزلاق الحساس للنطاق على الشريط المكتمل السابق فقط، ويُتنبأ بالطاقة الاستيعابية للحجم من ملاحظات متأخرة؛ ولا يستخدم المحرك أعلى اليوم أو أدناه أو إغلاقه اللاحق، ولا حجم اليوم الكامل.
المصدر: ملف README الخاص بـ quantjourney-bt، الإصدار 0.12.4، تمت قراءته في 6 أغسطس 2026.
يمكن التحقق من الافتراض الموثق بالرجوع إلى المصدر الذي ثبّتَّه بالفعل. أما الافتراض غير الموثق فليس سوى تخمين.
لماذا توجد الإضافة الخاصة باختبار Walk-Forward
تأتي أمثلة Walk-Forward، من WF01 إلى WF05، مع الإضافة [wf] واعتمادها على Optuna. يضبط اختبار Walk-Forward المعلمات باستخدام شريحة واحدة من البيانات التاريخية، ثم يقيس أداءها على الشريحة التالية، وبعد ذلك يحرّك الشريحتين إلى الأمام ويكرر العملية. وتختلف نسختا النافذة المتحركة والنافذة المتسعة في ما إذا كانت نافذة الضبط تستبعد أقدم بياناتها أثناء تقدمها. ويضيف أحد الأمثلة فترة تطهير وحظراً عند كل حد فاصل، فيستبعد المشاهدات الأقرب إلى نقطة الالتقاء، حتى لا تتسرب الشريحة التي جرى ضبط النموذج عليها إلى الشريحة التي يُقاس عليها أداؤه.
لا يحوّل أي من ذلك فكرة ضعيفة إلى فكرة ناجحة. لكنه يستبدل رقماً واحداً بتوزيع من الأرقام يمكنك مناقشته، وهذه هي الترقية الأساسية. أما الخطوة التالية فليست استخدام أموال حقيقية بعد: إذ يقيس التداول التجريبي قبل استخدام أموال حقيقية ما لا يستطيع الاختبار التاريخي رؤيته بنيوياً، بدءاً من مدى اقتراب تنفيذ أمرك من السعر الذي افترضه المحاكي، بينما تغطي قواطع الحماية لروبوتات التداول ما يفعله برنامجك في اليوم الذي لا يعمل فيه كما ينبغي. وللاطلاع على الإحصاءات التي يستند إليها كل ذلك، تتعمق ملاحظاتنا حول كتاب التداول الكمي مفتوح المصدر بدرجة أكبر.
الأسئلة الشائعة
هل يمكنك اختبار استراتيجية تاريخياً من دون مفتاح API؟
نعم. تتضمن quantjourney-bt مجموعة بيانات نموذجية مرفقة يمكن الوصول إليها عبر العلامة --sample-data، وتعمل الاستراتيجيات النموذجية عليها من دون حساب أو بيانات اعتماد. مجموعة البيانات صغيرة وتوضيحية، لذا تعامل مع هذه العملية باعتبارها فحصاً للتثبيت وسير العمل، لا دليلاً على فعالية استراتيجية.
لماذا يجب تثبيت إصدار حزمة Python الخاصة بالاختبار التاريخي؟
لا تضمن الحزمة التي تحمل إصداراً من سلسلة 0.x التوافق بين الإصدارات الفرعية، وقد لا تعلن عن تغيير قيمة افتراضية أو إعادة تسمية مقياس. ويعني تثبيت الإصدار باستخدام pip install quantjourney-bt==0.12.4 وتسجيل البيئة في ملف المتطلبات أن النتيجة التي تنتجها اليوم يمكن إعادة بنائها العام المقبل على المحرك نفسه الذي أنتجها.
بموجب أي ترخيص تُصدر quantjourney-bt؟
بموجب Apache License 2.0. يتيح الترخيص الاستخدام التجاري والتعديل، ويطلب منك إبقاء ملفات الترخيص والإشعارات مع أي إعادة توزيع، كما يتضمن منحاً صريحاً لحقوق براءات الاختراع من المساهمين. نُشر الإصدار 0.12.4 في 21 يوليو 2026، ويتطلب Python 3.11 أو إصداراً أحدث.
هل تعني نتيجة قوية للاختبار التاريخي أن الاستراتيجية ناجحة؟
لا. الاختبار التاريخي هو قياس واحد، خلال فترة واحدة، وعلى مجموعة أوراق مالية واحدة. وتُظهر اللوحات أعلاه أن القاعدة نفسها، من دون تغيير، أنتجت أرقاماً سنوية مختلفة جداً على ticker واحد، وأرقاماً مختلفة جداً عبر خمسة أسماء. وهذه هي الفجوة التي صُمم اختبار walk-forward والاختبار خارج العينة لكشفها.
كيفية احتساب اللوحات أعلاه
تمثل أسعار الإغلاق اليومية آخر print منتظم للجلسة في كل تاريخ، وفق توقيت نيويورك بين الساعة 9:30 صباحاً والساعة 4:00 مساءً. ويحافظ ذلك على صحة احتساب أيام الإغلاق المبكر من دون تثبيت مدة الجلسة مسبقاً. يغطي المتوسط السريع 20 جلسة، ويغطي المتوسط البطيء 50 جلسة، وكلاهما متوسط بسيط. أما أول 49 جلسة من كل سلسلة فهي فترة تمهيد لا تُحتفظ خلالها بأي مركز. تُركّب الأرقام السنوية حركة الإغلاق إلى الإغلاق لكل جلسة كانت القاعدة فيها في مركز شراء، بينما يركّب عمود الاحتفاظ حركة كل جلسة في السنة نفسها للمقارنة. اختيرت الأسماء الخمسة في المقطع المقارن لسجلات متصلة لا تتضمن أي split داخل الفترة، ولذلك لا تحتاج سلسلة الإغلاق إلى تعديل. الفترات ثابتة في الماضي، لذا تعيد هذه اللوحات الأرقام نفسها عند كل إعادة توليد.
تتضمن كل لوحة هنا استعلام SQL الكامل أسفلها مباشرة، ما يجعل إعادة تشغيل أرقام هذه الصفحة ممكنة، كما هي الحال مع التثبيت ذي الإصدار المحدد. ولقياس قاعدة خلال فترة زمنية من اختيارك قبل كتابة أي كود للاختبار التاريخي، اطرح السؤال باللغة الإنجليزية الواضحة على محطة Strasmore الطرفية.