تخطٍّ إلى المحتوى
الجمعة، 24 يوليو 2026 · لندنENع
ريان الأزهري.الاستدامة · الطاقة · الكربون · البيئة المبنيةومنعطفات عابرة إلى الفلسفة أو الدين أو البرمجة، حيثما قاد الفضول
التداول الكمي الإنتاجي

لا تستخدم رقماً عائماً للمال أبداً: الخلل الصامت الذي يُسيء تحجيم صفقاتك الفعلية

استخدام رقم الفاصلة العائمة لتمثيل المال خطأ برمجي كلاسيكي، لكنه في نظام تداول لا يكتفي بتقريب بنس تقريباً خاطئاً، بل يُحرّف حجم مركز فعلي بصمت تام. يشرح هذا المقال لماذا يحدث ذلك، ولماذا لا يكفي نوع Money وحده حلاً كما قد تتخيل.

ريان الأزهريأخصائي بيئي معتمد · 8 دقيقة قراءة
بطاقة عنوان لمقال لا تستخدم رقماً عائماً للمال أبداً، تُظهر ناتج 0.1 + 0.2 الكلاسيكي في الفاصلة العائمة، إلى جانب عمودين: مركز مقصود بحجم 100 ومركز مُنفَّذ فعلياً بحجم 133، أكبر بمقدار الثلث.

افتح موجّه Python واكتب 0.1 + 0.2، فتحصل على 0.30000000000000004. يمر كل مبرمج بهذا الموقف مرة على الأقل، فيكتفي بهزّ كتفيه ويصنّفه ضمن غرائب الفاصلة العائمة. وهذه اللامبالاة مقبولة في معظم البرمجيات، لكنها في نظام تداول هي بالضبط الطريقة التي تجد نفسك بها قد نفّذت صفقة أكبر بثلث مما قصدت، دون أن ينهار البرنامج أو يفشل اختبار واحد لينبهك.

القاعدة قديمة ومملة: لا تُخزّن قيماً نقدية أبداً في رقم ذي فاصلة عائمة (Float). لكن ما يستحق أن يُقال، وما لا تذكره تقريباً أي صيغة من صيغ هذه النصيحة، هو لماذا لا يكفي الحل المعتاد حين يدخل المال الحقيقي وأكثر من عملة واحدة في المعادلة. وهذا المقال عن الشكل المحدد والمكلف الذي يتخذه هذا الخلل في نظام يُنفّذ أوامر تداول فعلية.

عجز الفاصلة العائمة عن حمل المال

الفاصلة العائمة أساسها ثنائي، والمال أساسه عشري. فالرقم 0.1 ليس له تمثيل دقيق في معيار IEEE-754، تماماً كما لا يملك الكسر 1/3 تمثيلاً دقيقاً في النظام العشري، وبمجرد أن تجمع عدداً منها يتوقف الخطأ عن الاختباء في الخانة الخامسة عشرة ويبدأ بالظهور في الخانة العاشرة. وهذه ليست ملاحظة أكاديمية عابرة، بل خطأ يتراكم عبر تحجيم المراكز، ويتجمّع عبر التنفيذات (Fills)، وينتهي بكمية كان ينبغي أن تكون عدداً صحيحاً نظيفاً من الأسهم وقد صارت رقماً مثل 99.999999998.

سيرفض الوسيط تلك الكمية، أو الأسوأ، سيقرّبها بصمت في اتجاه لم تختره أنت. فالأسعار تتحرك على شبكة التِك (Tick)، والكميات تتحرك على شبكة اللوت (Lot)، وكلتا الشبكتين خطوات عشرية دقيقة. ورقم عائم لا يمكن أن يستقر تماماً على شبكة عشرية، فأي قيمة نقدية تحملها كرقم عائم تكون، ببنيتها، منزاحة قليلاً عن الشبكة التي يجب أن تحطّ عليها. والحل الذي يلجأ إليه كل مهندس محنّك هو الحل الصحيح فعلاً: استخدم نوعاً عشرياً بقاعدة 10 (مثل Decimal في Python)، أو كائنات Price وQuantity المُنمّطة في مكتبة الوسيط الخاصة بك، لكل ما هو سعر أو كمية أو حجم أو مبلغ مالي.

هذه هي النصيحة المعتادة حتى الآن، وهي نصيحة صحيحة. ومن هنا يبدأ التداول بجعل الأمر أكثر إثارة للاهتمام.

الخلل لا يكمن في بنس مُقرَّب، بل في صفقة مختلة الحجم

