← العودة إلى قائمة المقالات
May 25, 2026
5 دقائق للقراءة

داخل خوارزميتنا الخاصة: HRP + طويل/قصير + CVaR مع Hull-White

داخل خوارزميتنا الخاصة: HRP + طويل/قصير + CVaR مع Hull-White
#تحسين المحفظة
#HRP
#تكافؤ المخاطر الهرمي
#CVaR
#Hull-White
#EWMA
#طويل/قصير
#إدارة المخاطر
#Rust
#التمويل الكمي

في نظرتنا العامة «12 خوارزمية لتحسين المحفظة، مقارنة» قمنا بمقارنة اثنتي عشرة طريقة لتخصيص الأصول جنبًا إلى جنب. إحدى عشرة منها هي كلاسيكيات الكتب المدرسية. أما الثانية عشرة، Pipeline، فهي ملكنا — وقد حصلت على نقطة واحدة فقط في ذلك المنشور. هذه المقالة هي الغوص العميق: ما بداخلها، من أين تأتي كل صيغة، وكيف تتحول المواصفات إلى Rust.

لا تخترع Pipeline طريقة جديدة لحساب الأوزان. بل تأخذ الوصفة الأكثر قوة المعروفة — Hierarchical Risk Parity (HRP) — وتغلفها بطبقتين يحتاجهما حساب التداول الحي بالفعل ولكن HRP العادية تفتقر إليهما: الاتجاه (طويل/قصير من إشارات الاستراتيجية) وميزانية مخاطر صارمة (CVaR معدلة لنظام التقلبات الحالي). وهذا يعطي أربع مراحل.

المراحل الأربع

prices  I  log returns  II  HRP weights  III  long/short  IV  CVaR budget\text{prices} \;\xrightarrow{\text{I}}\; \text{log returns} \;\xrightarrow{\text{II}}\; \text{HRP weights} \;\xrightarrow{\text{III}}\; \text{long/short} \;\xrightarrow{\text{IV}}\; \text{CVaR budget}
  • I — عوائد لوغاريتمية لكل أصل.
  • II — أوزان أساسية من HRP.
  • III — تقسيم طويل/قصير من إشارات الوكيل، مع تحديد حصص المخاطر حسب الثقة.
  • IV — تصحيح CVaR مع تقلبات Hull-White؛ تذهب المخاطر الفائضة إلى النقد.

دعنا نتناولها بالترتيب.

المرحلة الأولى. العوائد اللوغاريتمية

كل شيء يبدأ بالانتقال من الأسعار إلى العوائد اللوغاريتمية:

ri,t=ln ⁣(Si,tSi,t1)r_{i,t} = \ln\!\left(\frac{S_{i,t}}{S_{i,t-1}}\right)

حيث ii هو الأصل و tt هي الخطوة الزمنية. تتراكم العوائد اللوغاريتمية بمرور الوقت وهي أكثر تماثلًا من التغيرات المئوية العادية — المدخل القياسي لأي حسابات تباين مشترك.

المرحلة الثانية. HRP كأساس

HRP، التي اقترحها ماركوس لوبيز دي برادو في عام 2016، تتجنب المشكلة المركزية في تحسين متوسط التباين — قلب مصفوفة التباين المشترك سيئة التكييف. إنها لا تقلبها على الإطلاق. بدلاً من ذلك، تعمل مع هيكل الارتباطات.

التباين المشترك والارتباط

من العوائد نبني مصفوفة التباين المشترك Σ\Sigma ونقوم بتطبيعها إلى مصفوفة الارتباط CC:

Σi,j=Cov(ri,rj),Ci,j=ρi,j=Cov(ri,rj)σiσj\Sigma_{i,j} = \mathrm{Cov}(r_i, r_j), \qquad C_{i,j} = \rho_{i,j} = \frac{\mathrm{Cov}(r_i, r_j)}{\sigma_i \sigma_j}

مصفوفة المسافة

نحول الارتباط إلى مقياس مسافة، بحيث تكون الأصول المرتبطة بقوة "قريبة من بعضها البعض":

di,j=1ρi,j2d_{i,j} = \sqrt{\frac{1 - \rho_{i,j}}{2}}

كلما اقتربت ρi,j\rho_{i,j} من 1، كلما اقتربت di,jd_{i,j} من 0 — وكلما زاد احتمال أن تشترك الأصول في مجموعة واحدة.

