
Как опередить любой рынок (Часть II): Прогнозирование технических индикаторов
Введение
Инвесторы, стремящиеся применить машинное обучение в электронной торговле, сталкиваются с многочисленными трудностями, и многим не удается достичь желаемых результатов. Цель статьи — выделить некоторые причины, по которым, по моему мнению, начинающий алгоритмический трейдер может не добиться удовлетворительной доходности относительно сложности своих стратегий. Я покажу, почему точность прогнозирования цены финансовой ценной бумаги часто не может превышать 50% и как прогнозирование значений технических индикаторов может повысить точность примерно до 70%. В руководстве будут предоставлены пошаговые инструкции по лучшим методам анализа временных рядов.
Прочитав статью, вы узнаете, как использовать Python и MQL5, чтобы повысить точность ваших моделей машинного обучения и находить индикаторы рыночных изменений раньше других участников рынка.
Прогнозирование значений индикаторов
Мы извлечем исторические данные из терминала MetaTrader 5 и проанализируем их с помощью стандартных библиотек Python. Анализ покажет, что прогнозирование изменений значений индикаторов более эффективно, чем прогнозирование изменений цен на торговые инструменты. Это справедливо, поскольку мы можем лишь частично наблюдать факторы, влияющие на цену торгового инструмента. В действительности мы не можем смоделировать каждую отдельную переменную, влияющую на цену символа, ввиду их огромного количества и сложности. Однако мы можем в полной мере наблюдать все факторы, влияющие на значение технического индикатора.
Сначала я продемонстрирую принцип, а затем в конце нашего обсуждения объясню, почему этот подход работает лучше. Увидев сначала принцип в действии, вам будет легче понять теоретическое объяснение. Начнем с выбора иконки списка символов в меню над графиком.
Наша цель здесь - получить данные:
- Откройте терминал MetaTrader 5.
- Выберите значок списка символов в меню над графиком.
- Выберите желаемый символ и таймфрейм для анализа.
- Экспортируйте исторические данные в файл со значениями, разделенными запятыми (csv).
Рис. 1. Получение исторических данных
Найдите символ, который вы хотите смоделировать.
Рис. 2. Поиск нужного символа
После этого выберите вкладку "Бары" в меню и обязательно запросите как можно больше данных.
Рис. 3. Запрос исторических данных
Выберите "Экспортировать бары" в нижнем меню, чтобы начать анализ данных в Python.
,
Рис. 4. Экспорт исторических данных
Как обычно, начнем с импорта необходимых нам библиотек.
#Load libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt
Далее считываем наши исторические рыночные данные. Обратите внимание, что терминал MetaTrader 5 экспортирует CSV-файлы, разделенные табуляцией, поэтому мы передаем нотацию табуляции в параметр разделителя нашего вызова pandas read csv.
#Read the data csv = pd.read_csv("/content/Volatility 75 Index_M1_20190101_20240131.csv",sep="\t") csv
Наши исторические данные будут выглядеть так. Нам нужно немного переформатировать заголовки столбцов, а также добавить технический индикатор.
Рис. 5. Наши исторические данные из терминала MetaTrader 5
Теперь переименуем столбцы.
#Format the data csv.rename(columns={"<DATE>":"date","<TIME>":"time","<TICKVOL>":"tickvol","<VOL>":"vol","<SPREAD>":"spread","<OPEN>":"open","<HIGH>":"high","<LOW>":"low","<CLOSE>":"close"},inplace=True) csv.ta.sma(length= 60,append=True) csv.dropna(inplace=True) csv
Рис. 6. Форматирование данных
Теперь мы можем определить наши входные данные.
#Define the inputs predictors = ["open","high","low","close","SMA_60"]
Далее мы масштабируем наши данные так, чтобы наша модель могла достаточно хорошо обучаться.
#Scale the data
csv["open"] = csv["open"] /csv.loc[0,"open"]
csv["high"] = csv["high"] /csv.loc[0,"high"]
csv["low"] = csv["low"] /csv.loc[0,"low"]
csv["close"] = csv["close"] /csv.loc[0,"close"]
csv["SMA_60"] = csv["SMA_60"] /csv.loc[0,"SMA_60"]
Подойдем к этой задаче как к задаче классификации. Наша цель будет категорической. Целевое значение 1 означает, что цена ценной бумаги выросла за 60 свечей, а целевое значение 0 означает, что цена упала за тот же период. Обратите внимание, что у нас есть две цели. Одна цель предназначена для отслеживания изменения цены закрытия, а другая — для отслеживания изменения скользящей средней.
Мы будем использовать ту же схему кодирования для изменений скользящей средней: целевое значение 1 означает, что будущее значение скользящей средней в следующих 60 свечах будет больше, и наоборот, целевое значение 0 означает, что значение скользящей средней снизится в течение следующих 60 свечей.
#Define the close csv["Target Close"] = 0 csv["Target MA"] = 0
Определите, насколько далеко в будущее вы хотели бы заглянуть.
#Define the forecast horizon look_ahead = 60
Введем целевые значения.
#Set the targets
csv.loc[csv["close"] > csv["close"].shift(-look_ahead) ,"Target Close"] = 0
csv.loc[csv["close"] < csv["close"].shift(-look_ahead) ,"Target Close"] = 1
csv.loc[csv["SMA_60"] > csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 0
csv.loc[csv["SMA_60"] < csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 1
csv = csv[:-look_ahead]
Мы подгоним ту же группу моделей к тому же набору данных. Помните, что единственное отличие состоит в том, что в первый раз наши модели будут пытаться предсказать изменение цены закрытия, тогда как во втором тесте они вместо этого попытаются предсказать изменение технического индикатора, в нашем примере — скользящей средней.
Определив цели, мы можем приступить к импорту моделей, необходимых для нашего анализа.
#Get ready from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.neural_network import MLPClassifier from sklearn.metrics import accuracy_score from sklearn.decomposition import PCA from sklearn.model_selection import TimeSeriesSplit
Мы подготовим разделение временного ряда, чтобы оценить, где наша ошибка проверки ниже. Кроме того, мы преобразуем наши входные данные с помощью функций анализа главных компонентов (Principal Components Analysis, PCA) в sklearn. Этот шаг необходим, поскольку наши входные столбцы могут быть коррелированы, что может затруднить процесс обучения нашей модели. Выполняя PCA, мы преобразуем наш набор данных в форму, которая гарантирует отсутствие корреляции между входными данными, тем самым повышая производительность нашей модели.
#Time series split splits = 10 gap = look_ahead models_close = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] models_ma = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] #Prepare the data pca = PCA() csv_reduced = pd.DataFrame(pca.fit_transform(csv.loc[:,predictors]))
Давайте теперь проследим за нашими уровнями точности (close accuracy), используя нейронную сеть, которая попытается напрямую прогнозировать изменения цены закрытия.
#Fit the neural network predicting close price model_close = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_close.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target Close"]) print("Close accuracy: ",accuracy_score(csv.loc[300070:,"Target Close"], model_close.predict(csv_reduced.loc[300070:,:])))
Точность прогнозирования изменения цены закрытия составила 49,9%. Это не впечатляет, учитывая уровень сложности, на который мы согласились. Мы могли бы получить тот же уровень точности с помощью более простой модели, которую легче поддерживать и понимать. Более того, если мы будем правы только в 49% случаев, то мы останемся в убытке. Давайте сравним этот результат с нашей точностью прогнозирования изменений индикатора скользящей средней (MA accuracy).
#Fit the model predicting the moving average model_ma = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_ma.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target MA"]) print("MA accuracy: ",accuracy_score(csv.loc[300070:,"Target MA"], model_ma.predict(csv_reduced.loc[300070:,:])))
Точность нашей модели составила 68,8% при прогнозировании изменений скользящей средней по сравнению с 49,9% при прогнозировании изменений цены. Это приемлемый уровень точности относительно сложности используемой нами методики моделирования.
Теперь мы подгоним различные модели и посмотрим, какая модель лучше всего предскажет изменения цены, а какая — изменения скользящей средней.
#Error metrics tscv = TimeSeriesSplit(n_splits=splits,gap=gap) error_close_df = pd.DataFrame(index=np.arange(0,splits),columns=models_close) error_ma_df = pd.DataFrame(index=np.arange(0,splits),columns=models_ma)
Сначала мы оценим точность каждой из выбранных нами моделей в прогнозировании цены закрытия.
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target Close"]) error_close_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target Close"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Рис. 7. Точность различных моделей, пытающихся классифицировать изменения цен
Рис. 8. Визуализация эффективности каждой из наших моделей
Мы можем оценить наивысшую точность, зафиксированную каждой моделью при прогнозировании цены закрытия.
for i in enumerate(np.arange(0,error_close_df.shape[1])): print(error_close_df.columns[i[0]]," ", error_close_df.iloc[:,i[0]].max())
LDA 0.5192457894678943
XGB 0.5119523008041539
Neural Net Simple 0.5234700724948571
Neural Net Large 0.5186627504042771
Как мы видим, ни одна из наших моделей не показала блестящих результатов. Все они находились в диапазоне 50%, однако линейный дискриминантный анализ (LDA) показал наилучшие результаты в группе.
С другой стороны, теперь мы установили, что наши модели будут демонстрировать более высокую точность при прогнозировании изменений определенных технических индикаторов. Теперь мы хотим определить из нашей группы кандидатов, какая модель лучше всего подходит для прогнозирования изменений скользящей средней.
#Training each model to predict changes in a technical indicator (in this example simple moving average) instead of close price. for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target MA"]) error_ma_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target MA"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Рис. 9. Точность моделей, пытающихся предсказать изменения скользящей средней
Рис. 10. Визуализация точности нашей модели при прогнозировании изменений скользящей средней
Оценим наивысшую точность, зафиксированную каждым типом модели.
for i in enumerate(np.arrange(0,error_ma_df.shape[1])): print(error_ma_df.columns[i[0]]," ", error_ma_df.iloc[:,i[0]].max())
Logistic Regression 0.6927054112625546
LDA 0.696401658911147
XGB 0.6932664488520731
Neural Net Simple 0.6947955513019373
Neural Net Large 0.6965006655445914
Обратите внимание, что даже несмотря на то, что большая нейронная сеть сразу показала максимальный уровень точности, мы не хотели бы использовать ее в деле из-за нестабильной производительности. Это можно увидеть по двум точкам на графике производительности большой нейронной сети, которые значительно ниже ее средней производительности. Таким образом, из результатов можно сделать вывод, что с учетом нашего текущего набора данных идеальная модель должна быть сложнее простой логистической регрессии и проще большой нейронной сети.
Далее мы построим торговую стратегию, которая прогнозирует будущие движения индикатора скользящей средней в качестве торгового сигнала. Нашей выбранной моделью будет небольшая нейронная сеть, поскольку она кажется гораздо более стабильной.
Сначала импортируем необходимые нам библиотеки.
#Import the libraries we need import MetaTrader5 as mt5 import pandas_ta as ta import pandas as pd
Далее настроим нашу торговую среду.
#Trading global variables MARKET_SYMBOL = 'Volatility 75 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 10000 #We will always enter at the minimum volume VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_M1 #Which model have we decided to work with? neural_network_model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
Давайте определим минимально допустимый объем по символу, которым мы хотим торговать.
#Determine the minimum volume for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
Теперь мы можем создать функцию, которая будет доставлять нам наши рыночные ордера.
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
Кроме того, нам нужна еще одна функция, которая поможет нам закрывать наши рыночные ордера.
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 10000, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
Кроме того, нам необходимо определить диапазон дат, за который мы хотим запросить данные.
#Update our date from and date to date_from = datetime(2024,1,1) date_to = datetime.now()
Прежде чем мы сможем передать запрошенные у брокера данные, мы должны сначала предварительно обработать их в том же формате, которым наша модель пользовалась во время обучения.
#Let's create a function to preprocess our data def preprocess(df): #Calculating 60 period Simple Moving Average df.ta.sma(length=60,append=True) #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
Двигаясь дальше, мы должны иметь возможность получать прогноз от нашей нейронной сети и интерпретировать этот прогноз как торговый сигнал для открытия длинной или короткой позиции.
#Get signals from our model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our moving average forecast model #Remember 1 means buy and 0 means sell forecast = neural_network_model.predict(last_close) return forecast[0]Наконец, свяжем всё это воедино, чтобы создать нашу торговую стратегию.
#Now we define the main body of our trading algorithm if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Рис. 11. Наша модель в действии
Теоретическое объяснение
По мнению автора, одной из причин, по которой мы можем наблюдать более высокую точность при прогнозировании изменений технических индикаторов, является тот факт, что мы никогда не можем наблюдать все переменные, влияющие на цену торгового инструмента. В лучшем случае мы можем наблюдать их лишь частично, тогда как при прогнозировании изменений технического индикатора мы полностью осознаем все входные данные, повлиявшие на технический индикатор. Мы даже знаем точную формулу любого технического индикатора.
Рис. 12. Мы знаем математическое описание всех технических индикаторов, но не существует математической формулы цены закрытия
Например, технический индикатор Money Flow Multiplier (MFM) рассчитывается по приведенной выше формуле. Поэтому, если мы хотим предсказать изменения в MFM, нам нужны только компоненты его формулы: цены закрытия (close), минимальные (low) и максимальные (high).
Напротив, при прогнозировании цены закрытия у нас нет конкретной формулы, которая бы говорила нам, какие входные данные на нее влияют. Это часто приводит к снижению точности, что говорит о том, что наш текущий набор входных данных может быть неинформативным или что мы внесли слишком много шума, выбрав некачественные входные данные.
По сути, задача машинного обучения — найти цель, определяемую набором входных данных. Когда мы используем технический индикатор в качестве цели, мы по сути утверждаем, что на технический индикатор влияют цены открытия, закрытия, минимальные и максимальные цены, что является правдой. Однако, как разработчики алгоритмов, мы часто используем наши инструменты наоборот. Мы используем набор ценовых данных и технических индикаторов для прогнозирования цены, подразумевая, что технические индикаторы влияют на цену, что неверно и никогда не будет верно.
При попытке изучить цель, базовая функция которой неизвестна, мы потенциально становимся жертвой так называемой ложной регрессии. Мы подробно обсуждали это в предыдущей статье. Проще говоря, наша модель может найти взаимосвязь, которой не существует в реальной жизни. Более того, модель может демонстрировать обманчиво низкий уровень ошибок при проверке, создавая впечатление, что она достаточно обучилась, хотя на самом деле она ничего не узнала о реальном мире.
Чтобы проиллюстрировать, что такое ложная регрессия, представьте, что мы с вами спускаемся с холма и прямо на горизонте видим неясную фигуру. Мы слишком далеко, чтобы разобрать, что это, но основываясь на том, что я видел, я кричу: "Там внизу собака". Итак, прибыв на место, мы обнаруживаем куст, а за кустом оказывается собака.
Рис. 13. Мог ли я видеть собаку?
Вы уже видите проблему? Я, конечно, мог бы заявить о том, что мое утверждение оказалось верным, но, по сути, я никак не мог увидеть собаку с того места, где мы стояли, когда я делал это утверждение - мы были слишком далеко, а фигура выглядела слишком размытой с того места, где мы стояли.
Между данными, которые я увидел на вершине горы, и выводом, к которому я пришел, просто не было никакой связи. То есть входные и выходные данные были независимы друг от друга. Всякий раз, когда модель рассматривает входные данные, которые не имеют никакого отношения к выходным, но при этом выдает правильный ответ, мы называем это ложной регрессией. Ложные регрессии случаются постоянно!
Поскольку у нас нет формул, описывающих, что влияет на цену торгового инструмента, мы склонны сталкиваться с ложными регрессиями, используя входные данные, которые не оказывают никакого влияния на нашу цель - цену закрытия. Попытка доказать, что регрессия не является ложной, может оказаться сложной задачей. Проще использовать цель, имеющую известную связь с входными данными.
Заключение
Мы рассмотрели преимущества прогнозирования изменений технических индикаторов над прямым прогнозированием цены закрытия. Необходимы дальнейшие исследования, чтобы выяснить, существуют ли какие-либо другие технические индикаторы, которые мы можем прогнозировать с большей точностью, чем скользящая средняя. Имейте в виду, что, хотя наша точность прогнозирования скользящей средней относительно высока, между изменениями скользящей средней и изменениями цены существует задержка.
Другими словами, скользящая средняя может падать, в то время как цена растет, однако если мы всем MQL5-сообществом будем коллективно работать над улучшением алгоритма, я уверен, что в конечном итоге мы сможем достичь нового уровня точности.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14936
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Да, что-то вроде этого :)
Максимка, ты что новые рынки открыл для себя, в каждом посте улыбки
Да, что-то вроде этого :)
Я понимаю, о чем вы, поиск таких индикаторов почему-то напоминает мне детскую игру в прятки. Как будто нужные нам данные прячутся где-то там, скрываясь за шумом и неинформативными индикаторами.
В одном согласен с автором: можно прогнозировать индикаторы, в остальном не очень. Прогнозировать можно без сторонних программ, а с помощью стандартных возможностей MQL5. Однако, моё личное мнение - прогнозировать достоверно можно только осцилляторы. Вот пример:
Прогнозируются тиковые объёмы.
В одном я согласен с автором: прогнозировать индикаторы можно, а вот все остальное - не очень. Можно прогнозировать и без сторонних программ, а с помощью стандартных возможностей MQL5. Однако мое личное мнение, что достоверно можно прогнозировать только осцилляторы. Вот пример:
Прогнозируются тиковые объемы.
Пока что я делаю так: сначала рассчитываю буфер индикатора, как обычно. Затем удаляю последние n записей и переношу все обратно. Затем я пытаюсь заполнить первые n записей своим прогнозом, после чего сдвигаю индикаторный буфер на n позиций вперед, так что первые n графиков - это прогнозные значения индикатора, за которыми следуют фактические расчеты индикатора, но мне не очень везет с этим подходом. Вы, кажется, освоили это, любое руководство, которым вы можете поделиться, будет оценено по достоинству.
Кроме того, вы упомянули, что считаете, что осцилляторы - это путь к успеху? Почему вы так считаете, мне интересно узнать больше о том, что вы думаете по этому поводу и какова ваша точка зрения.
для ручных жахальщиков и особенно верящих в свой внутренний голос
Нет, даже если не жахать, а торговать умеренным риском, то можно терять, просто на это уйдёт больше времени. Слив будет не за час-день, а за недели-месяцы. А что ещё за голос? Ты голоса слышишь какие-то?