في نظام مدفوعات، يكلّفك خلل الفاصلة العائمة سنتاً واحداً وجولة تسوية حسابات مزعجة. أما في نظام تداول، فالخلل نفسه يُعيد تحجيم رهانك بالكامل. والنسخة الأخطر منه لا علاقة لها بالتقريب على الإطلاق. إنها العملة.

Chart

The same leg, two sizings

A leg quoted in one currency and sized from an account in another shipped about a third too large, because the FX conversion was a plain float multiply with the wrong rate. No crash, no failing test. Illustrative and sanitised.

Intended (correct FX)100 sharesShipped (untyped float FX)133 shares
Source: Building a Production Quant Trading System (Titan), sanitised, illustrative

دخلت ساق (Leg) من الصفقة، مُسعَّرة بعملة ومُحجَّمة انطلاقاً من حساب بعملة أخرى، أكبر بنحو الثلث مما كان مقصوداً. والسبب كان مبتذلاً تماماً: كانت عملية تحويل العملات مجرد ضرب عائم غير مُنمَّط، استخدم سعر الصرف الخاطئ لتلك الساق. لم يُطلَق أي استثناء. ولم يفشل أي اختبار وحدة، لأن العملية الحسابية كانت سليمة تماماً من الناحية الحسابية. كان الرقم ببساطة رقماً خاطئاً، ورقم خاطئ من النوع الصحيح يمر مباشرة إلى الوسيط ليتحول إلى مركز حقيقي مبالغ في حجمه.

هذا هو الدرس الخاص بالتداول تحديداً: خلل الفاصلة العائمة في المال لا يُعلن عن نفسه بسجل تتبع الأخطاء (Stack Trace)، بل يُعلن عن نفسه بعد أسابيع، في هيئة تراجع لا تستطيع تفسيره، لأن الاستراتيجية التي تحققتَ منها لم تكن، بهدوء، الاستراتيجية التي أطلقتها فعلياً. اختبار المحاكاة حجّم الساق بشكل صحيح؛ أما المسار الفعلي فلم يفعل؛ ولم يكن هناك في الوسط أي شيء سيُخبرك بذلك.

لماذا نوع Money ليس الحل الذي تتخيله؟

الفكرة التالية البديهية هي: "حسناً، سأُغلّف المال في نوع Money يحمل عملته، وسيمسك المُصرّف (Compiler) هذا الخطأ من تلقاء نفسه." وهذا حدس صحيح، لكنه لا ينجح إن أُخذ حرفياً. وهذا هو الجزء الذي يستحق أن يُعلَّق فوق المكتب.

فنوع Money القياسي، على شكل Money(amount, currency)، سواء جاء من مكتبة خارجية أو من المكتبة القياسية للغة، يحمل بطاقة عملة ويسمح لك، دون أي اعتراض، بضربه في رقم مجرد. ولا بد أن يسمح بذلك، لأن هذه عملية مشروعة تماماً: تحجيم مركز، أو تطبيق كسر، والأخطر من ذلك أن تحويل العملات بحد ذاته هو عملية "مال × رقم عددي". وتحويل العملات الأجنبية يبدو تماماً كالعملية الآمنة، فلا يرى نظام الأنواع فيه أي خطأ. تلجأ إلى Money متوقعاً أن يُنقذك المُصرّف، لكنك أمام هذا الخلل بالذات لا تملك أي حماية إضافية، تماماً كحالك مع رقم عائم خام.

أما حاجز الأمان الذي ينجح بالفعل فهو أضيق وأشد صرامة: تحتاج إلى نوع نقدي يرفض دمج عملتين مختلفتين دون استدعاء صريح لدالة convert() تتحقق من سعر الصرف. فإضافة الدولار الأمريكي إلى الين الياباني ينبغي ألا يُصرَّف (Compile) أصلاً. وتحجيم ساق باليّن انطلاقاً من ميزانية بالدولار ينبغي أن يكون غير قابل للتمثيل في الشيفرة إطلاقاً، ما لم يُذكر التحويل صراحة، بسعر صرف يُمرَّر ويُتحقّق منه. وبمجرد أن تصبح العمليات الحسابية بين العملات ممكنة فقط عبر دالة واحدة متعمّدة وقابلة للتدقيق، تتحول قصة الحرب هذه إلى خطأ نوعي (Type Error) يظهر وقت كتابة الشيفرة، بدلاً من أن تبقى لغزاً داخل الأرباح والخسائر (P&L). الدرس هنا ليس "استخدم نوع Money"، بل "استخدم نوعاً نقدياً لا تُصرَّف عملياته غير المشروعة أصلاً"، وهذه مجموعة أنواع أضيق بكثير من تلك التي تكتفي بتتبع العملة.

الانضباط المطلوب: حد فاصل لا حظر شامل