مخطط الشجرة وترتيب الأوراق

من مصفوفة المسافة نبني تسلسلًا هرميًا للمجموعات عبر الربط المتوسط ونقرأ ترتيب الأوراق π=(π1,,πN)\pi = (\pi_1, \ldots, \pi_N) — وهو تبديل للأصول التي تجلس فيها الأصول المتشابهة متجاورة.

خطوة اختيارية: يمكن اختيار العدد الأمثل للمجموعات بواسطة معامل الصورة الظلية si=biaimax(bi,ai)s_i = \dfrac{b_i - a_i}{\max(b_i, a_i)}، حيث aia_i هي متوسط المسافة داخل المجموعة و bib_i هي متوسط المسافة إلى أقرب مجموعة مجاورة. لا تحتاج التمريرة الأساسية إليها — التقسيم الثنائي المتكرر يحترم التسلسل الهرمي بالفعل.

شبه القطرية

نقوم بتبديل صفوف وأعمدة Σ\Sigma بواسطة π\pi، وجمع القيم الكبيرة على طول القطر:

Σi,jq=Σπi,πj\Sigma^{q}_{i,j} = \Sigma_{\pi_i, \pi_j}

التقسيم الثنائي المتكرر

ثم يعمل التكرار من الأعلى إلى الأسفل. في كل خطوة، يتم تقسيم المجموعة إلى نصفين LL و RR، ويتم تخصيص رأس المال بين النصفين بنسبة عكسية لتبايناتهما:

wL=1/σL21σL2+1σR2,wR=1wLw_L = \frac{1/\sigma_L^2}{\dfrac{1}{\sigma_L^2} + \dfrac{1}{\sigma_R^2}}, \qquad w_R = 1 - w_L

يتم حساب تباين المجموعة على كتلة التباين المشترك الفرعية الخاصة بها كـ σC2=1m2i,jCΣi,jq\sigma_C^2 = \tfrac{1}{m^2}\sum_{i,j \in C}\Sigma^{q}_{i,j}. يستمر النزول حتى تحتوي كل عقدة على أصل واحد. الأوزان طويلة فقط، غير سالبة، ومجموعها 1.0.

في تطبيقنا، هذه هي الدالة hrp_from_cov(cov) -> Vec<f64>: الارتباط → المسافة → الربط المتوسط → ترتيب الأوراق → شبه القطرية → التقسيم الثنائي المتكرر. تستدعيها Pipeline كأساس لها — وهي أيضًا الدالة العامة optimize() لحالة عدم وجود إشارات.

المرحلة الثالثة. تراكب الطويل/القصير

HRP العادية هي محفظة "شراء فقط". لكن الاستراتيجية غالبًا ما تقول ليس فقط كم بل أي اتجاه. تأخذ المرحلة الثالثة إشارات لكل أصل (Long/Short) من الوكيل وتبني محفظتين فرعيتين.

  1. يتم تقسيم الأصول إلى سلال طويلة وقصيرة حسب الإشارة.
  2. داخل كل سلة، يتم حساب الأوزان بنفس HRP (على الكتلة الفرعية للتباين المشترك لتلك الأصول)، ومجموعها 1 لكل سلة.
  3. إذا أصدر الوكيل أيضًا ثقة pip_i، يتم تحديد حصص المخاطر بين الجانبين بواسطة الثقة الإجمالية:

ξL=iLpi,ξS=iSpi,λL=ξLξL+ξS,λS=ξSξL+ξS\xi_L = \sum_{i \in L} p_i, \quad \xi_S = \sum_{i \in S} p_i, \qquad \lambda_L = \frac{\xi_L}{\xi_L + \xi_S}, \quad \lambda_S = \frac{\xi_S}{\xi_L + \xi_S}

بدون ثقة، تعود الحصص إلى عدد الأصول في كل سلة. الوزن النهائي الموقع هو wi=λLwiHRPw_i = \lambda_L \cdot w_i^{\text{HRP}} للمراكز الطويلة و wi=λSwiHRPw_i = -\lambda_S \cdot w_i^{\text{HRP}} للمراكز القصيرة، وبعد ذلك يتم تطبيع التعرض الإجمالي بالكامل إلى 1.

