التنبؤ المطابق (Conformal Prediction) لتحديد حجم المراكز الواعي بالمخاطر
كل صيغة لتحديد حجم المركز تحتاج إلى تقدير لعدم اليقين. يحتاج معيار كيلي (Kelly criterion) إلى احتمال الربح ونسبة العائد (انظر معيار كيلي للاستراتيجيات). ويحتاج تحسين المتوسط-التباين (mean-variance) إلى مصفوفة تباين مشترك. وتحتاج القيمة المعرضة للمخاطر (VaR) إلى توزيع للعوائد. كل هذه الطرق تتطلب افتراضات حول العملية المولّدة للبيانات — وهي افتراضات تنتهكها الأسواق المالية بشكل روتيني.
يقدّم التنبؤ المطابق شيئاً مختلفاً: فترات تنبؤ مع ضمانات تغطية على عينة منتهية، دون أي افتراضات توزيعية بارامترية. إذا طلبت تغطية بنسبة 90%، فستحصل على تغطية لا تقل عن 90% — بغض النظر عما إذا كانت العوائد غاوسية (Gaussian) أو ذات ذيول ثقيلة أو ملتوية أو غير متجانسة التباين (heteroskedastic). الشرط الوحيد هو القابلية للتبادل (exchangeability) (أو شروط أضعف، كما سنرى).
تغطي هذه التدوينة النظرية والمتغيرات الرئيسية وتطبيقاً عملياً لتحديد حجم المراكز بلغة Python.
الفكرة الجوهرية: درجات عدم المطابقة (Nonconformity Scores)

يعمل التنبؤ المطابق عبر قياس مدى "غرابة" ملاحظة جديدة بالنسبة إلى البيانات السابقة. ويتم تحديد هذه الغرابة كمياً بواسطة درجة عدم مطابقة (nonconformity score) — أي دالة تقيس مدى ضعف توافق نقطة بيانات مع النمط المُلاحَظ في بقية البيانات.
بالنسبة للانحدار (regression) (التنبؤ بقيمة مستمرة مثل العوائد)، فإن أبسط درجة لعدم المطابقة هي البواقي المطلقة (absolute residual):
حيث هو أي متنبئ نقطي (انحدار خطي، غابة عشوائية، شبكة عصبية — لا يهم) و هي نقطة بيانات.
البصيرة الأساسية: إذا كانت نقاط البيانات قابلة للتبادل، فإن رتبة بين موزعة بانتظام على . هذه حقيقة توافيقية بحتة — لا تتطلب أي افتراضات حول توزيع أو .
من انتظام الرتبة هذا، يمكننا بناء فترات تنبؤ مع تغطية على عينة منتهية.
التنبؤ المطابق المقسّم (Split Conformal Prediction)

