صعود التداول الآلي: تداول الآلات في مؤشر S&P 500
نشرت: 2022-03-11في الوقت الحاضر ، أكثر من 60 في المائة من أنشطة التداول بأصول مختلفة (مثل الأسهم والعقود الآجلة للمؤشرات والسلع) لم يعد يقوم بها المتداولون "البشر" ، وبدلاً من ذلك يعتمدون على التداول الآلي. هناك برامج متخصصة تعتمد على خوارزميات معينة تشتري وتبيع الأصول تلقائيًا عبر أسواق مختلفة ، بهدف تحقيق عائد إيجابي على المدى الطويل.
في هذه المقالة ، سأوضح لك كيفية التنبؤ ، بدقة جيدة ، بكيفية وضع الصفقة التالية للحصول على مكاسب إيجابية. في هذا المثال ، بصفتي الأصل الأساسي للتداول ، اخترت مؤشر S&P 500 ، المتوسط المرجح لـ 500 شركة أمريكية برسملة أكبر. تتمثل الإستراتيجية البسيطة للغاية التي يجب تنفيذها في شراء مؤشر S&P 500 عندما تبدأ وول ستريت إكستشينج التداول ، في الساعة 9:30 صباحًا ، وبيعها في جلسة الإغلاق في الساعة 4:00 مساءً بالتوقيت الشرقي. إذا كان سعر إغلاق المؤشر أعلى من سعر الافتتاح ، فهناك مكسب إيجابي ، في حين يمكن تحقيق مكاسب سلبية إذا كان سعر الإغلاق أقل من سعر الافتتاح. لذا فإن السؤال هو: كيف نعرف ما إذا كانت جلسة التداول ستنتهي بسعر إغلاق أعلى من سعر الافتتاح؟ يعد التعلم الآلي أداة قوية لتحقيق مثل هذه المهمة المعقدة ، ويمكن أن يكون أداة مفيدة لدعمنا في قرار التداول.
التعلم الآلي هو الحدود الجديدة للعديد من تطبيقات الحياة الواقعية المفيدة. التداول المالي هو واحد من هؤلاء ، ويتم استخدامه كثيرًا في هذا القطاع. أحد المفاهيم المهمة حول التعلم الآلي هو أننا لا نحتاج إلى كتابة رمز لكل نوع من القواعد الممكنة ، مثل التعرف على الأنماط. هذا لأن كل نموذج مرتبط بالتعلم الآلي يتعلم من البيانات نفسها ، ومن ثم يمكن استخدامه لاحقًا للتنبؤ بالبيانات الجديدة غير المرئية.
إخلاء المسؤولية : الغرض من هذه المقالة هو إظهار كيفية تدريب أساليب التعلم الآلي ، وفي أمثلة التعليمات البرمجية المقدمة لا يتم شرح كل وظيفة. لا تهدف هذه المقالة إلى السماح بنسخة واحدة ولصق جميع التعليمات البرمجية وتشغيل نفس الاختبارات المقدمة ، حيث إن بعض التفاصيل مفقودة خارج نطاق المقالة. مطلوب أيضًا معرفة أساسية ببايثون. الهدف الرئيسي من المقالة هو إظهار مثال على كيفية فعالية التعلم الآلي في التنبؤ بعمليات الشراء والبيع في القطاع المالي. ومع ذلك ، فإن التجارة بالمال الحقيقي تعني امتلاك العديد من المهارات الأخرى ، مثل إدارة الأموال وإدارة المخاطر. هذه المقالة هي مجرد جزء صغير من "الصورة الكبيرة".
بناء أول برنامج تداول آلي للبيانات المالية
لذا ، هل تريد إنشاء برنامجك الأول لتحليل البيانات المالية والتنبؤ بالتداول الصحيح؟ دعني اريك كيف. سأستخدم Python لرمز التعلم الآلي ، وسنستخدم البيانات التاريخية من خدمة Yahoo Finance. كما ذكرنا سابقًا ، فإن البيانات التاريخية ضرورية لتدريب النموذج قبل إجراء توقعاتنا.
للبدء ، نحتاج إلى التثبيت:
- Python ، وعلى وجه الخصوص أقترح استخدام دفتر IPython.
- حزمة Yahoo Finance Python (الاسم الدقيق هو
yahoo-finance) من خلال الأمر الطرفي:pip install yahoo-finance. - نسخة تجريبية مجانية من حزمة التعلم الآلي تسمى GraphLab. لا تتردد في التحقق من الوثائق المفيدة لتلك المكتبة.
لاحظ أن جزءًا فقط من GraphLab هو مفتوح المصدر ، الإطار SFrame ، لذلك نحتاج إلى ترخيص لاستخدام المكتبة بأكملها. يوجد ترخيص مجاني لمدة 30 يومًا ورخصة غير تجارية للطلاب أو أولئك المشاركين في مسابقات Kaggle. من وجهة نظري ، تعد GraphLab Create مكتبة بديهية للغاية وسهلة الاستخدام لتحليل البيانات وتدريب نماذج التعلم الآلي.
التنقيب في كود بايثون
دعنا نتعمق في بعض أكواد Python لمعرفة كيفية تنزيل البيانات المالية من الإنترنت. أقترح استخدام دفتر IPython لاختبار الكود التالي ، لأن IPython يتمتع بالعديد من المزايا مقارنة بـ IDE التقليدي ، خاصةً عندما نحتاج إلى دمج كود المصدر وكود التنفيذ وبيانات الجدول والمخططات معًا في نفس المستند. للحصول على شرح موجز لاستخدام دفتر IPython ، يرجى إلقاء نظرة على مقالة مقدمة إلى IPython Notebook.
لذلك ، دعنا ننشئ دفتر ملاحظات IPython جديدًا ونكتب بعض التعليمات البرمجية لتنزيل الأسعار التاريخية لمؤشر S&P 500. ملاحظة ، إذا كنت تفضل استخدام أدوات أخرى ، فيمكنك البدء بمشروع Python جديد في IDE المفضل لديك.
import graphlab as gl from __future__ import division from datetime import datetime from yahoo_finance import Share # download historical prices of S&P 500 index today = datetime.strftime(datetime.today(), "%Y-%m-%d") stock = Share('^GSPC') # ^GSPC is the Yahoo finance symbol to refer S&P 500 index # we gather historical quotes from 2001-01-01 up to today hist_quotes = stock.get_historical('2001-01-01', today) # here is how a row looks like hist_quotes[0] {'Adj_Close': '2091.580078', 'Close': '2091.580078', 'Date': '2016-04-22', 'High': '2094.320068', 'Low': '2081.199951', 'Open': '2091.48999', 'Symbol': '%5eGSPC', 'Volume': '3790580000'} هنا ، hist_quotes عبارة عن قائمة من القواميس ، وكل عنصر من عناصر القاموس هو يوم تداول بقيم Open و High و Low و Close و Adj_close و Volume و Symbol و Date . خلال كل يوم تداول ، يتغير السعر عادةً بدءًا من سعر Open إلى سعر Close ، ويصل إلى الحد الأقصى والحد الأدنى للقيمة High Low . نحتاج إلى قراءته وإنشاء قوائم بكل من البيانات الأكثر صلة. أيضًا ، يجب ترتيب البيانات حسب أحدث القيم في البداية ، لذلك نحتاج إلى عكسها:
l_date = [] l_open = [] l_high = [] l_low = [] l_close = [] l_volume = [] # reverse the list hist_quotes.reverse() for quotes in hist_quotes: l_date.append(quotes['Date']) l_open.append(float(quotes['Open'])) l_high.append(float(quotes['High'])) l_low.append(float(quotes['Low'])) l_close.append(float(quotes['Close'])) l_volume.append(int(quotes['Volume'])) يمكننا حزم جميع عروض الأسعار التي تم تنزيلها في كائن SFrame ، وهو إطار بيانات قائم على عمود قابل للتوسع بدرجة كبيرة ، ويتم ضغطه. تتمثل إحدى المزايا في أنه يمكن أيضًا أن يكون أكبر من حجم ذاكرة الوصول العشوائي (RAM) لأنه مدعوم بالقرص. يمكنك التحقق من الوثائق لمعرفة المزيد حول SFrame.
لذلك ، دعنا نخزن البيانات التاريخية ثم نتحقق منها:
qq = gl.SFrame({'datetime' : l_date, 'open' : l_open, 'high' : l_high, 'low' : l_low, 'close' : l_close, 'volume' : l_volume}) # datetime is a string, so convert into datetime object qq['datetime'] = qq['datetime'].apply(lambda x:datetime.strptime(x, '%Y-%m-%d')) # just to check if data is sorted in ascending mode qq.head(3)| قريب | التاريخ والوقت | متوسط | قليل | افتح | الصوت |
| 1283.27 | 2001-01-02 00:00:00 | 1320.28 | 1276.05 | 1320.28 | 1129400000 |
| 1347.56 | 2001-01-03 00:00:00 | 1347.76 | 1274.62 | 1283.27 | 1880700000 |
| 1333.34 | 2001-01-04 00:00:00 | 1350.24 | 1329.14 | 1347.56 | 2131000000 |
الآن يمكننا save البيانات على القرص باستخدام طريقة SFrame ، على النحو التالي:
qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)دعونا نرى كيف يبدو S&P 500
لمعرفة كيف ستبدو بيانات S&P 500 المحملة ، يمكننا استخدام الكود التالي:
import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])ناتج الكود هو الرسم البياني التالي:
تدريب بعض نماذج التعلم الآلي
مضيفا النتيجة
كما ذكرت في الجزء التمهيدي من هذه المقالة ، فإن الهدف من كل نموذج هو توقع ما إذا كان سعر الإغلاق سيكون أعلى من سعر الافتتاح. ومن ثم ، في هذه الحالة ، يمكننا تحقيق عائد إيجابي عند شراء الأصل الأساسي. لذلك ، نحتاج إلى إضافة عمود outcome على بياناتنا والذي سيكون target أو المتغير predicted . سيكون كل صف من هذا العمود الجديد:
-
+1ليوم أعلى مع سعرClosingأعلى من سعرOpening. -
-1ليوم هابط مع سعرClosingأقل من سعرOpening.
# add the outcome variable, 1 if the trading session was positive (close>open), 0 otherwise qq['outcome'] = qq.apply(lambda x: 1 if x['close'] > x['open'] else -1) # we also need to add three new columns 'ho' 'lo' and 'gain' # they will be useful to backtest the model, later qq['ho'] = qq['high'] - qq['open'] # distance between Highest and Opening price qq['lo'] = qq['low'] - qq['open'] # distance between Lowest and Opening price qq['gain'] = qq['close'] - qq['open'] نظرًا لأننا نحتاج إلى التقييم قبل آخر يوم تداول ببضعة أيام ، فنحن بحاجة إلى تأخر البيانات لمدة يوم أو أكثر. لهذا النوع من العمليات المتأخرة ، نحتاج إلى كائن آخر من حزمة GraphLab يسمى TimeSeries . تحتوي TimeSeries على طريقة shift تتخلف عن البيانات بعدد معين من الصفوف.
ts = gl.TimeSeries(qq, index='datetime') # add the outcome variable, 1 if the bar was positive (close>open), 0 otherwise ts['outcome'] = ts.apply(lambda x: 1 if x['close'] > x['open'] else -1) # GENERATE SOME LAGGED TIMESERIES ts_1 = ts.shift(1) # by 1 day ts_2 = ts.shift(2) # by 2 days # ...etc.... # it's an arbitrary decision how many days of lag are needed to create a good forecaster, so # everyone can experiment by his own decisionإضافة المتنبئين
المتنبئون عبارة عن مجموعة من متغيرات الميزات التي يجب اختيارها لتدريب النموذج والتنبؤ بنتائجنا . لذلك ، يعد اختيار عامل التنبؤ أمرًا بالغ الأهمية ، إن لم يكن العنصر الأكثر أهمية ، في المتنبئ.
على سبيل المثال لا الحصر ، قد يكون أحد العوامل التي يجب مراعاتها هو ما إذا كان إغلاق اليوم أعلى من إغلاق الأمس ، وقد يتم تمديده مع إغلاق اليومين السابقين ، وما إلى ذلك. يمكن ترجمة خيار مماثل باستخدام الكود التالي:
ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close'] كما هو موضح أعلاه ، لقد أضفت عمودين جديدين للميزات ، feat1 feat2 في مجموعة البيانات ( ts ) التي تحتوي على 1 إذا كانت المقارنة صحيحة و 0 بخلاف ذلك.
تهدف هذه المقالة إلى إعطاء مثال على التعلم الآلي المطبق في القطاع المالي. أفضل التركيز على كيفية استخدام نماذج التعلم الآلي مع البيانات المالية ، ولن نخوض في التفاصيل فيما يتعلق بكيفية اختيار العوامل الصحيحة لتدريب النماذج. إنه أمر شامل للغاية لشرح سبب استخدام عوامل معينة فيما يتعلق بالآخرين ، بسبب الزيادة الكبيرة في التعقيد. بحثي الوظيفي هو دراسة العديد من الفرضيات حول اختيار العوامل لإنشاء تنبؤ جيد. لذا ، في البداية ، أقترح عليك تجربة العديد من المجموعات المختلفة من العوامل ، لمعرفة ما إذا كانت قد تزيد من دقة النموذج.
# add_features is a helper function, which is out of the scope of this article, # and it returns a tuple with: # ts: a timeseries object with, in addition to the already included columns, also lagged columns # as well as some features added to train the model, as shown above with feat1 and feat2 examples # l_features: a list with all features used to train Classifier models # l_lr_features: a list all features used to train Linear Regression models ts, l_features, l_lr_features = add_features(ts) # add the gain column, for trading operations with LONG only positions. # The gain is the difference between Closing price - Opening price ts['gain'] = ts['close'] - ts['open'] ratio = 0.8 # 80% of training set and 20% of testing set training = ts.to_sframe()[0:round(len(ts)*ratio)] testing = ts.to_sframe()[round(len(ts)*ratio):]تدريب نموذج شجرة القرار
يحتوي GraphLab Create على واجهة نظيفة جدًا لتنفيذ نماذج التعلم الآلي. يحتوي كل نموذج على طريقة تم create لتناسب النموذج مع مجموعة بيانات التدريب. المعلمات النموذجية هي:
-
training- هي مجموعة تدريب تحتوي على أعمدة مميزة وعمود مستهدف. -
target- هو اسم العمود الذي يحتوي على المتغير الهدف. -
validation_set- هي مجموعة بيانات لمراقبة أداء تعميم النموذج. في حالتنا ، ليس لديناvalidation_set. -
features- هي قائمة بأسماء أعمدة الميزات المستخدمة لتدريب النموذج. -
verbose- إذا كانtrue، اطبع معلومات التقدم أثناء التدريب.
في حين أن المعلمات الأخرى هي نموذجية للنموذج نفسه ، مثل:
-
max_depth- هو أقصى عمق للشجرة.
باستخدام الكود التالي ، نبني شجرة قراراتنا:
max_tree_depth = 6 decision_tree = gl.decision_tree_classifier.create(training, validation_set=None, target='outcome', features=l_features, max_depth=max_tree_depth, verbose=False)قياس أداء النموذج المناسب
الدقة هي مقياس مهم لتقييم جودة المتنبئ. هو عدد التنبؤات الصحيحة مقسومًا على إجمالي نقاط البيانات. نظرًا لأن النموذج مزود ببيانات التدريب ، فإن الدقة التي يتم تقييمها باستخدام مجموعة التدريب أفضل من تلك التي تم الحصول عليها بمجموعة اختبار.
الدقة هي جزء من التوقعات الإيجابية الإيجابية. نحتاج إلى الدقة لتكون رقمًا أقرب إلى 1 ، لتحقيق معدل ربح "مثالي". شجرة decision_tree الخاصة بنا ، كمصنف آخر من حزمة GraphLab Create ، لها طريقتها في evaluate للحصول على العديد من المقاييس المهمة للنموذج المجهز.
يقيس الاسترجاع قدرة المصنف على التنبؤ بأمثلة إيجابية. يمكن تفسير الاسترجاع على أنه احتمال تحديد المثال الإيجابي المختار عشوائيًا بشكل صحيح بواسطة المصنف. نحتاج إلى أن تكون الدقة رقمًا أقرب إلى 1 ، لتحقيق معدل ربح "مثالي".
سيوضح الكود التالي دقة النموذج المجهز مع كل من مجموعة التدريب ومجموعة الاختبار:
decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)كما هو موضح أعلاه ، تبلغ دقة النموذج مع مجموعة الاختبار حوالي 57 بالمائة ، وهو أفضل بطريقة ما من رمي قطعة نقود (50 بالمائة).
توقع البيانات
يحتوي GraphLab Create على نفس الواجهة للتنبؤ بالبيانات من النماذج المختلفة المجهزة. سوف نستخدم طريقة predict ، التي تحتاج إلى مجموعة اختبار للتنبؤ بالمتغير المستهدف ، في outcome حالتنا. الآن ، يمكننا التنبؤ بالبيانات من مجموعة الاختبار:
predictions = decision_tree.predict(testing) # and we add the predictions column in testing set testing['predictions'] = predictions # let's see the first 10 predictions, compared to real values (outcome column) testing[['datetime', 'outcome', 'predictions']].head(10)| التاريخ والوقت | حصيلة | تنبؤات |
| 2013-04-05 00:00:00 | -1 | -1 |
| 2013-04-08 00:00:00 | 1 | 1 |
| 2013-04-09 00:00:00 | 1 | 1 |
| 2013-04-10 00:00:00 | 1 | -1 |
| 2013-04-11 00:00:00 | 1 | -1 |
| 2013-04-12 00:00:00 | -1 | -1 |
| 2013-04-15 00:00:00 | -1 | 1 |
| 2013-04-16 00:00:00 | 1 | 1 |
| 2013-04-17 00:00:00 | -1 | -1 |
| 2013-04-18 00:00:00 | -1 | 1 |
الإيجابيات الكاذبة هي الحالات التي يتوقع فيها النموذج نتيجة إيجابية بينما تكون النتيجة الحقيقية من مجموعة الاختبار سلبية. والعكس صحيح ، السلبيات الكاذبة هي الحالات التي يتوقع فيها النموذج نتيجة سلبية حيث تكون النتيجة الحقيقية من مجموعة الاختبار إيجابية.
تنتظر إستراتيجية التداول الخاصة بنا الحصول على نتيجة إيجابية متوقعة لشراء S&P 500 بسعر Opening ، وبيعها بسعر Closing ، لذلك نأمل في الحصول على أقل معدل إيجابيات كاذبة لتجنب الخسائر. بعبارة أخرى ، نتوقع أن يتمتع نموذجنا بأعلى معدل دقة .
كما نرى ، هناك سلبيتان خاطئتان (في 2013-04-10 و 2013-04-11) وإيجابيتان كاذبتان (في 2013-04-15 و2013-04-18) ضمن القيم العشرة الأولى المتوقعة من مجموعة الاختبار.
بحساب بسيط ، مع الأخذ في الاعتبار هذه المجموعة الصغيرة المكونة من عشرة تنبؤات:
- الدقة = 6/10 = 0.6 أو 60٪
- الدقة = 3/5 = 0.6 أو 60٪
- استدعاء = 3/5 = 0.6 أو 60٪
لاحظ أن الأرقام المذكورة أعلاه عادة ما تكون مختلفة عن بعضها البعض ، لكنها في هذه الحالة هي نفسها.
اختبار رجعي للنموذج
نقوم الآن بمحاكاة كيفية تداول النموذج باستخدام قيمه المتوقعة. إذا كانت النتيجة المتوقعة تساوي +1 ، فهذا يعني أننا نتوقع يومًا صعوديًا . مع يوم الصعود ، نشتري المؤشر في بداية الجلسة ، ونبيع المؤشر في نهاية الجلسة خلال نفس اليوم. بالمقابل ، إذا كانت النتيجة المتوقعة تساوي -1 فإننا نتوقع يومًا هبوطيًا ، لذلك لن نتداول خلال ذلك اليوم.
يتم إعطاء الربح والخسارة ( pnl ) لصفقة يومية كاملة ، وتسمى أيضًا الدورة المستديرة ، في هذا المثال من خلال:
-
pnl = Close - Open(لكل يوم تداول)
باستخدام الكود الموضح أدناه ، أستدعي الوظيفة المساعدة plot_equity_chart لإنشاء مخطط بمنحنى المكاسب التراكمية (منحنى الأسهم). دون التعمق في الأمر ، تحصل ببساطة على سلسلة من قيم الربح والخسارة وتحسب سلسلة المبالغ التراكمية للتخطيط.
pnl = testing[testing['predictions'] == 1]['gain'] # the gain column contains (Close - Open) values # I have written a simple helper function to plot the result of all the trades applied to the # testing set and represent the total return expressed by the index basis points # (not expressed in dollars $) plot_equity_chart(pnl,'Decision tree model') Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511هنا ، شارب هي نسبة شارب السنوية ، وهي مؤشر مهم على جودة نموذج التداول.
النظر في التداولات التي يتم التعبير عنها يومًا بعد يوم في حين أن mean هو متوسط قائمة الربح والخسارة ، و sd هو الانحراف المعياري. للتبسيط في الصيغة الموضحة أعلاه ، فقد اعتبرت أن العائد الخالي من المخاطر يساوي 0.
بعض أساسيات التداول
يتطلب تداول المؤشر شراء أصل مشتق مباشرة من المؤشر. يقوم العديد من الوسطاء بتكرار مؤشر S&P 500 بمنتج مشتق يسمى CFD (عقد الفرق) ، وهو اتفاق بين طرفين لتبادل الفرق بين سعر الافتتاح وسعر إغلاق العقد.
مثال : قم بشراء 1 CFD S&P 500 عند Open (القيمة 2000) ، قم ببيعها عند Close اليوم (القيمة 2020). الفرق ، ومن ثم الربح ، هو 20 نقطة. إذا كانت قيمة كل نقطة 25 دولارًا:
- الربح الإجمالي هو
20 points x $25 = $500مع عقد CFD واحد.
لنفترض أن الوسيط يحتفظ بانزلاق سعرى قدره 0.6 نقطة لتحقيق إيراداته الخاصة:
- صافي الربح
(20 - 0.6) points x $25 = $485.
جانب آخر مهم يجب مراعاته هو تجنب الخسائر الكبيرة في التجارة. قد تحدث عندما تكون النتيجة المتوقعة +1 ولكن قيمة النتيجة الحقيقية هي -1 ، لذلك فهي إيجابية خاطئة . في هذه الحالة ، يتبين أن الجلسة الختامية هي يوم هبوطي مع سعر إغلاق أقل من الافتتاح ، ونحصل على خسارة.