ملاحظة صادقة حول الكود. تحمل المواصفات الأصلية عوامل تصحيح αL=λL/σL\alpha_L = \sqrt{\lambda_L}/\sigma_L, αS=λS/σS\alpha_S = \sqrt{\lambda_S}/\sigma_S — لكنها أيضًا تشير إليها بـ "هل نحتاج هذه الخطوة حتى؟". لا يطبق التنفيذ هذه العوامل: يتم دمج الجانبين مباشرة بواسطة حصص المخاطر λ\lambda، مما يحافظ على التعرض الإجمالي بالضبط عند 1 ولا يخلق أي رافعة مالية خفية. هذا تبسيط متعمد للمواصفات، وليس إغفالًا.

المرحلة الرابعة. CVaR مع تعديل Hull-White

HRP توازن المخاطر هيكليًا، لكنها لا تعرف شيئًا عن المستوى المطلق للمخاطر من الناحية النقدية. تضع المرحلة النهائية سقفًا صارمًا لمخاطر الذيل — وتجعلها حساسة لتغير نظام السوق.

عائد المحفظة وتقلبات EWMA

أولاً، نقوم بدمج الأوزان في عائد المحفظة ونقدر التقلبات الشرطية باستخدام EWMA:

rp,t=i=1nwiri,t,σp,t2=λσp,t12+(1λ)rp,t12r_{p,t} = \sum_{i=1}^{n} w_i\, r_{i,t}, \qquad \sigma_{p,t}^2 = \lambda\, \sigma_{p,t-1}^2 + (1 - \lambda)\, r_{p,t-1}^2

مع λ=0.94\lambda = 0.94 (قيمة RiskMetrics الكلاسيكية). تعطي EWMA تقلبات "اليوم" بدلاً من متوسطها على مدار التاريخ كله.

إعادة قياس Hull-White

الفكرة الرئيسية: لا يمكن أخذ العوائد الماضية كما هي — فقد حدثت تحت تقلبات مختلفة. تعيد طريقة Hull-White قياس كل عائد سابق إلى المستوى الحالي:

r~p,s=σp,t+1σp,srp,s,s=tN+1,,t\widetilde{r}_{p,s} = \frac{\sigma_{p,t+1}}{\sigma_{p,s}}\, r_{p,s}, \qquad s = t - N + 1, \ldots, t

يتم "تمديد" شهر هادئ، و"ضغط" شهر مضطرب، ويتم جلب التوزيع إلى النظام الحالي.

VaR و CVaR

على التوزيع المعاد قياسه، نأخذ كمية الخسارة ومتوسط الخسارة في الذيل:

VaRαHW=q1α(r~p)VaR_\alpha^{HW} = -q_{1-\alpha}(\widetilde{r}_p)

CVaRαHW=E ⁣[r~pr~pq1α(r~p)]CVaR_\alpha^{HW} = -\mathbb{E}\!\left[\widetilde{r}_p \mid \widetilde{r}_p \le q_{1-\alpha}(\widetilde{r}_p)\right]

يجيب CVaR (المعروف أيضًا باسم Expected Shortfall) ليس على سؤال "ما مدى سوء اليوم السيئ النموذجي" بل على سؤال "ما مدى سوءه في المتوسط عبر الأسوأ α\alpha بالمائة" — لذا فهو يرى سمك الذيل، وليس فقط حافته.

ميزانية المخاطر والنقد

إذا تجاوز CVaR العتبة المقبولة، يتم تقليص كل مركز محفوف بالمخاطر بعامل واحد، وينتقل رأس المال المحرر إلى النقد:

winew=γwi,γ=CVaRmaxCVaRαHW,wcash=1i=1nwineww_i^{new} = \gamma\, w_i, \quad \gamma = \frac{CVaR_{\max}}{CVaR_\alpha^{HW}}, \qquad w_{cash} = 1 - \sum_{i=1}^{n} |w_i^{new}|

لذا فإن المحفظة تقلل من مخاطرها عندما تنمو مخاطر الذيل وتعود إلى السوق عندما تهدأ.

من المواصفات إلى الكود

تعيش الخوارزمية بأكملها في صندوق Rust واحد، portfolio-pipeline، وتلتزم بالعقد الموحد لمساحة العمل:

pub fn optimize(prices: &[Vec<f64>]) -> Vec<f64>