التنبؤ المطابق المقسّم (Papadopoulos et al., 2002؛ Lei et al., 2018) هو المتغير الأكثر عملية. الخوارزمية بسيطة:
الخطوة 1. قسّم البيانات إلى مجموعة تدريب ومجموعة معايرة .
الخطوة 2. درّب أي نموذج على .
الخطوة 3. احسب درجات عدم المطابقة على مجموعة المعايرة:
الخطوة 4. لمستوى عدم تغطية مرغوب ، خذ بوصفها الكميّة التجريبية من . وبشكل ملموس، هذه هي البواقي الأصغر رتبةً (و كلما كان ، أي في حالة الصغيرة جداً).
الخطوة 5. فترة التنبؤ لنقطة جديدة هي:
ضمان التغطية
في ظل القابلية للتبادل بين بيانات المعايرة ونقطة الاختبار الجديدة:
هذا ضمان على عينة منتهية — وليس تقريباً مقارباً (asymptotic). إنه يصمد لأي نموذج ، ولأي توزيع للبيانات، ولأي حجم عينة . إذا كان متنبئاً رديئاً، فإن الفترات ستكون ببساطة أوسع. وضمان التغطية يظل قائماً.
هناك أيضاً حد أعلى عندما لا توجد تعادلات (ties) في الدرجات: ، لذا فإن التغطية ليست محافظة بإسراف.
لماذا يهم هذا في التداول
فترات التنبؤ التقليدية، مثلاً من انحدار خطي، تفترض أخطاء غاوسية. ويمكن لفترة غاوسية مُعايَرة على الكتلة الرئيسية من البيانات أن تسيء تقدير الذيول بشدة عندما تكون البواقي الحقيقية ذات ذيول ثقيلة (مثلاً Student- بعدد قليل من درجات الحرية): فالكتلة المركزية أنحف من الغاوسية، لذا فإن فترة غاوسية مطابقة للتباين تُفرط في التغطية قرب المركز بينما تُقصّر في التغطية عند الذيول، وفترة مُلائَمة للذيول تفعل العكس. النقطة ليست رقماً سحرياً واحداً — بل أن التغطية المحققة لفترة بارامترية تعتمد على افتراض توزيعي لم تتحقق منه.
تتجاوز فترات التنبؤ المطابق هذه المشكلة. إنها تتسع تلقائياً عندما يكون النموذج غير متيقن، وتحافظ على التغطية الهامشية (marginal coverage) بغض النظر عن توزيع الخطأ الحقيقي. وبالنسبة للمتداول، يعني هذا:
- إذا حددت حجم المراكز بشكل عكسي تناسبي مع عرض الفترة، فإنك تقلل التعرض تلقائياً عندما يكون النموذج غير متيقن.
- ضمان التغطية يعني أن تقديرات المخاطر الخاصة بك صادقة — إذا قلت "ستقع 90% من العوائد المحققة ضمن هذه الفترة"، فإن هذه العبارة صحيحة إحصائياً (هامشياً، في ظل القابلية للتبادل).
التنبؤ المطابق الكامل و Jackknife+
التنبؤ المطابق المقسّم بسيط لكنه يبدد البيانات: لا يمكن استخدام مجموعة المعايرة للتدريب. وهناك بديلان يعالجان هذا.
التنبؤ المطابق الكامل (Full Conformal Prediction)
يستخدم التنبؤ المطابق الكامل (Vovk et al., 2005) كل البيانات للتدريب والمعايرة معاً. لكل قيمة مرشحة لـ :
- وسّع مجموعة البيانات بـ .
- أعد ملاءمة النموذج على مجموعة البيانات الموسّعة.
- احسب كل درجات عدم المطابقة.
- أدرج في مجموعة التنبؤ إذا لم تكن درجة متطرفة جداً.
مجموعة التنبؤ هي:
حيث هي درجات عدم المطابقة المحسوبة باستخدام مجموعة البيانات الموسّعة.
يوفر التنبؤ المطابق الكامل أضيق الفترات لكنه باهظ حسابياً لمعظم النماذج — إذ يجب عليك إعادة ملاءمة النموذج لكل قيمة مرشحة على شبكة (grid). وبالنسبة للتنبؤ بالعوائد، قد يعني هذا آلاف عمليات إعادة الملاءمة لكل تنبؤ.
Jackknife+ (Barber et al., 2021)
يحقق jackknife+ توازناً. فهو يستخدم بواقي ترك-واحد-خارجاً (leave-one-out, LOO) لكنه يأخذ في الحسبان التباين في النموذج المُلاءَم عبر طيّات LOO.
ليكن هو النموذج المُدرَّب على كل البيانات باستثناء النقطة . عرّف درجة عدم مطابقة LOO بواسطة البواقي المطلقة المفردة:
تُبنى فترة تنبؤ jackknife+ بعدها من تنبؤات LOO عند نقطة الاختبار، مع توسيعها بهذه البواقي:
هنا يشير إلى القيمة الأصغر رتبةً من المجموعة ، ويشير إلى القيمة الأصغر رتبةً . الحد الأدنى يطرح البواقي من كل تنبؤ LOO؛ والحد الأعلى يضيفها. هذا اللاتناظر هو بيت القصيد بالكامل — فطيّ كلا الحدين إلى سيضع الحد الأدنى فوق التنبؤ، وهو خطأ.
يوفر jackknife+ ضمان تغطية لا يقل عن (أضعف قليلاً من ضمان للتنبؤ المطابق المقسّم)، لكنه يستخدم كل البيانات للتدريب والمعايرة معاً. وفي الممارسة العملية، تكون التغطية عادةً قريبة من .
بالنسبة لنماذج التداول المدرَّبة على بيانات محدودة (مثلاً النماذج الخاصة بنظام معين والتي لا تملك سوى بضع مئات من الملاحظات)، غالباً ما يكون jackknife+ الخيار الأفضل — فهو لا يضحّي ببيانات شحيحة من أجل المعايرة. والتكلفة هي من عمليات إعادة ملاءمة النموذج.
المشكلة مع السلاسل الزمنية المالية: عدم القابلية للتبادل