لا يعني هذا أن العائم محظور من شيفرتك البرمجية بالكامل، بل يعني أن له حداً لا يجوز أن يتجاوزه.

Figure

Where float is fine, and where it is forbidden

The rule is a boundary, not a blanket ban. Float belongs in the statistics; it must never reach an order.

float is fine

the statistical layer

Returns, Sharpe, volatility, z-scores. These are dimensionless ratios, so base-2 rounding at the fifteenth digit changes nothing you could measure.

float is forbidden

anything that becomes an order

Prices, quantities, sizes and money sit on exact decimal grids. A float cannot land on a tick or lot grid, so 99.999999998 shares is either rejected or silently rounded the way you did not choose.

The boundary

convert once, at the edge

One function turns 'risk 1% of equity' into a typed quantity, snaps it to the grid with a stated rounding policy, and does it on the way out. That single conversion is the only place a float may touch a size.

The trap

a Money type is not enough

A stock Money carries a currency but still lets you multiply it by a bare rate, because FX conversion is money times a scalar. Only a type that refuses to combine two currencies without an explicit, rate-checked convert protects you.

Make the wrong thing impossible to write, not something to remember.

Source: Building a Production Quant Trading System (Titan)

العائم ليس مقبولاً فحسب في الطبقة الإحصائية، بل هو الخيار المُفضَّل فيها: العوائد، ونسب شارب (Sharpe Ratio)، والتقلبات، والدرجات المعيارية (Z-scores). فهذه كلها نسب عديمة الأبعاد، والتقريب الثنائي في الخانة العشرية الخامسة عشرة لا صلة له برقم ستقارنه بعتبة معينة. وفرض Decimal على إحصاءاتك لا يمنحك شيئاً، بل يكلّفك السرعة والوضوح.

والخط الفاصل هنا حادّ: في اللحظة التي يتحول فيها رقم إلى أمر تداول، يجب أن يصبح كائن Money مُنمَّطاً. والمكان الوحيد الذي يجدر أن يُفرض فيه ذلك هو الدالة الواحدة عند حافة الاستراتيجية، التي تُحوّل نية ("خاطر بـ1% من رأس المال") إلى حجم فعلي. وتلك الدالة الواحدة هي المكان الوحيد المسموح فيه لرقم عائم أن يلمس كمية، وهي التي تُحوّله عند الخروج إلى كائن نقدي مُنمَّط، وتُقرّبه إلى أقرب نقطة على شبكة الأداة المالية.