هذا هو إسقاط المراكز الطويلة فقط (المراحل I، II، IV بدون إشارات) — نفس واجهة prices -> weights تمامًا مثل الخوارزميات الإحدى عشرة الأخرى، لذا فإن Pipeline هي بديل مباشر لأي منها. الإصدار الكامل مع كل مرحلة هو دالة منفصلة:

pub fn run(
    prices: &[Vec<f64>],
    signals: Option<&[Side]>,      // Long / Short per asset
    confidence: Option<&[f64]>,    // agent confidence → risk shares λ
    cfg: &PipelineConfig,          // CVaR / Hull-White parameters
) -> PipelineResult                // signed weights + cash + cvar + σ

القيم الافتراضية للتراكب: ذيل cvar_alpha = 0.05، ميزانية cvar_max = 0.05، EWMA ewma_lambda = 0.94، نافذة Hull-White hw_window = 0 (كل التاريخ). لا يحتوي التنفيذ على تبعيات خارجية وهو دفاعي عمدًا: في التواريخ القصيرة (أقل من 4 نقاط سعرية) يعيد أوزانًا متساوية، ولا يبدأ تراكب CVaR إلا عند ≥8 ملاحظات عائد — وإلا فلا يوجد شيء لتقدير الذيل منه.

لماذا Rust: قاعدة كود حتمية واحدة لكل من الاختبار الخلفي والإنتاج، بدون "Python في البحث، شيء آخر في الإنتاج"، وسريعة بما يكفي لتشغيل جميع الخوارزميات الاثنتي عشرة في طلب واحد عبر الواجهة الخلفية للمقارنة.

ما تكلفته من الوقت

ما مدى سرعة "سريعة بما يكفي"؟ لقد سحبنا جوهر HRP (العوائد اللوغاريتمية ← التباين المشترك ← الربط المتوسط ← شبه القطرية ← الأوزان المتكررة) إلى معيار مستقل وقمنا بتشغيل نفس العمليات الحسابية بالضبط عبر سبع لغات — C و C++ و Rust و Zig و Python و Node.js و Bun — في ظل ظروف متطابقة: Apple Silicon، خيط واحد، 365 ملاحظة يومية لكل أصل، أسعار اصطناعية، أعداد أصول NN من 10 إلى 10,000.

كلمة عن التعقيد، لأنه يحدد الشكل بأكمله. النسخة المدرسية من الربط المتوسط تعيد مسح مصفوفة المسافة الكاملة للعثور على أقرب زوج في كل عملية دمج — وهذا O(N3)O(N^3) ويصبح عنق الزجاجة عند بضعة آلاف من الأصول. يستخدم المعيار بدلاً من ذلك خوارزمية سلسلة أقرب الجيران بتعقيد O(N2)O(N^2) (Müllner 2011) — نفس الخوارزمية التي تقف خلف linkage(method='average') في SciPy. مع وجودها، لم يعد التجميع هو المرحلة المهيمنة: عند N=2000N = 2000 يستغرق ~15 مللي ثانية من تمريرة تبلغ ~0.5 ثانية. أصبحت التكلفة الآن مهيمنة بواسطة مصفوفة التباين المشترك، O(N2T)O(N^2 \cdot T) — وهي المرحلة الوحيدة التي لا يمكن لأي طريقة على نمط HRP تجنبها.

ما تظهره التشغيلات (الجداول الكاملة لكل لغة وسكربت إعادة إنتاج بأمر واحد موجودة في مستودع المشروع):

  • في المحافظ الواقعية، إنه مجاني. سلة العملات المشفرة تتكون من عشرات الأصول، ونادرًا ما تزيد عن مائة. عند N100N \le 100، تمريرة HRP كاملة تستغرق مللي ثانية بخانة واحدة حتى في Node وميكروثانية في Rust/C. إعادة حساب الأوزان في كل تكة ليست مشكلة.
  • Rust ضمن ~1.0–1.3× من C — نفس ترتيب الحجم، كلاهما مجمع، ومتعادلان فعليًا عندما يصل NN إلى الآلاف. C أسرع قليلاً في العمليات الحسابية الخام، لكن Rust توفر نفس القدرة على التنبؤ بدون جامع قمامة ولا سلوك غير معرف.
  • إنه يتوسع إلى آلاف الأصول. مع الربط O(N2)O(N^2)، تستغرق التمريرة الكاملة ~0.5 ثانية عند N=2000N = 2000 وبضع ثوانٍ عند N=5000N = 5000 في اللغات المجمعة؛ حتى Node المُفسَّرة تتجاوز N=2000N = 2000 في أقل من ثانيتين. ما يحدد السقف الآن هو مرحلة التباين المشترك، وليس التجميع.