يجب وضع أمر إيقاف الخسارة للحماية من أقصى خسارة نتحملها في التجارة ، ويتم تشغيل مثل هذا الأمر عندما ينخفض سعر الأصل عن القيمة الثابتة التي حددناها من قبل.
إذا نظرنا إلى السلسلة الزمنية التي تم تنزيلها من Yahoo Finance في بداية هذه المقالة ، فكل يوم له سعر Low وهو أقل سعر تم الوصول إليه خلال ذلك اليوم. إذا قمنا بتعيين مستوى إيقاف عند -3 نقاط بعيدًا عن سعر Opening ، Low - Open = -5 فسيتم تفعيل أمر الإيقاف ، وسيتم إغلاق المركز المفتوح بخسارة -3 نقاط بدلاً من -5 . هذه طريقة بسيطة لتقليل المخاطر. يمثل الكود التالي وظيفتي المساعدة لمحاكاة التجارة بمستوى التوقف:
# This is a helper function to trade 1 bar (for example 1 day) with a Buy order at opening session # and a Sell order at closing session. To protect against adverse movements of the price, a STOP order # will limit the loss to the stop level (stop parameter must be a negative number) # each bar must contains the following attributes: # Open, High, Low, Close prices as well as gain = Close - Open and lo = Low - Open def trade_with_stop(bar, slippage = 0, stop=None): """ Given a bar, with a gain obtained by the closing price - opening price it applies a stop limit order to limit a negative loss If stop is equal to None, then it returns bar['gain'] """ bar['gain'] = bar['gain'] - slippage if stop<>None: real_stop = stop - slippage if bar['lo']<=stop: return real_stop # stop == None return bar['gain']تكاليف التداول
تكاليف المعاملة هي المصاريف التي يتم تكبدها عند شراء أو بيع الأوراق المالية. تشمل تكاليف المعاملات عمولات السماسرة وفروق الأسعار (الفرق بين السعر الذي يدفعه التاجر مقابل الورقة المالية والسعر الذي يدفعه المشتري) ، ويجب أخذها في الاعتبار إذا أردنا إجراء اختبار رجعي لاستراتيجيتنا ، على غرار السيناريو الحقيقي. غالبًا ما يحدث الانزلاق في تداول الأسهم عندما يكون هناك تغيير في السبريد. في هذا المثال ولعمليات المحاكاة المستمرة التالية ، تم إصلاح تكاليف التداول على النحو التالي:
- الانزلاق السعري = 0.6 نقطة
- العمولة = 1 دولار لكل صفقة (دورة واحدة تكلف 2 دولار)
فقط لكتابة بعض الأرقام ، إذا كان إجمالي ربحنا 10 نقاط ، 1 نقطة = 25 دولارًا ، لذا 250 دولارًا شاملاً تكاليف التداول ، سيكون صافي ربحنا (10 - 0.6)*$25 - 2 = $233 .
يوضح الكود التالي محاكاة لإستراتيجية التداول السابقة مع وقف الخسارة بمقدار -3 نقاط. المنحنى الأزرق هو منحنى العوائد التراكمية. التكاليف الوحيدة المحتسبة هي الانزلاق (0.6 نقطة) ، ويتم التعبير عن النتيجة في نقاط الأساس (نفس الوحدة الأساسية لقيم S&P 500 التي تم تنزيلها من Yahoo Finance).
SLIPPAGE = 0.6 STOP = -3 trades = testing[testing['predictions'] == 1][('datetime', 'gain', 'ho', 'lo', 'open', 'close')] trades['pnl'] = trades.apply(lambda x: trade_with_stop(x, slippage=SLIPPAGE, stop=STOP)) plot_equity_chart(trades['pnl'],'Decision tree model') print("Slippage is %s, STOP level at %s" % (SLIPPAGE, STOP)) Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3 يتم استخدام الكود التالي لعمل تنبؤات بطريقة مختلفة قليلاً. يرجى الانتباه إلى طريقة predict التي تسمى بمعامل إضافي output_type = “probability” . تُستخدم هذه المعلمة لإرجاع احتمالات القيم المتوقعة بدلاً من التنبؤ بالفئة ( +1 لنتيجة متوقعة إيجابيًا ، -1 لنتيجة متوقعة سلبًا). يرتبط احتمال أكبر من أو يساوي 0.5 بقيمة متوقعة +1 وقيمة احتمالية أقل من 0.5 مرتبطة بقيمة متوقعة قدرها -1 . كلما زاد هذا الاحتمال ، زادت فرصة توقعنا ليوم Up Day حقيقي.
predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1) نقوم الآن باختبار النموذج بدالة مساعدة تسمى backtest_ml_model والتي تحسب سلسلة العوائد التراكمية بما في ذلك الانزلاق والعمولات ، وترسم قيمها. للإيجاز ، دون شرح وظيفة backtest_ml_model بدقة ، فإن التفاصيل المهمة التي يجب تسليط الضوء عليها هي أنه بدلاً من تصفية تلك الأيام outcome = 1 كما فعلنا في المثال السابق ، نقوم الآن بتصفية هذه predictions_prob أو الأكبر من threshold = 0.5 ، كالتالي:
trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')] تذكر أن صافي الربح لكل يوم تداول هو: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION .
مقياس مهم آخر يستخدم لتقييم جودة استراتيجية التداول هو الحد الأقصى للسحب . بشكل عام ، يقيس أكبر انخفاض منفرد من الذروة إلى الأسفل ، في قيمة المحفظة المستثمرة. في حالتنا ، هذا هو أهم هبوط من الذروة إلى أسفل منحنى الأسهم (لدينا أصل واحد فقط في محفظتنا ، S&P 500). لذلك ، بالنظر إلى SArray للربح والخسارة pnl ، نحسب التراجع على النحو التالي:
drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown) يتم حساب backtest_summary داخل الدالة المساعدة:
- الحد الأقصى للسحب (بالدولار) كما هو موضح أعلاه.
- الدقة مع طريقة
Graphlab.evaluation. - الدقة ، مع طريقة
Graphlab.evaluation. - استدعاء باستخدام طريقة
Graphlab.evaluation.
بتجميعها جميعًا ، يوضح المثال التالي منحنى الأسهم الذي يمثل العوائد التراكمية لاستراتيجية النموذج ، مع التعبير عن جميع القيم بالدولار.
model = decision_tree predictions_prob = model.predict(testing, output_type="probability") THRESHOLD = 0.5 bt_1_1 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_1) Mean of PnL is 54.054286 Sharpe is 3.502897 Round turns 511 Name: DecisionTree Accuracy: 0.577373211964 Precision: 0.587084148728 Recall: 0.724637681159 Max Drawdown: -1769.00025 لزيادة دقة القيم المتوقعة ، بدلاً من الاحتمال القياسي 0.5 (50 بالمائة) ، نختار قيمة حد أعلى ، لنكون أكثر ثقة في أن النموذج يتوقع يوم صعود .
THRESHOLD = 0.55 # it's the minimum threshold to predict an Up day so hopefully a good day to trade bt_1_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_2) Mean of PnL is 118.244689 Sharpe is 6.523478 Round turns 234 Name: DecisionTree Accuracy: 0.560468140442 Precision: 0.662393162393 Recall: 0.374396135266 Max Drawdown: -1769.00025كما نرى في الرسم البياني أعلاه ، فإن منحنى الأسهم أفضل بكثير من ذي قبل (Sharpe هو 6.5 بدلاً من 3.5) ، حتى مع عدد أقل من المنعطفات المستديرة.
من هذه النقطة فصاعدًا ، سننظر في جميع الطرز التالية ذات العتبة الأعلى من القيمة القياسية.
تدريب المصنف اللوجستي
يمكننا تطبيق بحثنا ، كما فعلنا سابقًا مع شجرة القرار ، في نموذج مصنف لوجستي. يحتوي GraphLab Create على نفس الواجهة مع كائن Logistic Classifier ، وسوف نستدعي طريقة create لبناء نموذجنا بنفس قائمة المعلمات. علاوة على ذلك ، نفضل توقع متجه الاحتمال بدلاً من متجه الفئة المتوقع (المكون من +1 لنتيجة إيجابية ، و -1 لنتيجة سلبية) ، لذلك سيكون لدينا عتبة أكبر من 0.5 لتحقيق دقة أفضل في منطقتنا التوقع.
model = gl.logistic_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_2_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_2_2) Mean of PnL is 112.704215 Sharpe is 6.447859 Round turns 426 Name: LogisticClassifier Accuracy: 0.638491547464 Precision: 0.659624413146 Recall: 0.678743961353 Max Drawdown: -1769.00025 في هذه الحالة ، يوجد ملخص مشابه جدًا لشجرة القرار. بعد كل شيء ، كلا النموذجين عبارة عن مصنفات ، يتنبأون فقط بفئة من النتائج الثنائية ( +1 ، -1 ).
تدريب نموذج الانحدار الخطي
الاختلاف الرئيسي في هذا النموذج هو أنه يتعامل مع القيم المستمرة بدلاً من الفئات الثنائية ، كما ذكرنا من قبل. لا يتعين علينا تدريب النموذج بمتغير مستهدف يساوي +1 لأيام الصعود و -1 لأيام الهبوط ، يجب أن يكون هدفنا متغيرًا مستمرًا. نظرًا لأننا نريد توقع مكاسب إيجابية ، أو بمعنى آخر سعر إغلاق أعلى من سعر الافتتاح ، يجب أن يكون الهدف الآن هو عمود الربح في مجموعة التدريب الخاصة بنا. أيضًا ، يجب أن تتكون قائمة الميزات من قيم مستمرة ، مثل Open Close وما إلى ذلك.
للإيجاز ، لن أخوض في تفاصيل كيفية تحديد الميزات الصحيحة ، لأن هذا خارج نطاق هذه المقالة ، والتي تميل أكثر لإظهار كيف يجب أن نطبق نماذج مختلفة للتعلم الآلي على مجموعة بيانات. قائمة المعلمات التي تم تمريرها إلى طريقة الإنشاء هي:
-
training- هي مجموعة تدريب تحتوي على أعمدة مميزة وعمود مستهدف. -
target- هو اسم العمود الذي يحتوي على المتغير الهدف. -
validation_set- هي مجموعة بيانات لمراقبة أداء تعميم النموذج. في حالتنا ، ليس لديناvalidation_set. -
features- هي قائمة بأسماء أعمدة الميزات المستخدمة لتدريب النموذج ، بالنسبة لهذا النموذج ، سنستخدم مجموعة أخرى من الاحترام لنماذج المصنف. -
verbose- إذا كان هذاtrue، فسيتم طباعة معلومات التقدم أثناء التدريب. -
max_iterations- هو الحد الأقصى لعدد مرات المرور المسموح بها عبر البيانات. يمكن أن يؤدي المزيد من التمريرات على البيانات إلى نموذج أكثر دقة تدريبًا.
model = gl.linear_regression.create(training, target='gain', features = l_lr_features, validation_set=None, verbose=False, max_iterations=100) predictions = model.predict(testing) # a linear regression model, predict continuous values, so we need to make an estimation of their # probabilities of success and normalize all values in order to have a vector of probabilities predictions_max, predictions_min = max(predictions), min(predictions) predictions_prob = (predictions - predictions_min)/(predictions_max - predictions_min) حتى الآن ، لدينا تنبؤات تمثل SArray للمكاسب المتوقعة ، في حين أن predictions_prob هو SArray مع قيم predictions طبيعية. للحصول على دقة جيدة وعدد معين من المنعطفات المستديرة ، مقارنة بالنماذج السابقة ، اخترت قيمة عتبة 0.4 . بالنسبة إلى predictions_prob أقل من 0.4 ، لن تفتح وظيفة المساعد backtest_linear_model صفقة لأنه من المتوقع حدوث يوم هبوط . خلاف ذلك ، سيتم فتح التجارة.
THRESHOLD = 0.4 bt_3_2 = backtest_linear_model(testing, predictions_prob, target='gain', threshold=THRESHOLD, STOP = -3, plot_title=model.name()) backtest_summary(bt_3_2) Mean of PnL is 138.868280 Sharpe is 7.650187 Round turns 319 Name: LinearRegression Accuracy: 0.631989596879 Precision: 0.705329153605 Recall: 0.54347826087 Max Drawdown: -1769.00025تدريب شجرة معززة
كما فعلنا سابقًا تدريب شجرة قرار ، سنقوم الآن بتدريب مصنف شجرة معزز بنفس المعلمات المستخدمة في نماذج المصنفات الأخرى. بالإضافة إلى ذلك ، قمنا بتعيين عدد max_iterations = 12 من أجل زيادة الحد الأقصى لعدد التكرارات للتعزيز. ينتج عن كل تكرار إنشاء شجرة إضافية. قمنا أيضًا بتعيين قيمة أعلى للعتبة من 0.5 لزيادة الدقة.
model = gl.boosted_trees_classifier.create(training, target='outcome', features=l_features, validation_set=None, max_iterations=12, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.7 bt_4_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_4_2) Mean of PnL is 112.002338 Sharpe is 6.341981 Round turns 214 Name: BoostedTreesClassifier Accuracy: 0.563068920676 Precision: 0.682242990654 Recall: 0.352657004831 Max Drawdown: -1769.00025تدريب الغابة العشوائية
هذا هو آخر نموذج تم تدريبه لدينا ، وهو مصنف عشوائي للغابات ، مؤلف من مجموعة من أشجار القرار. يتم تعيين الحد الأقصى لعدد الأشجار المراد استخدامها في النموذج على num_trees = 10 لتجنب الكثير من التعقيد والتجهيز الزائد.
model = gl.random_forest_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False, num_trees = 10) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_5_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_5_2) Mean of PnL is 114.786962 sharpe is 6.384243 Round turns 311 Name: RandomForestClassifier Accuracy: 0.598179453836 Precision: 0.668810289389 Recall: 0.502415458937 Max Drawdown: -1769.00025Collecting All the Models Together
Now we can join all the strategies together and see the overall result. It's interesting to see the summary of all Machine Learning models, sorted by their precision.
| اسم | accuracy | precision | round turns | sharpe |
| LinearRegression | 0.63 | 0.71 | 319 | 7.65 |
| BoostedTreesClassifier | 0.56 | 0.68 | 214 | 6.34 |
| RandomForestClassifier | 0.60 | 0.67 | 311 | 6.38 |
| DecisionTree | 0.56 | 0.66 | 234 | 6.52 |
| LogisticClassifier | 0.64 | 0.66 | 426 | 6.45 |
If we collect all the profit and loss for each one of the previous models in the array pnl , the following chart depicts the equity curve obtained by the sum of each profit and loss, day by day.
Mean of PnL is 119.446463 Sharpe is 6.685744 Round turns 1504 First trading day 2013-04-09 Last trading day 2016-04-22 Total return 179647Just to give some numbers, with about 3 years of trading, all models have a total gain of about 180,000 dollars. The maximum exposition is 5 CFD contracts in the market, but to reduce the risk they all are closed at the end of each day, so overnight positions are not allowed.
Statistics of the Aggregation of All Models Together
Since each model can open a trade, but we added 5 concurrent models together, during the same day there could be from 1 contract up to 5 CFD contracts. If all models agree to open trades during the same day, there is a high chance to have an Up day predicted. Moreover, we can group by the number of models that open a trade at the same time during the opening session of the day. Then we evaluate precision as a function of the number of concurrent models.
As we can see by the chart depicted above, the precision gets better as the number of models do agree to open a trade. The more models agree, the more precision we get. For instance, with 5 models triggered the same day, the chance to predict an Up day is greater than 85%.
خاتمة
Even in the financial world, Machine Learning is welcomed as a powerful instrument to learn from data and give us great forecasting tools. Each model shows different values of accuracy and precision, but in general, all models can be aggregated to achieve a better result than each one of them taken singularly. GraphLab Create is a great library, easy to use, scalable and able to manage Big Data very quickly. It implements different scientific and forecasting models, and there is a free license for students and Kaggle competitions.
Additional disclosure: This article has been prepared solely for information purposes, and is not an offer to buy or sell or a solicitation of an offer to buy or sell any security or instrument or to participate in any particular trading strategy. Examples presented on these sites are for educational purposes only. Past results are not necessarily indicative of future results.
