
Торговая стратегия SP500 на языке MQL5 для начинающих
Введение
В этой статье мы обсудим, как можно построить интеллектуальную торговую стратегию для индекса Standard & Poor's 500 (S&P 500) с помощью языка MQL5. В ходе обсуждения будет показано, как воспользоваться гибкостью языка MQL5 для создания динамического советника на основе сочетания искусственного интеллекта и технического анализа. Обоснование — если мы будем опираться исключительно на ИИ, это может привести к нестабильным стратегиям; однако мы можем добиться более надежного подхода, направляя ИИ с помощью проверенных временем принципов технического анализа. Эта гибридная стратегия направлена на выявление закономерностей, скрытых в огромных объемах данных в наших терминалах MetaTrader 5, используя преимущества ИИ, традиционный анализ и язык MQL5 для повышения эффективности торговли и процесса принятия решений.
Прочитав эту статью, вы получите:
- Эффективные принципы программирования для MQL5
- Информацию о том, как трейдеры могут с легкостью анализировать большие наборы данных по нескольким символам одновременно с помощью команд линейной алгебры в API MQL5.
- Методы выявления скрытых, неочевидных закономерностей в рыночных данных.
История: Что такое Standard & Poor's 500?
Индекс S&P 500, введенный в 1957 г., представляет собой важнейший ориентир, отражающий общие показатели экономики США. За прошедшие годы множество компаний было добавлено в этот индекс и исключено из него, что отражает динамичный характер рынка. Среди старейших участников — General Electric, J.P. Morgan и Goldman Sachs — сохранили свои позиции в индексе с момента его создания.
Индекс S&P 500 представляет собой среднее значение по 500 крупнейшим компаниям в Соединенных Штатов Америки. Стоимости каждой компании придается вес в зависимости от ее капитализации на рынке. Поэтому S&P 500 можно также рассматривать как взвешенный по рыночной капитализации индекс 500 крупнейших публичных компаний мира.
В наши дни такие ведущие компании, как Tesla и Nvidia, задали новые стандарты с самого его основания. Сейчас большую часть веса индекса S&P 500, помимо вышеупомянутых гигантов, составляют крупные технологические компании, включая Google, Apple и Microsoft. Эти титаны технологий изменили ландшафт индекса, отразив сдвиг экономики в сторону технологий и инноваций.
Обзор нашей торговой стратегии
В интернете легко найти список компаний, входящих в индекс. Можно использовать свое понимание состава этого индекса, чтобы разработать торговую стратегию. Выберем несколько крупнейших компаний в индексе и используем соответствующую стоимость каждой из них в качестве ИИ-модели для прогнозирования цены закрытия индекса на основе выборки компаний, имеющих пропорционально большие веса. Мы хотим разработать стратегию, которая объединит ИИ с надежными, проверенными временем технологиями.
Предлагаемая система технического анализа для генерации сигналов будет использовать принципы следования за трендом. Нужно добавить несколько ключевых индикаторов:
- Индекс товарного канала (Commodity Channel Index, CCI) будет нашим индикатором канала. Будем заключать только те сделки, которые подкрепляет объем.
- Индекс относительной силы (Relative Strength Index, RSI) и процентный диапазон Уильямса (Williams Percent Range, WPR) для оценки давления покупателей или продавцов на рынке.
- Скользящая средняя (MA) — как наш индикатор окончательного подтверждения.
Индекс товарного канала (Commodity Channel Index, CCI) вращается вокруг 0. Значение выше 0 предполагает большой объем для возможности покупки, а значение ниже 0 указывает на объем, поддерживающий продажу. Для подтверждения сделки нам необходимо соответствие другим индикаторам, что повысит наши шансы найти наиболее вероятные настройки.
Поэтому, чтобы мы могли покупать:
- Значение CCI должно быть больше 0.
- Значение RSI должно быть больше 50.
- Значение WPR должно быть больше -50.
- MA должна быть ниже цены закрытия.
И наоборот, чтобы мы могли продавать:
- Значение CCI должно быть меньше 0.
- Значение RSI должно быть меньше 50.
- Значение WPR должно быть меньше -50.
- MA должна быть выше цены закрытия.
С другой стороны, для создания нашей ИИ-системы нужно сначала получить исторические цены 12 акций с крупной рыночной капитализацией, входящих в S&P 500, и цены закрытия S&P 500. Это будут наши обучающие данные, на основе которых мы построим модели множественной линейной регрессии для прогнозирования будущих цен индекса.
После этого ИИ-система будет использовать обычную регрессию наименьших квадратов для прогнозирования значения закрытия S&P 500.
Рис. 1. Наша модель будет рассматривать S&P500 как результат сбора этих акций.
Как только данные будут готовы, мы сможем оценить параметры нашей модели линейной регрессии с помощью следующей формулы.
Рис. 2. Приведенное выше уравнение демонстрирует один из многих возможных способов оценки параметров модели множественной линейной регрессии.
Выражение "Ax - y", окруженное двумя вертикальными линиями, известно как норма вектора L2, произносится "эл-2". В повседневной речи, спрашивая о физическом объекте, насколько он велик, мы говорим о его размере или велиине. Аналогично, когда мы спрашиваем, насколько велик вектор, мы имеем в виду его норму. Вектор, по сути, представляет собой список чисел. Хотя есть разные типы норм, наиболее распространенными являются нормы L1 и L2. Сегодня сосредоточим свое внимание на норме L2, которая рассчитывается как квадратный корень из суммы квадратов величин внутри вектора.
В этом контексте:
- Матрица "A" представляет собой входные данные, состоящие из значений цены закрытия 12 различных акций.
- Символ "x" обозначает значения коэффициентов для нашей модели множественной линейной регрессии, по одному коэффициенту на каждую акцию.
- Продукт "Ax" представляет собой прогнозы относительно будущей цены индекса S&P 500, основанные на наших обучающих данных.
- Обозначение "y" символизирует вектор фактических цен закрытия, которые мы стремились спрогнозировать в процессе обучения.
По сути, первое уравнение показывает нам, что мы ищем значения x, сводящие к минимуму норму L2 выражения Ax - y. Иными словами, ищем значения x, которые сводят к минимуму ошибку нашего прогноза при прогнозировании цены закрытия S&P 500.
Второе уравнение показывает, что значения x, достигающие максимальной ошибки, можно найти, умножив псевдообратную величину A на выходные данные (y) из нашего обучающего набора. Псевдообратная операция успешно реализуется в языке MQL5.
Всего несколько строк кода — и мы можем эффективно найти псевдообратное решение на языке MQL5.
Реализация: Торговая стратегия SP500
Начало работы
Для начала нужно задать входные данные для нашего советника. Нам нужные такие входные данные, которые позволяют пользователю изменять периоды технических индикаторов.
//+------------------------------------------------------------------+ //| SP500 Strategy EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com" #property version "1.00" /* The goal of this application is to implement a strategy to trade the SP500 based on the performance of a sample of stocks that hold proportionally large weights in the index and classical technical analysis. We will use the following stocks: 1)Microsoft 2)Apple 3)Google 4)Meta 5)Visa 6)JPMorgan 7)Nvidia 8)Tesla 9)Johnsons N Johnsons 10)Home Depot 11)Proctor & Gamble 12)Master Card A strategy that relies solely on AI may prove to be unstable. We want to use a combination of AI guided by trading strategies that have been trusted over time. Our AI will be a linear system mapping the price of each stock to the closing price of the index. Recall that financial time series data can be very noisy. Using simpler models may be beneficial. We will attempt to find linear coefficients using ordinary least squares. We will use the pseudo inverse solution to find our model parameters. Our technical analysis will be based on trend following principles: 1)We must observe increasing volume backing a trade before we enter. 2)We want 2 confirmation indicators, RSI and WPR, confirming the trend. 3)We want price to break above the previous week high or low. 4)We want price above or below our MA before we trade. Our goal is to find an optimal combination of statistical insight, and classical technical analysis to hopefully build a strategy with an edge. Gamuchirai Zororo Ndawana Selebi Phikwe Botswana Monday 8 July 2024 21:03 */ //Inputs //How far ahead into the future should we forecast? input int look_ahead = 10; //Our moving average period input int ma_period = 120; //Our RSI period input int rsi_period = 10; //Our WPR period input int wpr_period = 30; //Our CCI period input int cci_period = 40; //How big should our lot sizes be? int input lot_multiple = 20;
Затем нам нужно импортировать свою торговую библиотеку, чтобы можно было управлять нашими позициями.
//Libraries //Trade class #include <Trade/Trade.mqh> CTrade Trade;
В дальнейшем нам следует задать глобальные переменные, которые будут использоваться во всей нашей программе.
//Global variables //Smallest lot size double minimum_volume; //Ask double ask_price; //Bid double bid_price; //How much data should we fetch? int fetch = 20; //Determine the starting date for our inputs int inupt_start; //Determine the starting date for our outputs int output_start; //Which symbols are we going to use to forecast the SP500 close? string desired_symbols[] = {"MSFT","AAPL","NVDA","GOOG","TSLA","META","JPM","JNJ","V","PG","HD","MA"}; //Define our input matrix matrix input_matrix = matrix::Ones(fetch,(ArraySize(desired_symbols))-1); //Define our output matrix, our output matrix has one column matrix output_matrix = matrix::Ones(fetch,1); //A vector to store the initial values of each column vector initiall_input_values = vector::Ones((ArraySize(desired_symbols))); //A variable to store the initial value of the output double initiall_output_value = 0.0; //Defining the matrix that will store our model parameters matrix b; //Define our target symbol string target_symbol = "US_500"; //A vector to temporarily store the historic closing price of each stock vector symbol_history = vector::Zeros(fetch); //A flag to let us know when our model is ready bool model_initialized = false; //A flag to let us know if the training process has started bool model_being_trained = false; //Our model's prediction double model_forecast = 0; //Handlers for our technical indicators int cci_handler,rsi_handler,wpr_handler,ma_handler; //Buffers for our technical indicator values double cci_buffer[],rsi_buffer[],wpr_buffer[],ma_buffer[];
Теперь, когда мы добрались до этого этапа, нужно настроить наши технические индикаторы в обработчике инициализации.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //Let's setup our technical indicators rsi_handler = iRSI(target_symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE); ma_handler = iMA(target_symbol,PERIOD_CURRENT,ma_period,0,MODE_EMA,PRICE_CLOSE); wpr_handler = iWPR(target_symbol,PERIOD_CURRENT,wpr_period); cci_handler = iCCI(target_symbol,PERIOD_CURRENT,cci_period,PRICE_CLOSE); //Get market data minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); //--- return(INIT_SUCCEEDED); }
Нам нужны вспомогательные функции. Сначала определим процедуру для инициализации нашей модели множественной линейной регрессии. Процедура подгонки начинается с установки флага для обозначения, что наша модель проходит обучение. Дополнительные функции решают такие задачи, как получение обучающих данных и подгонка модели. Разбив код на более мелкие функции, мы избежим неоднократного написания одних и тех же строк кода.
//+------------------------------------------------------------------+ //This function defines the procedure for fitting our model void model_initialize(void) { /* Our function will run the initialization procedure as follows: 1)Update the training flag to show that the model has been trained 2)Fetch the necessary input and output data 3)Preprocess the data, normalization and scaling etc. 4)Fit the model 5)Update the flags to show that the model has been trained. From there our application will have the ability to begin trading. */ //First let us set our flag to denote that the model is being trained model_being_trained = true; //Defining the input start time and the output start time //Remember the input data should not contain the most recent bars //The most recent bars must only be included in the output start time //The input data should be older than the output data. inupt_start = 1+look_ahead; output_start = 1; //Fetch the input data fetch_input_data(inupt_start,fetch); //Fetch the output data fetch_output_data(output_start,fetch); //Prepare the data prepare_data(0); //Fit our model fit_linear_model(); //The model is ready model_ready(); }
Теперь приступим к определению функции, которая отвечает за извлечение наших входных данных. Первый столбец нашей входной матрицы будет заключать в себе столбец единиц, представляющий собой точку захвата или среднюю цену закрытия S&P 500, когда значения всех акций равны нулю. Хотя в финансовом мире такой сценарий не имеет смысла, ведь если бы стоимость всех акций была равна 0, цена закрытия S&P 500 тоже была бы равна 0.
В остальном наша процедура получения входных данных проста:
- Инициализируем входную матрицу, заполненную единицами.
- Изменяем форму матрицы
- Вводим значение цены закрытия каждой акции, начиная со второго столбца
- Убеждаемся, что первый столбец заполнен единицами.
//This function will get our input data ready void fetch_input_data(int f_input_start,int f_fetch) { /* To get the input data ready we will to cycle through the symbols available and copy the requested amount of data to the globally scoped input matrix. Our function parameters are: 1)f_input_start: this is where we should get our input data, for the current data pass 0 and only fetch 1. 2)f_fetch: this is how much data should be fetched and copied into the input matrix. */ Print("Preparing to fetch input data"); //Let's observe the original state of our input matrix Print("We will reset the input matrix before fetching the data we need: "); input_matrix = matrix::Ones(f_fetch,(ArraySize(desired_symbols))-1); Print(input_matrix); //We need to reshape our matrix input_matrix.Reshape(f_fetch,13); //Filling the input matrix //Then we need to prepare our input matrix. //The first column must be full of 1's because it represents the intercept. //Therefore we will skip 0, and start filling input values from column 1. This is not a mistake. for(int i=1; i <= ArraySize(desired_symbols);i++) { //Copy the input data we need symbol_history.CopyRates(desired_symbols[i-1],PERIOD_CURRENT,COPY_RATES_CLOSE,f_input_start,f_fetch); //Insert the input data into our input matrix input_matrix.Col(symbol_history,i); } //Ensure that the first column is full of ones for our intercept vector intercept = vector::Ones(f_fetch); input_matrix.Col(intercept,0); //Let us see what our input matrix looks like now. Print("Final state of our input matrix: "); Print(input_matrix); }Процедура извлечения выходных данных будет у нас более или менее похожа на ту, которую мы использовали для входных данных.
//This function will get our output data ready void fetch_output_data(int f_output_start, int f_fetch) { /* This function will fetch our output data for training our model and copy it into our globally defined output matrix. The model has only 2 parameters: 1)f_output_start: where should we start copying our output data 2)f_fetch: amount of data to copy */ Print("Preparing to fetch output data"); //Let's observe the original state of our output matrix Print("Ressetting output matrix before fetching the data we need: "); //Reset the output matrix output_matrix = matrix::Ones(f_fetch,1); Print(output_matrix); //We need to reshape our matrix output_matrix.Reshape(f_fetch,1); //Output data //Copy the output data we need symbol_history.CopyRates(target_symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_output_start,f_fetch); //Insert the output data into our input matrix output_matrix.Col(symbol_history,0); //Let us see what our input matrix looks like now. Print("Final state of our output matrix: "); Print(output_matrix); }
Прежде чем приступить к подгонке нашей модели, нужно стандартизировать и масштабировать наши данные. Это важный шаг, поскольку он упрощает для нашего приложения изучение скрытых взаимосвязей в темпах изменений.
Обратите внимание, что процедура подгонки состоит из 2 отдельных фаз. В первой фазе, когда модель обучается впервые, нам нужно сохранить значения, которые мы использовали для масштабирования каждого столбца в векторе. Каждый столбец будет разделен на первое значение в нем, то есть первое значение в каждом столбце будет равно единице.
С этого момента, если мы наблюдаем в этом столбце любое значение меньше единицы, это значит, что цена падает, а значения больше единицы указывают на рост цены. Если мы наблюдаем значение 0.72, то цена упала на 28%, а если мы наблюдаем значение 1.017, то цена выросла на 1.7%.
Сохранять начальное значение каждого столбца следует только один раз, после чего все будущие входные данные нужно делить на одну и ту же величину, чтобы обеспечивать единообразие.
//This function will normalize our input data so that our model can learn better //We will also scale our output so that it shows the growth in price void prepare_data(int f_flag) { /* This function is responsible for normalizing our inputs and outputs. We want to first store the initial value of each column. Then we will divide each column by its first value, so that the first value in each column is one. The above steps are performed only once, when the model is initialized. All subsequent inputs will simply be divided by the initial values of that column. */ Print("Normalizing and scaling the data"); //This part of the procedure should only be performed once if(f_flag == 0) { Print("Preparing to normalize the data for training the model"); //Normalizing the inputs //Store the initial value of each column for(int i=0;i<ArraySize(desired_symbols);i++) { //First we store the initial value of each column initiall_input_values[i] = input_matrix[0][i+1]; //Now temporarily copy the column, so that we can normalize the entire column at once vector temp_vector = input_matrix.Col(i+1); //Now divide that entire column by its initial value, if done correctly the first value of each column should be 1 temp_vector = temp_vector / initiall_input_values[i]; //Now reinsert the normalized column input_matrix.Col(temp_vector,i+1); } //Print the initial input values Print("Our initial input values for each column "); Print(initiall_input_values); //Scale the output //Store the initial output value initiall_output_value = output_matrix[0][0]; //Make a temporary copy of the entire output column vector temp_vector_output = output_matrix.Col(0); //Divide the entire column by its initial value, so that the first value in the column is one //This means that any time the value is less than 1, price fell //And every time the value is greater than 1, price rose. //A value of 1.023... means price increased by 2.3% //A value of 0.8732.. would mean price fell by 22.67% temp_vector_output = temp_vector_output / initiall_output_value; //Reinsert the column back into the output matrix output_matrix.Col(temp_vector_output,0); //Shpw the scaled data Print("Data has been normlised and the output has been scaled:"); Print("Input matrix: "); Print(input_matrix); Print("Output matrix: "); Print(output_matrix); } //Normalize the data using the initial values we have allready calculated if(f_flag == 1) { //Divide each of the input values by its corresponding input value Print("Preparing to normalize the data for prediction"); for(int i = 0; i < ArraySize(desired_symbols);i++) { input_matrix[0][i+1] = input_matrix[0][i+1]/initiall_input_values[i]; } Print("Input data being used for prediction"); Print(input_matrix); } }
Далее нам необходимо определить функцию, которая задаст обязательные условия для установки флага, чтобы позволить нашему приложению начать торговлю.
//This function will allow our program to switch states and begin forecasting and looking for trades void model_ready(void) { //Our model is ready. //We need to switch these flags to give the system access to trading functionality Print("Giving the system permission to begin trading"); model_initialized = true; model_being_trained = false; }
Чтобы получить прогноз от нашего советника, мы должны сначала извлечь самые последние доступные нам данные, выполнить их предварительную обработку, а затем применить уравнение линейной регрессии для получения прогноза от нашей модели.
//This function will obtain a prediction from our model double model_predict(void) { //First we have to fetch current market data Print("Obtaining a forecast from our model"); fetch_input_data(0,1); //Now we need to normalize our data using the values we calculated when we initialized the model prepare_data(1); //Now we can return our model's forecast return((b[0][0] + (b[1][0]*input_matrix[0][0]) + (b[2][0]*input_matrix[0][1]) + (b[3][0]*input_matrix[0][2]) + (b[4][0]*input_matrix[0][3]) + (b[5][0]*input_matrix[0][4]) + (b[6][0]*input_matrix[0][5]) + (b[7][0]*input_matrix[0][6]) + (b[8][0]*input_matrix[0][7]) + (b[9][0]*input_matrix[0][8]) + (b[10][0]*input_matrix[0][9]) + (b[11][0]*input_matrix[0][10])+ (b[12][0]*input_matrix[0][11]))); }
Необходимо реализовать процедуру подгонки нашей линейной модели с помощью определенных и поясненных выше уравнений. Обратите внимание, что наш API MQL5 обеспечивает нам свободу действий для гибкого и простого исполнения команд линейной алгебры. Мы можем без затруднений объединять операторы в цепочку, что делает наши продукты высокопроизводительными.
Это особенно важно, если вы хотите продавать свою продукцию на рынке. Избегайте использования циклов for, когда вместо этого можно применить векторные операции, поскольку векторные операции быстрее циклов. Ваши конечные пользователи получат очень быстро реагирующее приложение, которое не будет тормозить, что сделает их пользовательский опыт приятным.
//This function will fit our linear model using the pseudo inverse solution void fit_linear_model(void) { /* The pseudo inverse solution finds a list of values that maps our training data inputs to the output by minimizing the error, RSS. These coefficient values can easily be estimated using linear algebra, however these coefficients minimize our error on training data and on not unseen data! */ Print("Attempting to find OLS solutions."); //Now we can estimate our model parameters b = input_matrix.PInv().MatMul(output_matrix); //Let's see our model parameters Print("Our model parameters "); Print(b); }
Затем нам понадобится функция, которая будет отвечать за обновление наших технических индикаторов и излечение текущих рыночных данных.
//Update our technical indicator values void update_technical_indicators() { //Get market data ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid_price = SymbolInfoDouble(_Symbol,SYMBOL_BID); //Copy indicator buffers CopyBuffer(rsi_handler,0,0,10,rsi_buffer); CopyBuffer(cci_handler,0,0,10,cci_buffer); CopyBuffer(wpr_handler,0,0,10,wpr_buffer); CopyBuffer(ma_handler,0,0,10,ma_buffer); //Set the arrays as series ArraySetAsSeries(rsi_buffer,true); ArraySetAsSeries(cci_buffer,true); ArraySetAsSeries(wpr_buffer,true); ArraySetAsSeries(ma_buffer,true); }
Теперь напишем функцию, которая будет выполнять сделанные нами торговые настройки. Наша функция сможет совершать сделки только в том случае, если обе наши системы согласованы.
//Let's try find an entry signal void find_entry(void) { //Our sell setup if(bearish_sentiment()) { Trade.Sell(minimum_volume * lot_multiple,_Symbol,bid_price); } //Our buy setup else if(bullish_sentiment()) { Trade.Buy(minimum_volume * lot_multiple,_Symbol,ask_price); } }
Это стало возможным благодаря наличию функций, которые определяют медвежьи настроения в соответствии с приведенными выше описаниями.
//This function tells us if all our tools align for a sell setup bool bearish_sentiment(void) { /* For a sell setup, we want the following conditions to be satisfied: 1) CCI reading should be less than 0 2) RSI reading should be less than 50 3) WPR reading should be less than -50 4) Our model forecast should be less than 1 5) MA reading should be below the moving average */ return((iClose(target_symbol,PERIOD_CURRENT,0) < ma_buffer[0]) && (cci_buffer[0] < 0) && (rsi_buffer[0] < 50) && (wpr_buffer[0] < -50) && (model_forecast < 1)); }
То же самое справедливо и для функции, которая определяет бычьи настроения.
//This function tells us if all our tools align for a buy setup bool bullish_sentiment(void) { /* For a sell setup, we want the following conditions to be satisfied: 1) CCI reading should be greater than 0 2) RSI reading should be greater than 50 3) WPR reading should be greater than -50 4) Our model forecast should be greater than 1 5) MA reading should be above the moving average */ return((iClose(target_symbol,PERIOD_CURRENT,0) > ma_buffer[0]) && (cci_buffer[0] > 0) && (rsi_buffer[0] > 50) && (wpr_buffer[0] > -50) && (model_forecast > 1)); }
Наконец, нам нужна вспомогательная функция для обновления значений стоп-лосса и тейк-профита.
//+----------------------------------------------------------------------+ //|This function is responsible for calculating our SL & TP values | //+----------------------------------------------------------------------+ void update_stoploss(void) { //First we iterate over the total number of open positions for(int i = PositionsTotal() -1; i >= 0; i--) { //Then we fetch the name of the symbol of the open position string symbol = PositionGetSymbol(i); //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading if(_Symbol == symbol) { //Now we get information about the position ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price long type = PositionGetInteger(POSITION_TYPE); //Position Type double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value //If the position is a buy if(type == POSITION_TYPE_BUY) { //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = NormalizeDouble(ask_price - ((min_distance * sl_width)/2),_Digits); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = NormalizeDouble(ask_price + (min_distance * sl_width),_Digits); //If our current stop loss is less than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } //If the position is a sell else if(type == POSITION_TYPE_SELL) { //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = NormalizeDouble(bid_price + ((min_distance * sl_width)/2),_Digits); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = NormalizeDouble(bid_price - (min_distance * sl_width),_Digits); //If our current stop loss is greater than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } }
Все наши вспомогательные функции будут в надлежащий момент вызываться обработчиком OnTick, который отвечает за управление потоком событий внутри терминала при каждом изменении цены.
void OnTick() { //Our model must be initialized before we can begin trading. switch(model_initialized) { //Our model is ready case(true): //Update the technical indicator values update_technical_indicators(); //If we have no open positions, let's make a forecast using our model if(PositionsTotal() == 0) { //Let's obtain a prediction from our model model_forecast = model_predict(); //Now that we have sentiment from our model let's try find an entry find_entry(); Comment("Model forecast: ",model_forecast); } //If we have an open position, we need to manage it if(PositionsTotal() > 0) { //Update our stop loss update_stoploss(); } break; //Default case //Our model is not yet ready. default: //If our model is not being trained, train it. if(!model_being_trained) { Print("Our model is not ready. Starting the training procedure"); model_initialize(); } break; } }
Рис. 3. Наше приложение в действии.
Ограничения
Есть несколько ограничений, касающихся выбранного нами подхода к созданию ИИ-моделей. Выделим наиболее важные из них:
1.1 Коррелированные входные данные
Проблемы, вызванные коррелированными входными данными, свойственны не только линейным моделям. Эта проблема затрагивает многие модели машинного обучения. Некоторые из компаний, данные об акциях которых находятся в нашей входной матрице, работают в одной отрасли, и стоимость их акций, как правило, растет и опускается одновременно. Это может затруднить для нашей модели выделение влияния каждой акции на эффективность индекса, потому что движения стоимости акций могут маскировать друг друга.
1.2 Нелинейная целевая функция
Зашумленные по своей природе наборы финансовых данных обычно больше подходят для менее сложных моделей, таких как множественная линейная регрессия. Однако в реальной жизни, в большинстве случаев, фактически аппроксимируемые функции редко бывают линейными. Следовательно, чем дальше истинная функция от линейности, тем больше будет дисперсия, наблюдаемая в точности модели.
1.3 Ограничения прямого подхода к моделированию
Наконец, если бы мы захотели включить в нашу модель все акции, входящие в индекс S&P 500, мы получили бы модель, содержащую 500 параметров. Оптимизация такой модели потребовала бы значительных вычислительных ресурсов, а интерпретация ее результатов была бы сопряжена с трудностями. Решения на основе прямого моделирования в этом контексте не поддаются эффективному масштабированию; по мере добавления в будущем большего количества акций количество параметров для оптимизации может быстро стать неуправляемым.
Заключение
В этой статье мы продемонстрировали, насколько просто начать создавать ИИ-модели, интегрирующие технический анализ для принятия обоснованных торговых решений. По мере перехода к более продвинутым моделям большинство рассмотренных здесь шагов останутся неизменными. Это должно придать новичкам уверенности, поскольку они знают, что выполнили проект по машинному обучению от начала и до конца и, как можно надеяться, поняли обоснование каждого принятого решения.
Все наше приложение использует собственный код языка MQL5 и стандартные технические индикаторы, доступные в любой установленной платформе MetaTrader 5. Надеемся, эта статья вдохновит вас к дальнейшему исследованию возможностей имеющихся инструментов и повышению уровня владения языком MQL5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14815





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо за интересный и хорошо документированный код, с нетерпением жду, когда он заработает. Есть по крайней мере одна ловушка для новичков, это изменить целевой символ в строке 92 на символ вашего брокера для sp500, также в строке 84, чтобы соответствовать символам акций, которые есть у вашего брокера.
Что должна возвращать строка 286 "Параметры нашей модели"? Я получаю [-nan(ind)], остальное работает отлично.
Спасибо за интересный и хорошо документированный код, с нетерпением жду, когда он заработает. Есть по крайней мере одна ловушка для новичков, это изменить целевой символ в строке 92 на символ вашего брокера для sp500, также в строке 84, чтобы соответствовать символам акций, которые есть у вашего брокера.
Что должна возвращать строка 286 "Параметры нашей модели"? Я получаю [-nan(ind)], остальное работает отлично.
Я надеялся, что это действительно сработает. Спасибо автору за то, что поделился ею.
не может загрузить индикатор'Relative Strength Index' [4302]
не может загрузить индикатор 'Moving Average' [4302]
не может загрузить индикатор 'Williams' Percent Range' [4302]
не может загрузить индикатор 'Commodity Channel Index' [4302]
Я понимаю, что индикаторы должны автоматически загружаться и отображаться.
Я вручную разместил индикаторы на графике и даже создал шаблон со всеми индикаторами. Даже когда график каждой акции открыт и все индикаторы размещены на графике вручную, советник не может самостоятельно считать индикаторы.
не может загрузить индикатор 'Moving Average' [4302]
не может загрузить индикатор 'Williams' Percent Range' [4302]
не может загрузить индикатор 'Commodity Channel Index' [4302]
Как я понимаю, индикаторы должны автоматически загружаться и отображаться.(не получается)
Я вручную разместил индикаторы на графике и даже создал шаблон со всеми индикаторами. Даже когда открыт график каждой акции и все индикаторы размещены на графике вручную, советник не может самостоятельно считать индикаторы.