الخلاصة العملية: في أحجام محافظنا، اختيار Rust لا يتعلق بـ "التغلب على C" (C أسرع قليلاً هنا) — بل يتعلق بقاعدة كود حتمية واحدة للبحث والإنتاج، بدون توقفات جامع القمامة وسنوات من مساحة الأداء. المعيار الكامل بسبع لغات، مع النتائج وسكربت إعادة الإنتاج، مفتوح في مستودع المشروع.

أين تقع Pipeline بين الاثنتي عشرة

في مقارنتنا على سلة واحدة (معدلة عمدًا)، تصرفت Pipeline مثل HRP — لأنه من خلال نقطة الدخول optimize() الطويلة فقط، فإنها هي HRP مع تراكب CVaR. لا تعمل آليتها الاتجاهية إلا عندما تزودها بإشارات استراتيجية. هذا هو الهدف كله: Pipeline ليست "مُحسِّنًا آخر لاختبار الأوزان الخلفي" بل هي طبقة التنفيذ بين إشارات الاستراتيجية والأوامر الحقيقية — فهي تأخذ مكالمات الشراء/البيع الخاصة بك، وتوزع رأس المال بواسطة HRP داخل كل جانب، وتوازن الجانبين بالثقة، وتقص مخاطر الذيل إلى ميزانية محددة.

للحصول على السياق الكامل — ما هي الطرق الإحدى عشرة الأخرى الموجودة وكيف تختلف — راجع النظرة العامة، «12 خوارزمية لتحسين المحفظة، مقارنة». ويمكنك تجربة كل ذلك مباشرة على portfolio-optimizer.marketmaker.cc.

المراجع

  1. López de Prado, M. (2016). Building Diversified Portfolios that Outperform Out of Sample. The Journal of Portfolio Management.
  2. López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley.
  3. Hull, J., & White, A. (1998). Incorporating Volatility Updating into the Historical Simulation Method for Value at Risk. Journal of Risk.
  4. Rockafellar, R. T., & Uryasev, S. (2000). Optimization of Conditional Value-at-Risk. Journal of Risk.
  5. RiskMetrics Group (1996). RiskMetrics — Technical Document. J.P. Morgan.
  6. Marketmaker.cc: marketmaker.cc

الاستشهاد

@article{soloviov2026pipeline,
  author = {Soloviov, Eugen and Zhuravleva, Marina and Kiselev, Kirill},
  title = {Inside Our House Algorithm: HRP + Long/Short + CVaR with Hull-White Adjustment},
  year = {2026},
  url = {https://marketmaker.cc/ar/blog/post/portfolio-pipeline-hrp-cvar},
  description = {A deep dive into Pipeline, a composite portfolio allocation algorithm built on Hierarchical Risk Parity with a signal-driven long/short overlay and a Hull-White CVaR risk-budget correction, with the full specification and its Rust implementation.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Marina Zhuravleva
Marina Zhuravleva

Financial mathematics

Fifth-year student at Bauman Moscow State Technical University (Automatic Control Systems), specializing in financial mathematics. Background in calibrating stochastic-volatility (Heston) and local-volatility (Dupire) models, fair pricing of options including exotics via both Monte-Carlo and analytic formulas, hedging-error reduction, and exposure to LSV models.

Kirill Kiselev
Kirill Kiselev

Portfolio optimization

Fourth-year student at the Faculty of Mechanics and Mathematics, Novosibirsk State University (NSU); thesis on Heston-model calibration and delta-hedging within the same model. Works on portfolio optimization.

Newsletter

ابقَ متقدماً على السوق

اشترك في نشرتنا الإخبارية للحصول على رؤى حصرية حول تداول الذكاء الاصطناعي وتحليلات السوق وتحديثات المنصة.

نحترم خصوصيتك. يمكنك إلغاء الاشتراك في أي وقت.