وحين تُقرَّب النتيجة إلى الشبكة، يجب أن يحدث ذلك عن قصد لا عن مصادفة. فكمية مثل 99.999999998 سهماً يجب أن تستقر عند 99 أو 100، وأيّهما هو قرار سياسة تختاره أنت، لا شيئاً ترثه من النظام. حدّد صراحة ما إذا كنت تُقرِّب لأعلى دائماً، أو وفق قاعدة الزوجي الأقرب (Half-Even)، أو دائماً باتجاه الحجم الأصغر الأكثر تحفظاً، وقرِّب مرة واحدة بالضبط، عند تلك النقطة الفاصلة. فـDecimal يعتمد افتراضياً تقريب البنوك (Banker's Rounding)، بينما قد يتوقع وسيطك شيئاً مختلفاً، وأي قاعدة غير مُعلنة لكسر التعادل هي موضع إضافي ينتظر فيه افتراض خاطئ أن تحطّ عليه إحدى التنفيذات.

القاعدة الأعم التي يندرج تحتها هذا الدرس

تراجع خطوة إلى الوراء، وستجد أن قاعدة المال هذه ليست إلا حالة خاصة من مبدأ أعم يُثمر في كل زاوية من نظام يتعامل مع رأس مال حقيقي: اجعل الخطأ مستحيل الكتابة، لا شيئاً عليك أن تتذكره. فمعيار برمجي تحتفظ به في ذاكرتك وحدها هو معيار ستنساه في الثانية فجراً، أثناء الصفقة التي تهم فعلاً. أما المعايير التي تصمد فهي تلك التي تفرضها عليك الأدوات أو نظام الأنواع نفسه، لا ذاكرتك.

لديك ثلاث روافع مرتبة بحسب قوتها. الأقوى: اجعل العملية الخطرة غير موجودة أصلاً، بحيث يكون نوع العملة خالياً من أي عملية ضرب غير مُتحقَّق منها بين العملات، فلا يستطيع التعبير عن الخلل من الأساس. تليها: اجعلها تفشل بصوت عالٍ، بحيث يوقفها معامل إلزامي بلا قيمة افتراضية، أو فحص في CI، قبل أن تعمل. والأضعف، لكنها لا تزال مفيدة: اجعلها ظاهرة للعيان، بحيث يبدو رقم عائم خام في مسار التحجيم سطراً خاطئاً بوضوح أثناء المراجعة. عبارة "لا تستخدم أبداً رقماً عائماً للمال" عبارة يسهل تذكرها، لكن الذاكرة أضعف الروافع جميعها. أما النسخة الدائمة الصامدة فهي نوع بيانات لا يسمح لك بالخطأ من الأساس.

إن أخذت شيئاً واحداً من هذا المقال، فخذ هذا: دقّق المسار الوحيد في نظامك الذي تتحول فيه نية إلى أمر تداول، وتأكد من أن رقماً عائماً خاماً لا يمكنه أن ينجو من تلك الرحلة. فكل ما هو قبل تلك الحدود يمكن أن يبقى عائماً؛ ولا شيء بعدها يجوز له ذلك.

وهذه واحدة من أربع "ثوابت غير قابلة للتفاوض" في أساسيات النظام الذي تنبع منه هذه المقالات. والفصل الكامل، تخطيط المشروع والثوابت غير القابلة للتفاوض، متاح للقراءة مجاناً، ويضع قاعدة المال إلى جانب التنميط الكامل للأنواع، والبذور العشوائية الثابتة، ووحدة مقاييس واحدة مشتركة: أربع قواعد، تُسقط كل واحدة منها فئة كاملة من الأخطاء المتكررة، لا خطأ واحداً بعينه.

هذا مقال هندسي، وليس نصيحة استثمارية، ولا يحتوي على أي استراتيجية قابلة للتداول. جميع الأرقام توضيحية ومُنقّحة.

Chart

The same leg, two sizings

A leg quoted in one currency and sized from an account in another shipped about a third too large, because the FX conversion was a plain float multiply with the wrong rate. No crash, no failing test. Illustrative and sanitised.

Intended (correct FX)100 sharesShipped (untyped float FX)133 shares
Source: Building a Production Quant Trading System (Titan), sanitised, illustrative

Figure

Where float is fine, and where it is forbidden

The rule is a boundary, not a blanket ban. Float belongs in the statistics; it must never reach an order.

float is fine

the statistical layer

Returns, Sharpe, volatility, z-scores. These are dimensionless ratios, so base-2 rounding at the fifteenth digit changes nothing you could measure.

float is forbidden

anything that becomes an order

Prices, quantities, sizes and money sit on exact decimal grids. A float cannot land on a tick or lot grid, so 99.999999998 shares is either rejected or silently rounded the way you did not choose.

The boundary

convert once, at the edge

One function turns 'risk 1% of equity' into a typed quantity, snaps it to the grid with a stated rounding policy, and does it on the way out. That single conversion is the only place a float may touch a size.

The trap

a Money type is not enough

A stock Money carries a currency but still lets you multiply it by a bare rate, because FX conversion is money times a scalar. Only a type that refuses to combine two currencies without an explicit, rate-checked convert protects you.

Make the wrong thing impossible to write, not something to remember.

Source: Building a Production Quant Trading System (Titan)

قراءات إضافية

طاقة المكاتب، الجزء 2 من 25

مقالات ذات صلة

اختبارك التاريخي ليس دليلاً: لماذا تموت أنظمة التداول الكمي قبل أن تتداول

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

· 9 min

دليل شامل للهجرة من ووردبريس إلى Next.js

دليل عملي شامل للانتقال بموقع محتوى من ووردبريس إلى Next.js دون فقدان ترتيبك في محركات البحث: قاعدة الحفاظ على الروابط التي تحكم كل شيء، وبنية محتوى قادرة على الصمود أثناء الانتقال، والتعامل مع اللغتين العربية والإنجليزية، ومتطلبات السيو والأمان، وعملية الانتقال التي يمكنك التراجع عنها في أي وقت.

· 20 min

لقد توقفت عن الدفع مقابل استضافة ووردبريس

استضافة ووردبريس المُدارة ضريبة تدفعها مالاً وأداءً وقلقاً أمنياً. إليك كيف احتفظت بمحرّر ووردبريس الذي أحبّه، وشغّلته محلياً مجاناً، ونشرت الموقع كملفات HTML ساكنة على شبكة الحافة: زاحف بلغة Node بلا اعتمادات خارجية جعلته مفتوح المصدر.

· 7 min

مقالات إلى بريدك

كتابات جديدة عن سوريا والاستدامة والتمويل، بضع مرات في الشهر.

يمكنك إلغاء الاشتراك في أي وقت. يقرأها أكثر من ٤٢٠٠ متخصّص.