يتطلب الضمان المطابق القياسي القابلية للتبادل: التوزيع المشترك لـ ثابت تحت التباديل (permutations). وبالنسبة للبيانات المستقلة والمتطابقة التوزيع (i.i.d.)، يصمد هذا بداهة.
السلاسل الزمنية المالية ليست قابلة للتبادل. فالعوائد تُظهر:
- تجمّع التقلبات (Volatility clustering): فترات التقلب العالي تتبع فترات التقلب العالي (تأثيرات GARCH).
- الزخم والارتداد إلى المتوسط (Momentum and mean reversion): الارتباط الذاتي في العوائد أو مربعات العوائد.
- تغيرات النظام (Regime changes): التوزيع يتحول عبر الزمن (أسواق صاعدة مقابل أسواق هابطة).
إذا طبّقت التنبؤ المطابق المقسّم بسذاجة على سلسلة زمنية — باستخدام تقسيم معايرة عشوائي — فإنك تنتهك البنية الزمنية. درجات المعايرة من عام 2017 الهادئ لن تعكس عدم اليقين في عام 2020 المتقلب. وضمان التغطية الخاص بك ينهار.
الاستدلال المطابق التكيفي (Adaptive Conformal Inference, ACI)

قدّم Gibbs و Candes (2021, NeurIPS) الاستدلال المطابق التكيفي (Adaptive Conformal Inference, ACI) للتعامل مع انزياح التوزيع والبيانات غير القابلة للتبادل. والفكرة أنيقة: بدلاً من استخدام مستوى تغطية ثابت، يتم تكييف مستوى عدم التغطية المستهدف فورياً (online) بناءً على ما إذا كانت الفترات الأخيرة قد غطّت النتيجة الحقيقية، ثم إعادة اشتقاق الكميّة من توزيع الدرجات في كل خطوة.
خوارزمية ACI
ACI لا يدفع عرض الفترة مباشرةً. بل يحافظ على معامل تكيفي لعدم التغطية ويعيد حساب الكميّة المطابقة منه. في كل خطوة زمنية :
- احسب العتبة المطابقة بوصفها الكميّة التجريبية من مجموعة البواقي الحالية (درجات المعايرة، بالإضافة إلى أي درجات محققة حتى الآن): .
- لاحظ الخصائص ، وأنتج الفترة .
- لاحظ القيمة الحقيقية واحسب مؤشر الخطأ .
- حدّث المستوى (وليس العرض):
حيث هو حجم الخطوة و هو عدم التغطية المستهدف. إذا أخطأت فترة ()، يتقلص ، مما يدفع الكميّة التالية إلى الأعلى ويوسّع الفترة؛ وإذا غطّت، ينمو وتضيق الفترات. والأهم أن هنا بـ وحدات احتمالية — فهو يدفع مستوى ضمن ، وليس العتبة بوحدات العائد الخام — لذا فإن نفس يتصرف بشكل منطقي سواء كانت البواقي بترتيب أم لا.
ضمان التغطية لـ ACI
يوفر ACI ضمان تغطية على المدى الطويل لا يعتمد على نموذج توزيعي:
لأن مقصوص (clipped) إلى ، فإن البسط محدود بـ مضروباً في ثابت، لذا فإن الطرف الأيمن هو وتتقارب تردد عدم التغطية التجريبي إلى . العبارة الدقيقة: يضمن ACI أن تردد عدم التغطية التجريبي على المدى الطويل يتقارب إلى لتسلسلات اعتباطية (بما في ذلك العدائية)، شريطة أن تبقى المستويات المكيَّفة محدودة — وهو ما يفرضه القص (clip). إنه ضمان على تردد التغطية، وليس على إفادة الفترة (informativeness): ففي ظل تسلسل عدائي حقاً، يمكن للفترات أن تنمو إلى عرض غير مُفيد بينما تظل تحقق هدف التغطية.
ACI المضبوط ديناميكياً (DtACI)
نقّح Gibbs و Candes (2024, JMLR) خوارزمية ACI عبر الضبط الديناميكي لحجم الخطوة . فبدلاً من تثبيت ، يحافظون على مجموعة مرشحة ويجمعونها عبر قاعدة تجميع الخبراء (expert-aggregation)، مفضّلين التي تكون تغطيتها الأخيرة أقرب إلى الهدف.
يعالج هذا مشكلة عملية: كبير يتكيف بسرعة مع تغيرات النظام لكنه ينتج عروض فترات متقلبة؛ و صغير مستقر لكنه بطيء في التكيف. يوازن DtACI بين هذين تلقائياً.
لماذا يهم هذا في التداول
تأمل استراتيجية صنع سوق (market-making) تستخدم نموذجاً للتنبؤ بالعوائد. خلال الأسواق الهادئة، تكون الفترات المطابقة ضيقة — فالنموذج واثق، ويمكنك اتخاذ مراكز أكبر. وعندما يقفز التقلب (موسم الأرباح، إعلانات FOMC، الصدمات الجيوسياسية)، يتكيف مستوى ACI وتتسع الفترات خلال بضع خطوات زمنية. ويتقلص حجم مراكزك استجابةً لذلك، دون أي نموذج تقلب صريح أو منطق لكشف النظام.
هذا هو قياس عدم اليقين بوصفه إشارة من الدرجة الأولى، وليس فكرةً لاحقة.
تحديد حجم المراكز بفترات مطابقة

