فترة الثقة بالـBootstrap للاختبار التاريخي
منحنى رأس مال واحد هو عينة واحدة. تعرّف إلى بناء فترة ثقة بالـBootstrap لنسبة Sharpe في الاختبار التاريخي وقراءتها، حتى عندما تشمل الصفر.
تجيب فترة الثقة في الاختبار التاريخي عن سؤال واحد: ما الجزء من منحنى رأس المال الذي يعود إلى الاستراتيجية، وما الجزء الذي يعود إلى الفترة المحددة من التاريخ التي طُبِّقت عليها؟ تُنشئ عملية Bootstrap للاختبار التاريخي هذه الفترة عبر إعادة أخذ عينات من سلسلة العوائد مرات عديدة، وإعادة حساب المقياس الإحصائي لكل عينة، ثم قراءة المئينات للنتائج. فمقياس Sharpe البالغ 1.4، والمقاس على مدى سنة واحدة من المشاهدات اليومية، يحمل فترة ثقة عند مستوى 95 percent واسعة بما يكفي لتشمل الصفر. وتوضح اللوحات أدناه أسباب ذلك.
لماذا يمثل منحنى أداء واحد عينة واحدة
يمنحك الاختبار الخلفي رقماً واحداً لكل مقياس: نسبة Sharpe واحدة، وعائداً سنوياً واحداً، وأسوأ تراجع واحداً، ومعدل نجاح واحداً. لا تمثل أيٌّ من هذه الأرقام القيمة الحقيقية للاستراتيجية. فكل رقم منها تقدير مبني على فترة محدودة من أيام التداول، وكان مسار آخر بالطول نفسه سيعطي نتيجة مختلفة.
يلغي الرسم أدناه الاستراتيجية من المعادلة تماماً. فهو يقيس أبسط مركز ممكن: الاحتفاظ بـ SPY لمدة سنة تقويمية في كل مرة، استناداً إلى عوائد الأسعار من الإغلاق إلى الإغلاق.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
date,
toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2011-12-01'
AND date < '2026-01-01'
)
SELECT
toString(toYear(date)) AS year,
count() AS obs_count,
round(avg(ret) * 252 * 100, 2) AS ann_return_pct,
round(stddevSamp(ret) * sqrt(252) * 100, 2) AS ann_vol_pct,
round(avg(ret) / stddevSamp(ret) * sqrt(252), 2) AS sharpe_ratio
FROM daily
WHERE date >= '2012-01-01'
AND ret IS NOT NULL
GROUP BY year
ORDER BY yearيغطي كل صف نحو 250 جلسة، أي ما يقارب سنة التداول المعتادة. لم يتغير شيء في المركز خلال أي من هذه الفترات. ففي 2012 بلغت Sharpe السنوية المقاسة 1.06؛ وفي 2025 بلغت 0.88، مع عرض 14 سنوات على الرسم. إن الاحتفاظ بمؤشر واسع لمدة سنة واحدة يغير الرقم المعلن بأكثر مما قد يصنفه معظم القراء ضمن الضوضاء، ولذلك يرث الاختبار الخلفي لاستراتيجية بالطول نفسه هذا القدر على الأقل من التباين. يوضح دليل نسبة Sharpe اصطلاح التحويل إلى أساس سنوي المستخدم في العمود، فيما يشرح كيفية قياس العوائد الشهرية آلية حساب عوائد الفترات.
ما اتساع النطاق حول Sharpe المحسوب بالاختبار التاريخي؟
ينخفض الخطأ المعياري لتقدير Sharpe تقريباً بمعدل يتناسب عكسياً مع الجذر التربيعي لعدد المشاهدات. بدلاً من الاعتماد على هذه الصيغة، قِس التشتت مباشرةً. قسّم عشرين عاماً من جلسات التداول إلى نوافذ غير متداخلة ذات طول ثابت، واحسب Sharpe السنوي داخل كل نافذة، ثم راقب مدى تباعد هذه القيم.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
date,
toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2005-12-01'
AND date < '2026-01-01'
),
indexed AS
(
SELECT
ret,
row_number() OVER (ORDER BY date) AS i
FROM daily
WHERE date >= '2006-01-01'
AND ret IS NOT NULL
),
blocks AS
(
SELECT
w,
intDiv(i, w) AS blk,
count() AS n,
avg(ret) / nullIf(stddevSamp(ret), 0) * sqrt(252) AS sharpe
FROM indexed
CROSS JOIN (SELECT arrayJoin([21, 63, 126, 252, 504]) AS w) AS ws
GROUP BY w, blk
HAVING n = w
)
SELECT
concat(toString(w), ' sessions') AS horizon,
count() AS block_count,
round(quantileDeterministic(0.05)(sharpe, blk), 2) AS sharpe_p05,
round(quantileDeterministic(0.50)(sharpe, blk), 2) AS sharpe_p50,
round(quantileDeterministic(0.95)(sharpe, blk), 2) AS sharpe_p95,
round(quantileDeterministic(0.95)(sharpe, blk)
- quantileDeterministic(0.05)(sharpe, blk), 2) AS sharpe_band_width
FROM blocks
WHERE sharpe IS NOT NULL
GROUP BY w
ORDER BY wعند 21 sessions لكل نافذة، يمتد المئين الخامس إلى المئين الخامس والتسعين لقيم Sharpe المقاسة من -3.37 إلى 6.97، أي بفارق قدره 10.34 نقطة Sharpe عبر 238 نافذة. وعند تمديد كل نافذة إلى 504 sessions، ينخفض الفارق إلى 1.67 نقطة، لكن القياس يستند حينها إلى 8 نافذة فقط. يتحرك عاملان في الوقت نفسه. يصبح التقدير أكثر دقة مع زيادة حجم العينة، بينما يتقلص عدد العينات المستقلة التي يمكن للتاريخ أن يوفرها. وهذا التوتر هو موضوع التحليل بأكمله.
كيفية إنشاء فترة ثقة لاختبار رجعي باستخدام Bootstrap
الإجراء قصير بما يكفي لعرضه كاملاً.
- ابدأ بسلسلة العوائد المحققة للاستراتيجية، بحيث تمثل كل قيمة فترة واحدة، وعددها N.
- اسحب N عائداً عشوائياً من القائمة نفسها مع الإرجاع. قد تظهر بعض القيم مرتين، وقد لا تظهر قيم أخرى إطلاقاً.
- أعد حساب الإحصاء على العينة المعاد سحبها.
- كرر العملية عدة آلاف من المرات، واحتفظ بكل قيمة.
- رتّب القيم المحفوظة، واقرأ المئينين 2.5 و97.5 للحصول على فترة ثقة بنسبة 95 في المئة.
ثبّت مولّد الأرقام العشوائية قبل الخطوة 2، وسجّل قيمة البذرة بجوار فترة الثقة المنشورة. إن Bootstrap هو تقدير بطريقة Monte Carlo؛ لذلك تختلف تشغيلتان من دون بذرة مثبتة في الخانات الأخيرة، ولا يستطيع المراجع الذي لا يمكنه إعادة تشغيل حساب فترة الثقة التحقق منها. وهذا هو المبدأ نفسه الموضح في إعداد اختبار رجعي قابل لإعادة الإنتاج.
يمر كل من متوسط العائد وSharpe بسلاسة عبر الخطوة 2، لأن كلاً منهما يتعامل مع قائمة العوائد بوصفها مجموعة من القيم. أما الحد الأقصى للتراجع فلا يفعل ذلك. إذ يقرأ التراجع المسار بترتيبه الزمني. وتؤدي إعادة سحب تعيد ترتيب العوائد إلى تراجع أسوأ لم ينتج عن أي ترتيب في سلسلة الصفقات الفعلية. يظل من المفيد استخدام Bootstrap له، بشرط وصف الناتج بدقة: إنه توزيع التراجع عبر مسارات تاريخية أُعيد ترتيبها، وليس توقعاً للتراجع التالي. يرد التعريف في الحد الأقصى للتراجع.
لماذا يكون bootstrap القائم على IID خاطئاً لعوائد السوق
تفترض الخطوة الثانية أن كل عائد مستقل وموزع بالتوزيع نفسه، وهو افتراض IID. لكن العوائد اليومية تخالف هذا الافتراض بطريقة تغيّر عرض الفترة. تحمل العوائد الموقعة أثراً ضعيفاً فقط من اليوم السابق. أما أحجامها فتتكتل؛ إذ تتجاور التحركات الكبيرة، وتأتي الأيام الهادئة في سلاسل متتابعة.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
ticker,
date,
toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'MSFT', 'KO', 'XOM', 'JNJ', 'PG')
AND date >= '2015-11-01'
AND date < '2026-01-01'
),
lagged AS
(
SELECT
ticker,
date,
ret,
lagInFrame(ret, 1) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS ret_prev
FROM daily
WHERE ret IS NOT NULL
AND abs(ret) < 0.35
)
SELECT
ticker,
count() AS obs_count,
round(corr(ret, ret_prev), 3) AS return_autocorr,
round(corr(abs(ret), abs(ret_prev)), 3) AS abs_return_autocorr
FROM lagged
WHERE date >= '2016-01-01'
AND ret_prev IS NOT NULL
GROUP BY ticker
ORDER BY abs_return_autocorr DESCبالنسبة إلى SPY، بلغ الارتباط الذاتي عند الإبطاء الأول للعوائد اليومية المطلقة 0.366، مقاساً عبر 2514 جلسة، مقابل -0.133 للعوائد الموقعة في الأيام نفسها. قارن بين العمودين لأي اسم في الرسم البياني. يؤدي خلط ترتيب سلسلة العوائد إلى تدمير هذا التكتل، ويُظهر تشغيل bootstrap وفق IID على هذه البيانات فترة أضيق مما تدعمه العينة. ويسير الخطأ في الاتجاه الأقل فائدة: إذ يمنح الاستراتيجية صورة أفضل من حقيقتها.
الـ moving block bootstrap واختيار طول الكتلة
يكمن الحل في إعادة أخذ عينات من كتل متجاورة بدلاً من العوائد الفردية. اختر طولاً للكتلة L، واسحب عشوائياً كتلًا تتكون كل منها من L عوائد متتالية مع الإرجاع، ثم صِل هذه الكتل واحدة تلو الأخرى حتى يبلغ طول السلسلة الاصطناعية N. تبقى التبعية داخل الكتلة محفوظة بالكامل، إذ تظل العوائد بترتيبها الأصلي. أما نقاط الوصل بين الكتل فهي وحدها مصطنعة.
يوازن طول الكتلة بين خطأين، ولا يتيح أي إعداد تجنب كليهما. تتصرف الكتل القصيرة مثل IID bootstrap وتقلل من تقدير عرض الفترة، وهذا انحياز. أما الكتل الطويلة فتحافظ على قدر أكبر من التبعية، لكنها تترك عدداً أقل من الكتل المتميزة التي يمكن السحب منها. لذلك تكرر كل عينة معادَة أجزاء كبيرة من التاريخ نفسه، وتصبح الفترة نفسها أكثر تقلباً، وهذا تباين. وتربط القواعد الإرشادية المنشورة طول الكتلة بـ N مرفوعاً للقوة ثلث. تعامل معها كنقاط بداية.
ومن أساليب التشخيص الأقل كلفة فحص كيفية تغير التباين مع الأفق الزمني. في ظل الاستقلال، يساوي تباين مجموع عوائد k يوماً k مضروباً في تباين عائد اليوم الواحد، وتكون نسبة الاثنين مساوية لـ 1.
استعلام SQL الدقيق وراء كل رقم
WITH daily AS
(
SELECT
date,
toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2005-12-01'
AND date < '2026-01-01'
),
indexed AS
(
SELECT
ret,
row_number() OVER (ORDER BY date) AS i
FROM daily
WHERE date >= '2006-01-01'
AND ret IS NOT NULL
),
base AS
(
SELECT varSamp(ret) AS var_1d FROM indexed
),
blocks AS
(
SELECT
k,
intDiv(i, k) AS blk,
count() AS n,
sum(ret) AS block_ret
FROM indexed
CROSS JOIN (SELECT arrayJoin([2, 3, 5, 10, 21, 42, 63]) AS k) AS ks
GROUP BY k, blk
HAVING n = k
)
SELECT
concat(toString(k), ' sessions') AS block_length,
count() AS block_count,
round(varSamp(block_ret) / (k * any(var_1d)), 3) AS variance_ratio
FROM blocks
CROSS JOIN base
GROUP BY k
ORDER BY kعند 2 sessions بلغت النسبة 0.844؛ وعند 63 sessions بلغت 0.584، بعد احتسابها على أساس 78 كتلة غير متداخلة. وتعني القيم القريبة من 1 أن المجموع يتدرج بالطريقة التي تتدرج بها السحوبات المستقلة عند ذلك الأفق. أما القيم البعيدة عن 1 فتحدد الآفاق التي لا تزال التبعية تؤثر فيها، وهو النطاق الذي يجب أن يغطيه طول الكتلة. اختر أقصر كتلة تتجاوز هذا النطاق، ثم راقب العدد الذي انخفضت إليه الكتل.
إعادة أخذ العينات ضمن المجموعات والعدد بجانب كل فترة
تحافظ إعادة أخذ العينات ضمن المجموعات، أو حسب نظام التقلب، أو حسب الشهر التقويمي، على الشرط الذي جعل السؤال مهماً. إذا كان الادعاء أن استراتيجية ما تحقق معامل شارب الخاص بها في أنظمة التقلب المرتفع، فإن الفترة المبنية على السحب من أيام التقلب المرتفع وحدها هي التي تختبر هذا الادعاء. لكن كلفة ذلك حسابية. فتقسيم 250 مشاهدة إلى أربع مجموعات يترك نحو 60 مشاهدة في كل مجموعة، وتنتج إعادة أخذ عينات bootstrap من 60 مشاهدة فترةً أعرض بنحو الضعف من نظيرتها المبنية على العينة الكاملة.
لذلك، اعرض عدد المشاهدات في المجموعة بجانب كل فترة، في كل مرة. ويجسّد العمود block_count في اللوحات أعلاه هذه الممارسة بوضوح: فالقراءة عند المئين الخامس المستخرجة من تسع نوافذ تختلف عن قراءة مستخرجة من مئتي نافذة، حتى عندما تُعرض القيمتان بمنزلتين عشريتين.
ما الذي لا يستطيع bootstrap إصلاحه
يقيس bootstrap شيئاً واحداً: ضوضاء المعاينة في إحصاء محسوب من البيانات المتاحة لديك. لكنه لا يجيب عما إذا كان من الممكن تحقيق تلك البيانات أصلاً.
ينتج عن الاختبار التاريخي المتأثر بـ انحياز التطلع إلى المستقبل سلسلة عوائد لم يكن من الممكن تداولها، ثم يعيد bootstrap نطاقاً ضيقاً وواثقاً حول نتيجة وهمية. كما أن بناء مجموعة من مكوّنات المؤشر الحالية يحمّلها انحياز البقاء، ويرث كل نموذج معاد سحبه من تلك المجموعة الانحياز نفسه. وهناك فجوة ثالثة تتمثل في أثر الاختيار: إذا شغّلت 200 نسخة، ثم احتفظت بالأفضل، فإن فترة bootstrap لذلك الإصدار تصف ضوضاء معاينته وحده، وتتجاهل عمليات السحب الـ199 التي استُخدمت لاختياره. النطاقات الصادقة مفيدة، لكنها لا تغني عن بيانات خارج العينة.
ملاحظات على البيانات والمنهج
- العوائد هي عوائد سعرية من الإغلاق إلى الإغلاق، مستخرجة من أشرطة يومية. ولا تشمل توزيعات الأرباح، ما يقلل مستوى كل عائد وكل قيمة Sharpe بنحو عائد التوزيعات. أما التشتت الذي يتناوله هذا المنشور فلا يتأثر تقريباً.
- النوافذ والكتل غير متداخلة، لذلك يستخدم كل إحصاء مقاس شريحة منفصلة من السجل التاريخي. ولو كانت النوافذ متداخلة، لتضخم العدد الظاهري للعينات.
- تستخدم Quantiles مقدّراً حتمياً، لذلك تؤدي إعادة تشغيل اللوحة إلى النسبة المئوية نفسها، لا إلى تقريب جديد.
- تستخدم لوحة الارتباط الذاتي ستة أسماء كبيرة لم يحدث في أي منها split للسهم داخل النافذة، وتستبعد أي يوم يتجاوز فيه التغير 35 في المئة، ما يمنع دخول آثار تعديل الأسعار في الارتباط.
الأسئلة الشائعة
ماذا يخبرك فاصل الثقة بطريقة Bootstrap عن الاختبار الرجعي؟
يعطيك نطاق القيم التي يُحتمل أن يتخذها المقياس إذا أُعيد أخذ عينات من العملية نفسها على مدى العدد نفسه من الفترات. وإذا احتوى فاصل بنسبة 95 في المئة على الصفر، فهذا يعني أن العينة قصيرة جداً بحيث لا تميّز بين الاستراتيجية وغياب أي أفضلية تماماً.
كم عدد عمليات إعادة أخذ العينات بطريقة Bootstrap الكافي؟
بالنسبة إلى فاصل بنسبة 95 في المئة، يكون إجراء بضعة آلاف من عمليات إعادة أخذ العينات مستقراً عادةً، ويُعدّ 10,000 خياراً افتراضياً شائعاً ولا يكلّف كثيراً. أما الكميات الكمية في أطراف التوزيع فتحتاج إلى عدد أكبر؛ فالمئين الأول من 1,000 سحب يُستخلص من نحو عشر قيم.
ما طول الكتلة الذي ينبغي أن يستخدمه Moving Block Bootstrap؟
لا يوجد طول صحيح على نحو شامل. تربطه القواعد التقريبية بحجم العينة مرفوعاً إلى قوة الثلث، ويتمثل الفحص العملي في الأفق الذي يتوقف عنده التباين عن التوسع خطياً، وهو ما تقيسه لوحة نسبة التباين أعلاه. انشر الطول الذي استخدمته إلى جانب الفاصل.
هل يمكنك استخدام Bootstrap لحساب أقصى تراجع؟
نعم، مع مراعاة ترتيب العوائد. يعتمد التراجع على تسلسل العوائد، لذلك فإن إعادة أخذ عينات مبنية على خلطها تعرض تراجعاً لتاريخ أُعيد ترتيبه. ويحافظ Moving Block Bootstrap على التتابعات القصيرة، ولذلك فهو الأداة الأفضل لإحصاءات المسار.
هل يصحح Bootstrap مشكلة الإفراط في الملاءمة؟
لا. فهو يقيس ضوضاء أخذ العينات داخل سلسلة عوائد واحدة. أما انحياز التطلع إلى المستقبل وتأثير الاختيار الناتج عن اختبار نسخ متعددة، فكلاهما يقع خارج نطاق ما يستطيع رصده.
تتضمن كل لوحة هنا استعلام SQL الذي أنشأها. غيّر الرمز أو طول النافذة وشغّله بنفسك على منصة Strasmore الطرفية.