الآن لنربط التنبؤ المطابق بتحديد حجم المراكز بشكل ملموس. المتغير الأساسي هو نصف عرض فترة التنبؤ بالنسبة إلى حالة البواقي المطلقة المتناظرة. مع الفترة المتناظرة ، يكون العرض الكامل . وللحفاظ على اتساق الصيغ والكود، نقيس كل شيء مقابل العرض الكامل في كل مكان.
تحديد الحجم بالعرض العكسي (Inverse-Width Sizing)
أبسط نهج: حدد الحجم بشكل عكسي تناسبي مع عرض الفترة.
حيث هو ثابت قياس مُعايَر وفق ميزانية المخاطر الخاصة بك. عندما يكون النموذج واثقاً (فترة ضيقة)، تتخذ مركزاً أكبر. وعندما يكون غير متيقن (فترة واسعة)، تتخذ مركزاً أصغر.
هذا مماثل لاستهداف التقلب ()، لكن بفارق جوهري: عرض الفترة المطابقة هو مقياس لعدم اليقين خالٍ من الافتراضات التوزيعية، وليس تقديراً بارامترياً للتقلب. فهو يلتقط عدم اليقين التنبؤي في ظل ضمان التغطية، وليس مجرد تباين العوائد.
تحديد الحجم بنسبة الأفضلية (Edge-Ratio Sizing) ومرشّح عدم التداول
يتجاهل تحديد الحجم بالعرض العكسي المحض قوة الإشارة نفسها. والتنقيح الطبيعي هو القياس وفق نسبة الأفضلية (edge ratio) — التنبؤ النقطي بالنسبة إلى عرض الفترة:
هذا نظير مطابق لنسبة الإشارة إلى الضوضاء (signal-to-noise ratio): العائد المتوقع مقسوماً على مقياس لعدم اليقين خالٍ من الافتراضات التوزيعية. ونستخدمه لكل من تحديد الحجم ومرشّح عدم التداول.
المرشّح مبدئي (principled). إذا كانت الفترة تتخطى الصفر،
فإن فترة التنبؤ تشمل عوائد موجبة وسالبة معاً — فالعائد المحقق قد يحمل بشكل معقول الإشارة المعاكسة لتنبؤك. عرّف عتبة أفضلية دنيا وتداول فقط عندما يكون . لاحظ أن المضمون الهندسي لـ " كبير بما يكفي بحيث لم تعد الفترة تتخطى الصفر" هو بالضبط (إذ تتخطى الفترة الصفر عندما يكون ). اختر على المقياس الفعلي لـ عبر الاختبار الرجعي (backtesting)؛ فبالنسبة لبواقي العوائد اليومية يكون عادةً أقل بكثير من ، لذا فإن ضئيلاً قد يسمح بكل الصفقات تقريباً و كبيراً قد لا يسمح بأي منها. عايره وفق بياناتك.
حول "كيلي المطابق" (Conformal Kelly)
من المغري ربط الفترات المطابقة بكسر كيلي . لكن هو بالفعل كسر كامل ومحدود مشتق من احتمال ربح ونسبة عائد ؛ وضربه في نسبة غير محدودة مثل ليس له أي مبرر من نظرية القرار — إذ يمكن أن يتجاوز 1 أو يقلب الإشارة بشكل مستقل عن ، وهو يحتسب الأفضلية مرتين (الأفضلية التي يرمّزها بالفعل). لذلك فإننا لا نقدّم مضاعف "كيلي مطابق".
إذا أردت قيادة كيلي من الفترة، فعليك فعلياً اشتقاق و منها، وهذا يتطلب افتراضاً صريحاً حول التوزيع داخل الفترة (الفترات المطابقة لا تقول شيئاً عن ذلك عمداً — انظر القيود). فمثلاً، في ظل شكل مفترض داخل الفترة، يمكنك تقريب ونسبة عائد من هندسة الفترة — لكن اشهر هذا الافتراض بوضوح، لأنه يعيد إدخال نفس الالتزام البارامتري الذي كان التنبؤ المطابق يهدف إلى تجنبه.
البديل الصادق والخفيف على الافتراضات هو استخدام نسبة الأفضلية بوصفها تقليصاً لكيلي الجزئي (fractional-Kelly shrinkage): ارفع الحجم عندما يكون العائد المتوقع كبيراً بالنسبة إلى الفترة، واخفضه عندما يكون صغيراً، وطبّق هذا فوق سقف صارم — بشكل صريح كإرشاد تجريبي (heuristic)، وليس بوصفه "كسر كيلي".
التطبيق بلغة Python
إليك تطبيقاً عملياً. نعرض كلاً من مسار المقسّم/المُلاءَم مسبقاً (split/prefit) والمسار الزمني (EnbPI)، لأن بيت القصيد في هذه التدوينة هو أن البيانات المالية غير قابلة للتبادل.
الإعداد وتحضير البيانات
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import KFold
from mapie.regression import MapieRegressor, MapieTimeSeriesRegressor
from mapie.subsample import BlockBootstrap
def prepare_features(prices: pd.Series, lookback: int = 20) -> pd.DataFrame:
"""Create features from a price series."""
df = pd.DataFrame()
returns = prices.pct_change()
for lag in range(1, lookback + 1):
df[f"ret_lag_{lag}"] = returns.shift(lag)
for window in [5, 10, 20]:
df[f"ret_mean_{window}"] = returns.rolling(window).mean().shift(1)
df[f"ret_std_{window}"] = returns.rolling(window).std().shift(1)
df[f"ret_skew_{window}"] = returns.rolling(window).skew().shift(1)
df["target"] = returns.shift(-1)
return df.dropna()
التنبؤ المطابق المقسّم باستخدام MAPIE (المُلاءَم مسبقاً Prefit)
بالنسبة للمقسّم/المُلاءَم مسبقاً، يتطلب cv="prefit" استخدام method="base" (مقدّر التقسيم-المطابق الساذج). أما الخيار method="plus" فهو مقدّر CV+/jackknife+ وهو غير متوافق مع cv="prefit" — إذ يحتاج إلى كائن تحقق متقاطع (cross-validation) بدلاً من ذلك. نستخدم التركيبة الصحيحة هنا، ونوحّد تحديد الحجم على العرض الكامل.
def split_conformal_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3, # scaling constant, in width units
max_position: float = 1.0,
min_edge: float = 0.05, # threshold on |pred| / width
) -> pd.DataFrame:
"""
Position sizing using split (prefit) conformal prediction intervals.
Sizing rule (consistent with the prose):
edge_t = |pred_t| / width_t
size_t = clip(k / width_t, 0, max_position) # inverse-width
size_t = 0 if edge_t < min_edge # no-trade filter
"""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.6)
n_cal = int(len(X) * 0.2)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_cal, y_cal = X.iloc[n_train:n_train + n_cal], y.iloc[n_train:n_train + n_cal]
X_test, y_test = X.iloc[n_train + n_cal:], y.iloc[n_train + n_cal:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
base_model.fit(X_train, y_train)
mapie = MapieRegressor(estimator=base_model, cv="prefit", method="base")
mapie.fit(X_cal, y_cal)
y_pred, y_intervals = mapie.predict(X_test, alpha=alpha)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred,
"lower": lower,
"upper": upper,
"width": width,
"edge_ratio": edge_ratio,
"position_size": position_size,
"actual": y_test.values,
}, index=X_test.index)
التنبؤ المطابق الزمني باستخدام EnbPI
لأن العوائد غير قابلة للتبادل، فإن التقسيم العشوائي/المُلاءَم مسبقاً أعلاه ليس سوى خط أساس. يستخدم MapieTimeSeriesRegressor من MAPIE مع method="enbpi" (Xu & Xie, 2021) التمهيد التمهيدي بالكتل (block bootstrap) وتحديث البواقي المصمم للاعتمادية الزمنية. هذه هي الأداة التي تطابق حجة التدوينة نفسها.
def enbpi_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3,
max_position: float = 1.0,
min_edge: float = 0.05,
) -> pd.DataFrame:
"""Position sizing with EnbPI (block-bootstrap, time-series conformal)."""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.7)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_test, y_test = X.iloc[n_train:], y.iloc[n_train:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
cv = BlockBootstrap(n_resamplings=30, length=20, overlapping=False, random_state=42)
mapie_ts = MapieTimeSeriesRegressor(base_model, method="enbpi", cv=cv, agg_function="mean")
mapie_ts.fit(X_train, y_train)
y_pred, y_intervals = mapie_ts.predict(X_test, alpha=alpha, ensemble=True)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": position_size, "actual": y_test.values,
}, index=X_test.index)
الاستدلال المطابق التكيفي (الفوري Online)
للتداول الحي، ننفّذ ACI الحقيقي من الصفر: نحافظ على مستوى عدم التغطية ، ونحدّثه جمعياً (additively)، ونعيد اشتقاق الكميّة من مجموعة البواقي في كل خطوة. تفصيلتان على العينة المنتهية تهمّان:
- استخدم الإحصائية الرتبية (order statistic)، وليس كميّة مُستكملة بالاستيفاء (interpolated). فدالة
np.quantileتستوفي افتراضياً، مما قد يقع أسفل الرتبة المطلوبة بقليل ويُقصّر في التغطية؛ مرّرmethod="higher"(بشكل مكافئ"inverted_cdf"). - عندما تتجاوز الرتبة المطلوبة ( صغير، تغطية مستهدفة عالية)، تكون العتبة الصحيحة (الفترة = الخط بأكمله)، وليس قصّاً إلى أكبر بواقي. فالقص يكسر بصمت ضمان .
class AdaptiveConformalSizer:
"""
Online position sizing with Adaptive Conformal Inference (Gibbs & Candes,
2021). Updates the miscoverage LEVEL alpha_t and re-derives the quantile
from the residual set each step -- gamma is in probability units.
"""
def __init__(self, base_model, alpha=0.1, gamma=0.02,
max_position=1.0, min_edge=0.05, k=1e-3):
self.base_model = base_model
self.alpha_target = alpha # target miscoverage
self.alpha_t = alpha # adaptive miscoverage level
self.gamma = gamma # step size, in [0, 1] units
self.max_position = max_position
self.min_edge = min_edge
self.k = k
self.residuals = []
self.q_hat = np.inf
self.coverage_history = []
@staticmethod
def _conformal_quantile(residuals, alpha_t):
"""(1 - alpha_t) conformal quantile via the order statistic."""
n = len(residuals)
if n == 0:
return np.inf
rank = int(np.ceil((1.0 - alpha_t) * (n + 1)))
if rank > n: # required order statistic does not exist
return np.inf # -> interval is the whole line
level = rank / n
return float(np.quantile(residuals, min(level, 1.0), method="higher"))
def calibrate(self, X_cal, y_cal):
preds = self.base_model.predict(X_cal)
self.residuals = list(np.abs(np.asarray(y_cal) - preds))
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
def predict_and_size(self, X_t) -> dict:
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
lower, upper = mu_hat - self.q_hat, mu_hat + self.q_hat
width = upper - lower # = 2 * q_hat
edge_ratio = abs(mu_hat) / width if np.isfinite(width) and width > 0 else 0.0
if edge_ratio < self.min_edge:
size = 0.0
else:
size = min(self.k / width, self.max_position) if width > 0 else 0.0
return {
"prediction": mu_hat, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": size * np.sign(mu_hat),
"alpha_t": self.alpha_t, "q_hat": self.q_hat,
}
def update(self, X_t, y_t: float):
"""ACI update: adapt the LEVEL, then re-derive the quantile."""
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
residual = abs(y_t - mu_hat)
covered = int(residual <= self.q_hat)
err_t = 1 - covered
self.alpha_t = float(np.clip(
self.alpha_t + self.gamma * (self.alpha_target - err_t), 0.0, 1.0
))
self.residuals.append(residual)
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
self.coverage_history.append(covered)
@property
def running_coverage(self) -> float:
if not self.coverage_history:
return float("nan")
return float(np.mean(self.coverage_history))
تجميع كل ذلك: حلقة الاختبار الرجعي
def backtest_aci_sizing(prices: pd.Series, alpha=0.1, gamma=0.02) -> pd.DataFrame:
"""Backtest position sizing with Adaptive Conformal Inference."""
df = prepare_features(prices)
X = df.drop(columns=["target"]).values
y = df["target"].values
index = df.index
n_train = int(len(X) * 0.5)
n_cal = int(len(X) * 0.2)
X_train, y_train = X[:n_train], y[:n_train]
X_cal, y_cal = X[n_train:n_train + n_cal], y[n_train:n_train + n_cal]
X_test, y_test = X[n_train + n_cal:], y[n_train + n_cal:]
test_index = index[n_train + n_cal:]
model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
model.fit(X_train, y_train)
sizer = AdaptiveConformalSizer(base_model=model, alpha=alpha, gamma=gamma)
sizer.calibrate(X_cal, y_cal)
records = []
for i in range(len(X_test)):
result = sizer.predict_and_size(X_test[i])
result["actual"] = y_test[i]
result["pnl"] = result["position_size"] * y_test[i]
records.append(result)
sizer.update(X_test[i], y_test[i]) # online residual + level update
results = pd.DataFrame(records, index=test_index)
results["cumulative_pnl"] = results["pnl"].cumsum()
results["running_coverage"] = (
((results["lower"] <= results["actual"]) &
(results["actual"] <= results["upper"])).expanding().mean()
)
return results
تقييم النتائج
def evaluate(results: pd.DataFrame, alpha: float = 0.1):
"""Print evaluation metrics for conformal position sizing."""
covered = ((results["actual"] >= results["lower"]) &
(results["actual"] <= results["upper"]))
print(f"Target coverage: {1 - alpha:.1%}")
print(f"Empirical coverage: {covered.mean():.1%}")
print(f"Mean interval width: {results['width'].mean():.6f}")
print(f"Median position size: {results['position_size'].abs().median():.4f}")
print(f"Fraction no-trade: {(results['position_size'] == 0).mean():.1%}")
print(f"Total PnL (bps): {results['pnl'].sum() * 10000:.1f}")
sd = results['pnl'].std()
sharpe = results['pnl'].mean() / sd * np.sqrt(252) if sd > 0 else float('nan')
print(f"Sharpe (annualized): {sharpe:.2f}")
اعتبارات عملية
اختيار درجة عدم المطابقة
البواقي المطلقة هي الخيار الافتراضي، لكنها تفترض أن فترة التنبؤ ينبغي أن تكون متناظرة حول التنبؤ النقطي. وبالنسبة للعوائد المالية، غالباً ما تكون الفترات اللامتناظرة أكثر منطقية:
- انحدار الكميّات المطابق (Conformalized Quantile Regression, CQR): لائم منحدرات الكميّات عند المستويين و ، ثم طبّق المطابقة (Romano et al., 2019). تتكيف الفترات في شكلها مع التوزيع المحلي — أوسع في الجانب الهابط أثناء التراجعات، وأوسع في الجانب الصاعد أثناء الارتفاعات. (مع CQR لم تعد الفترة متناظرة، لذا فإن هو العرض الحقيقي أعلى-ناقص-أدنى — استمر في استخدام بوصفه المقام في كل مكان.)
- الدرجات المُطبَّعة (Normalized scores): ، حيث هو تقدير محلي للتقلب. ينتج هذا فترات تكيفية شرطياً — أضيق أثناء أنظمة التقلب المنخفض، وأوسع أثناء أنظمة التقلب العالي — مع الحفاظ على التغطية الهامشية.
حجم مجموعة المعايرة
يصمد ضمان تغطية التنبؤ المطابق المقسّم لأي حجم لمجموعة المعايرة ، لكن عرض الفترة يتناقص كلما زاد . بالنسبة لـ صغير جداً قد لا توجد الإحصائية الرتبية المطلوبة، وفي هذه الحالة تكون العتبة الصادقة (فترة غير مفيدة لكن صحيحة) — أما القص إلى أكبر بواقي فيُبطل الضمان بصمت. للتداول العملي:
- نقطة معايرة تعطي فترات ضيقة بشكل معقول.
- مفضّل لتقدير كميّة مستقر.
- مع ACI، تكون مجموعة المعايرة للتهيئة فقط؛ وتحديثات المستوى الفورية تتولى الباقي.
تواتر إعادة التدريب
يمكن للنموذج الأساسي أن يتقادم. وهناك نهجان:
- أعد التدريب دورياً (مثلاً شهرياً) وأعد معايرة الكميّة المطابقة.
- استخدم ACI ودع المستوى التكيفي يعوّض عن تقادم النموذج — إذ تتسع الفترات تلقائياً كلما نمت بواقي النموذج.
الخيار 2 أبسط وفعال بشكل مفاجئ. تعمل الطبقة المطابقة كشبكة أمان: حتى إذا تدهور النموذج، فإن تردد تغطية ACI على المدى الطويل لا يزال يتقارب إلى الهدف.
تكاليف المعاملات
تتفاعل الفترات المطابقة مع تكاليف المعاملات بطريقة مفيدة. عندما تكون الفترات واسعة (عدم يقين عالٍ)، تكون المراكز صغيرة، فيكون معدل دوران الصفقات منخفضاً. وعندما تضيق الفترات (النموذج واثق)، تنمو المراكز — لكن النموذج أرجح أن يكون محقاً، لذا فإن معدل الدوران يستحق الدفع مقابله.
يمكنك أيضاً دمج تكاليف المعاملات مباشرةً في مرشّح عدم التداول:
يضمن هذا أنك تتداول فقط عندما تتجاوز الأفضلية الصافية المتوقعة عتبةً مقيسةً بالعرض المطابق — باستخدام نفس مقام كما في كل مكان.
المقارنة مع الطرق التقليدية
| الخاصية | فترة الثقة الغاوسية (Gaussian CI) | فترة الثقة بالتمهيد (Bootstrap CI) | فترة التنبؤ المطابق (Conformal PI) |
|---|---|---|---|
| الافتراض التوزيعي | أخطاء طبيعية (Normal) | i.i.d. + مقاربة | لا شيء (القابلية للتبادل) |
| ضمان على عينة منتهية | لا | لا | نعم |
| يعمل مع أي نموذج | لا | نعم | نعم |
| يتكيف مع عدم تجانس التباين | لا | جزئياً | مع CQR / الدرجات المُطبَّعة |
| يتعامل مع انزياح التوزيع | لا | لا | متغير ACI / EnbPI |
| التكلفة الحسابية | منخفضة | متوسطة | المقسّم: منخفضة؛ jackknife+: إعادة ملاءمة؛ الكامل: باهظ |
التمهيد (bootstrap) "مقارب" فقط في ضمانه؛ فهو لا يزال يفترض بيانات i.i.d./قابلة للتبادل وانسيابية (smoothness)، لذا فإن خانة افتراضه التوزيعي ليست "خالية من الافتراض". كما أن عمود "المطابق" الواحد يخفي تكاليف مختلفة جداً عبر المتغيرات، وهو ما يفصّله صف التكلفة الآن.
القيود
التنبؤ المطابق ليس سحراً. القيود الرئيسية للتداول:
-
تغطية هامشية، وليست شرطية. الضمان هو أن هامشياً — أي بالمتوسط على العشوائية في كل من و . أما لقيمة شرط محددة ، فقد تكون التغطية المحلية أعلى أو أدنى من . ويعالج انحدار الكميّات المطابق هذا جزئياً.
-
القابلية للتبادل متطلب حقيقي. بالنسبة للتنبؤ المطابق المقسّم، يجب أن تكون بيانات المعايرة ونقطة الاختبار قابلة للتبادل. والبيانات المالية ليست كذلك. ويخفف ACI و EnbPI هذا إلى ضمان على المدى الطويل، لكن التغطية على المدى القصير قد تنحرف.
-
عرض الفترة ليس كثافة احتمالية. تخبرك الفترة المطابقة بالمكان الذي سيقع فيه باحتمال ، لكنها لا تقول شيئاً عن التوزيع داخل الفترة. فهي ليست بديلاً عن توزيع تنبؤي كامل — وهذا بالضبط هو السبب في أن اشتقاق و لكيلي منها يتطلب افتراضاً إضافياً صريحاً.
-
القمامة تدخل، فيخرج الأوسع. ينتج نموذج أساسي رديء فترات واسعة. يضمن التنبؤ المطابق التغطية لكن ليس الفائدة. فإذا لم يكن لنموذجك أي قدرة تنبؤية، فإن الفترات ستكون واسعة لدرجة أن محدد حجم المراكز لن يتداول أبداً.
الخلاصة
يوفر التنبؤ المطابق إطاراً صارماً وخالياً من الافتراضات التوزيعية لقياس عدم اليقين، وهو إطار يندمج طبيعياً في تحديد حجم المراكز:
- التنبؤ المطابق المقسّم للمعايرة الثابتة غير المتصلة (offline) مع تغطية على عينة منتهية.
- Jackknife+ عندما تكون بيانات المعايرة شحيحة وتريد استخدام كل الملاحظات (بتكلفة من عمليات إعادة الملاءمة).
- الاستدلال المطابق التكيفي / EnbPI للتداول الفوري مع الأسواق غير الثابتة (non-stationary).
- تحديد حجم المراكز عبر العرض العكسي للفترة ومرشّح عدم التداول بنسبة الأفضلية — وإذا انتقلت إلى كيلي، فبعد اشتقاق و بصدق فقط، وليس عبر مضاعف غير مبرر.
الميزة الأساسية على البدائل البارامترية: لا تحتاج أبداً إلى تحديد أو التحقق من افتراض توزيعي بارامتري. الفترات صادقة بحكم بنائها (هامشياً، في ظل القابلية للتبادل). وبالنسبة للمتداول المنهجي، يعني هذا مصدراً أقل لمخاطر النموذج — وفي عمل تكون فيه مخاطر النموذج وجودية، فإن لذلك أهميته.
المراجع:
- Papadopoulos, H., Proedrou, K., Vovk, V., Gammerman, A. (2002). Inductive confidence machines for regression. ECML.
- Vovk, V., Gammerman, A., Shafer, G. (2005). Algorithmic Learning in a Random World. Springer.
- Lei, J., G'Sell, M., Rinaldo, A., Tibshirani, R.J., Wasserman, L. (2018). Distribution-free predictive inference for regression. JASA.
- Xu, C., Xie, Y. (2021). Conformal prediction interval for dynamic time-series (EnbPI). ICML.
- Barber, R.F., Candes, E.J., Ramdas, A., Tibshirani, R.J. (2021). Predictive inference with the jackknife+. Annals of Statistics.
- Gibbs, I., Candes, E.J. (2021). Adaptive conformal inference under distribution shift. NeurIPS.
- Gibbs, I., Candes, E.J. (2024). Conformal inference for online prediction with arbitrary distribution shifts. JMLR.
- Romano, Y., Patterson, E., Candes, E.J. (2019). Conformalized quantile regression. NeurIPS.
- Cordier, T. et al. (2022). MAPIE: an open-source library for distribution-free uncertainty quantification. arXiv:2207.12274.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.