
Переосмысливаем классические стратегии на языке Python: Пересечения скользящих средних
Введение
Многие из сегодняшних торговых стратегий были созданы в совершенно иных рыночных условиях. Решающее значение имеет оценка их актуальности на современных рынках, где преобладают алгоритмы. В данной статье рассмотрена стратегия пересечений скользящих средних для оценки ее эффективности в современной финансовой среде.
В статье будут рассмотрены следующие вопросы:
- Существуют ли количественные данные в пользу дальнейшего использования стратегии?
- В чём преимущества этой стратегии по сравнению с прямым ценовым анализом?
- Сохраняет ли эта стратегия свою эффективность в современном алготрейдинге?
- Существуют ли другие индикаторы, которые могут повысить точность стратегии?
- Можно ли эффективно использовать ИИ, чтобы прогнозировать пересечения скользящих средних до их появления?
Метод применения скользящих средних широко изучался в последние десятилетия. Фундаментальная концепция использования этих индикаторов для выявления трендов и торговых сигналов стала основой технического анализа, хотя ее точное происхождение остается неясным.
Обычно стратегия пересечения рассматривает два скользящих средних с разным периодом, но важнейшее условие — чтобы один период был длиннее другого. Когда скользящее среднее с более коротким периодом пересекает скользящее среднее с менее коротким периодом вверх, это сигнализирует о потенциально бычьем тренде, а при пересечении вниз — о медвежьем.Специалисты по техническому анализу десятилетиями используют эту стратегию для определения точек входа и выхода, оценки настроения рынка и в других целях. Для определения текущей эффективности стратегии подвергнем ее современному количественному испытанию. Ниже подробно описан наш подход.
Рис. 1. Пример пересечений скользящих средних в приложении к валютной паре CADJPY.
Общие сведения
Мы отправимся в увлекательное путешествие, в котором соединим своей терминал MetaTrader5 со средой Python. Для начала запросим данные M15 по валютной паре EURUSD с 1 января 2020 года по 25 июня 2024 года. Такой обширный набор данных даст нам полное представление о поведении рынка в последнее время.
Нашим следующим шагом будет установка двух целей. Первая измерит точность прогнозирования непосредственного изменения цен и послужит нашей отправной точкой. Этот ориентир поможет нам сравнить, насколько хорошо мы прогнозируем пересечения скользящих средних. Попутно поищем дополнительные технические индикаторы для повышения точности. Наконец, мы попросим полученные компьютерные модели определить ключевые переменные для прогнозирования пересечений скользящих средних. Если модель не отдает приоритет двум нашим скользящим средним, это может означать, что наши первоначальные предположения были неверными.
Прежде чем погрузиться в цифры, рассмотрим возможные результаты:
-
Преимущество прямого прогнозирования цен: если прямое прогнозирование изменений цен обеспечивает точность не ниже точности, которую дают пересечения скользящих средних, это свидетельствует об отсутствии каких-либо преимуществ их использования, ставя под сомнение обоснованность стратегии.
-
Преимущество прогнозирования пересечений: если при прогнозировании пересечений скользящих средних мы достигнем более высокой точности, это побудило бы нас искать больше данных для дальнейшего улучшения своих прогнозов, подчеркивая потенциальную ценность стратегии.
-
Неактуальность скользящих средних: если наши модели не идентифицируют одно из скользящих средних как решающее для прогнозирования пересечений, это означает, что другие переменные могут быть более значимыми, и говорит об отсутствии предполагаемой связи между двумя скользящими средними.
-
Актуальность скользящих средних: если одно или оба скользящих средних помечены как важные для прогнозирования пересечений, это подтверждает значимую связь между ними, что позволяет строить надежные модели для обоснованных прогнозов.
С помощью этого анализа нам будет проще понять сильные и слабые стороны использования в торговой стратегии пересечений скользящих средних, что приведет нас к более эффективным методам прогнозирования.
Эксперимент. Пересечения скользящих средних все еще надежны?
Начнем с импорта необходимых нам библиотек Python.
import pandas as pd import pandas_ta as ta import numpy as np import MetaTrader5 as mt5 from datetime import datetime import seaborn as sns import time
Затем введем учетные данные для входа.
account = 123436536 password = "Enter Your Password" server = "Enter Your Broker"
В продолжение попытаемся войти в свой торговый счет.
if(mt5.initialize(login=account,password=password,server=server)): print("Logged in succesfully") else: print("Failed to login")
Авторизация выполнена успешно
Затем определим несколько глобальных переменных.
timeframe = mt5.TIMEFRAME_M15 deviation = 1000 volume = 0 lot_multiple = 10 symbol = "EURUSD"
После этого получим рыночные данные по символу, которым собираемся торговать.
#Setup trading volume symbols = mt5.symbols_get() for index,symbol in enumerate(symbols): if symbol.name == "EURUSD": print(f"{symbol.name} has minimum volume: {symbol.volume_min}") volume = symbol.volume_min * lot_multiple
EURUSD имеет минимальнный объем: 0.01
Теперь приготовимся получить обучающие данные.
#Specify date range of data to be modelled date_start = datetime(2020,1,1) date_end = datetime.now()
Далее определим, насколько далеко в будущее мы хотим заглянуть.
#Define how far ahead we are looking look_ahead = 20
Затем можно перейти к получению рыночных данных от нашего терминала MetaTrader5, а после этого разметить данные. В схеме разметки используем "1" для кодирования движения вверх и "0" для движений вниз.
#Fetch market data market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end)) market_data["time"] = pd.to_datetime(market_data["time"],unit='s') #Add simple moving average technical indicator market_data.ta.sma(length=5,append=True) #Add simple moving average technical indicator market_data.ta.sma(length=50,append=True) #Delete missing rows market_data.dropna(inplace=True) #Add a column for the target market_data["target"] = 0 market_data["close_target"] = 0 #Encoding the target ma_cross_conditions = [ (market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead)), (market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead)) ] #Encoding pattern ma_cross_choices = [ #Fast MA above Slow MA 1, #Fast MA below Slow MA 0 ] price_conditions = [ (market_data["close"] > market_data["close"].shift(-look_ahead)), (market_data["close"] < market_data["close"].shift(-look_ahead)) ] #Encoding pattern price_choices = [ #Price fell 0, #Price rose 1 ] market_data["target"] = np.select(ma_cross_conditions,ma_cross_choices) market_data["close_target"] = np.select(price_conditions,price_choices) #The last rows do not have answers market_data = market_data[:-look_ahead] market_data
Рис. 2. Датафрейм с нашими рыночными данными в текущем виде.
Теперь импортируем нужные нам библиотеки машинного обучения.
#XGBoost from xgboost import XGBClassifier #Catboost from catboost import CatBoostClassifier #Random forest from sklearn.ensemble import RandomForestClassifier #LDA and QDA from sklearn.discriminant_analysis import LinearDiscriminantAnalysis , QuadraticDiscriminantAnalysis #Logistic regression from sklearn.linear_model import LogisticRegression #Neural network from sklearn.neural_network import MLPClassifier #Time series split from sklearn.model_selection import TimeSeriesSplit #Accuracy metrics from sklearn.metrics import accuracy_score #Visualising performance from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt from sklearn.model_selection import learning_curve
Подготовка к выполнению разбиения таймсерии на наборе данных.
#Time series split splits = 10 gap = look_ahead models = ["Logistic Regression","Linear Discriminant Analysis","Quadratic Discriminant Analysis","Random Forest Classifier","XGB Classifier","Cat Boost Classifier","Neural Network Small","Neural Network Large"]
Оценим точность множества различных моделей и сохраним точность, которой достигла каждая модель, в одном датафрейме. Один датафрейм будет хранить точность прогнозирования скользящих средних, а второй датафрейм измеряет точность прямого прогнозирования изменений цен.
error_ma_crossover = pd.DataFrame(index=np.arange(0,splits),columns=models)
error_price = pd.DataFrame(index=np.arange(0,splits),columns=models)
Теперь перейдем к измерению точности каждой модели. Но сначала нам необходимо определить входные данные, которые будут использованы в наших моделях.
predictors = ["open","high","low","close","tick_volume","spread","SMA_5","SMA_50"]
Для измерения точности каждой модели мы обучим их на части набора данных, а затем протестируем на оставшейся части, которая им не была предъявлена во время обучения. Библиотека TimeSeriesSplit разбивает для нас датафрейм, упрощая этот процесс.
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
#Training each model to predict changes in the moving average cross over for i,(train,test) in enumerate(tscv.split(market_data)): model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True) model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"target"] ) error_ma_crossover.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(market_data)): model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True) model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"close_target"] ) error_price.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"close_target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))
Сначала рассмотрим датафрейм, измеряющий точность при прямом прогнозировании изменений цен.
error_price
Рис. 3. Точность при прямом прогнозировании изменения цен.
Прежде чем двигаться дальше, интерпретируем результаты. Первое замечание, которое можно сделать: ни одна из наших моделей не справляется с задачей хорошо, а некоторые при прямом прогнозировании цен продемонстрировали точность менее 50%. Такая низкая эффективность удручает, это значит, что более или менее сопоставимых с этими моделями результатов можно добиться, просто случайно угадывая. Наши модели расположены по возрастанию сложности: слева простая логическая регрессия, а справа глубокие нейросети. Как можно наблюдать, возрастающая сложность моделей не повышает точность прямого прогнозирования цен. Теперь посмотрим, есть ли какие-то улучшения при прогнозировании пересечений скользящих средних.
error_ma_crossover
Рис. 4. Наша точность при прогнозировании пересечений скользящих средних.
Как видно из датафрейма выше, линейный дискриминантный анализ (ЛДА) справился с этой задачей исключительно хорошо. Это самая эффективная (с большим отрывом) из всех рассмотренных нами моделей. Более того, если сравнить повышенную эффективность модели ЛДА с тем, насколько плохо она справилась с первой задачей, можно ясно увидеть, что пересечения скользящих средних могут быть более надежными для прогнозирования, чем прямые изменения цены. В этом случае неоспоримы преимущества прогнозирования пересечений скользящих средних.
Визуализация результатов
Представим наглядно полученные выше результаты.
Рис. 5. Визуализация полученных результатов.
Улучшение алгоритма ЛДА особенно заметно на диаграммах "ящик с усами", что указывает на значительное повышение уровня обученности нашей модели. Кроме того, наблюдалось небольшое, но заметное повышение производительности логистической регрессии. Примечательно, что при прогнозировании пересечений скользящих средних ЛДА последовательно выдавал результаты, тесно сгруппированные в "ящиках с усами", демонстрируя нужную точность и непротиворечивость. Такая сгруппированность свидетельствует о стабильности прогнозов модели, причем стационарные остатки, скорее всего, указывают на надежную взаимосвязь, изученную моделью.
Теперь проанализируем типы ошибок, допущенных нашей моделью. Наша цель — определить, когда она работает лучше: при выявлении движений вверх или вниз, или ее эффективность сбалансирована для обеих задач.
Рис. 6. Матрица несоответствий эффективности нашей ЛДА-модели.
Представленная выше матрица несоответствий отображает слева истинную классификацию, а внизу – прогноз нашей модели. Данные говорят о том, что модель сделала больше ошибок при прогнозировании движений вверх: за все время она ошибочно отнесла движение вверх к движениям вниз в 47% случаев. С другой стороны, модель очень хорошо сработала, прогнозируя движения вниз, и за все время спутала истинное движение вниз с движением вверх лишь в 25% случаев. Поэтому очевидно, что наша модель лучше предсказывает движения вниз, чем движения вверх.
Мы можем визуализировать прогресс в обучении нашей модели по мере того, как она встречает все больше обучающих данных. Приведенный ниже график позволяет оценить, переобучается или недообучается наша модель на предложенных данных. Переобучение происходит, когда модель обучается, извлекая из данных шум, но не улавливая значимые взаимосвязи. На недообучение, с другой стороны, указывает на существенный разрыв между точностью обучения (синяя линия) и точностью валидации (оранжевая линия) на графике. На текущем графике мы наблюдаем заметный, но не слишком большой разрыв между результатами обучения и результатами валидации, что говорит о фактическом переобучении нашей ЛДА-модели на предложенных данных. Однако шкала слева показывает, что переобучение не слишком значительное.
Рис. 7. Кривая обучения для нашего классификатора ЛДА.
С другой стороны, недообучение характеризует низкая точность обучения и валидации. В качестве примера мы включили кривую обучения одной из наших малоэффективных моделей (ебольшой нейросети). На приведенном ниже графике мы наблюдаем нестабильную связь между эффективностью нашей модели и количеством предложенных ей обучающих данных. Первоначально по мере увеличения количества данных эффективность валидации модели снижается, пока не достигнет поворотной точки и не начнет повышаться по мере приближения количества обучающих выборок к 10000. Затем эффективность перестает расти, и наблюдаются лишь незначительные улучшения, несмотря на значительный постоянный рост объема доступных обучающих данных.
Рис. 8. Кривая обучения нашей небольшой нейросети.
Исключение признаков
В большинстве проектов машинного обучения все входные данные редко непосредственно относятся к целевой переменной. Как правило, только часть доступных входных данных подходит для прогнозирования целевых уровней. Исключение неподходящих данных обеспечивает ряд преимуществ, например:
- Повышение эффективности вычислений во время обучения модели и конструирования признаков.
- Повышение точности модели, особенно если удаленные признаки были зашумленными.
Далее нам нужно определить, есть ли между скользящими средними значимая связь. Применим для проверки предполагаемой связи алгоритмы исключения признаков. Если этим алгоритмам не удается удалить скользящие средние из списка входных данных, это означает наличие значимой связи. И наоборот, если они успешно удаляют эти признаки, это говорит об отсутствии значимой связи между скользящими средними и пересечением скользящих средних.
Воспользуемся методом отбора признаков, известным как "пошаговое исключение". Этот метод начинается с подгонки линейной модели с использованием всех доступных входных данных и последующим измерением точности модели. Затем, удаляя по одному признаку, каждый раз отмечается воздействие на точность модели. На каждом шаге удаляется признак, вызывающий наименьшее снижение точности, пока признаки не кончатся. На этом этапе алгоритм автоматически отбирает выявленные им важнейшие признаки, рекомендуя их к использованию.
Стоит упомянуть один существенный недостаток удаления признаков, который заключается в том, что при наличии в наборе данных зашумленных и неважных столбцов могут оказаться неинформативными важные столбцы. Следовательно, алгоритм пошагового исключения может непреднамеренно исключить важный признак, поскольку из-за шума в системе он покажется неинформативным.
Теперь посмотрим, какие столбцы наш компьютер считает важными. Начнем с импорта библиотеки под названием mlxtend, содержащей реализации алгоритма пошагового исключения.
from mlxtend.feature_selection import SequentialFeatureSelector
Затем применим этот алгоритм к нашему набору данных. Обратим особое внимание на 3 из переданных нами параметров:
- "k_features=" указывает алгоритму, сколько столбцов выбрать. Мы можем велеть алгоритму выбирать только столбцы, которые он считает необходимыми, передав интервал от 1 до общего количества столбцов в наборе данных.
- "forward=" указывает алгоритму, следует ему использовать прямое или обратное пошаговое исключение; мы предпочитаем обратное, поэтому для этого параметра установим значение "False".
- "n_jobs=" указывает алгоритму, следует ли параллельно выполнять расчеты; передаем "-1", чтобы разрешить алгоритму использовать все доступные ядра, это значительно сократит затрачиваемое время.
backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(), k_features=(1,market_data.loc[:,predictors].shape[1]), forward=False, verbose=2, scoring="accuracy", cv=5, n_jobs=-1 ).fit(market_data.loc[:,predictors],market_data.loc[:,"target"])
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done 3 out of 8 | elapsed: 8.0s remaining: 13.3s
[Parallel(n_jobs=-1)]: Done 8 out of 8 | elapsed: 8.0s remaining: 0.0s
[Parallel(n_jobs=-1)]: Done 8 out of 8 | elapsed: 8.0s finished
После завершения процесса мы можем с помощью следующей команды получить список входных данных, которые наш алгоритм считает важными.
backward_feature_selector.k_feature_names_
('open', 'high', 'close', 'SMA_5', 'SMA_50')
И как видим, алгоритм пошагового исключения добавил наши 2 скользящих средних в свой список важных признаков. Для нас это отличная новость, поскольку это подтверждает, что наша торговая стратегия не является просто результатом ложной регрессии.
Разработка функций
Теперь, когда мы установили значимую связь между двумя нашими скользящими средними, требующую дальнейших усилий по ее совершенствованию, выясним, могут ли дополнительные технические индикаторы повысить точность прогнозирования пересечений скользящих средних. Именно здесь машинное обучение является более искусством, чем наукой, поскольку сложно предсказать, какие входные данные окажутся полезными. Наш подход будет включать добавление нескольких признаков, которые, по нашему мнению, могут быть полезны, и оценку их фактического влияния.
Мы соберем рыночные данные с прежнего рынка, но на этот раз включим дополнительные индикаторы:
- Moving Average Convergence Divergence (MACD, схождение и расхождение скользящих средних). MACD представляет собой мощный технический индикатор, следующий за трендом, с помощью которого мы можем эффективнее отслеживать изменения основных рыночных режимов.
- Awesome Oscillator (AO, Чудесный осциллятор). Индикатор AO славится очень надежными сигналами на выход, когда какой-либо тренд меняет импульс.
- Aroon. Индикатор Aroon используют для выявления начала новых трендов.
- Chaikins Commodity Index (Товарный индекс Чайкина). Индикатор The Chaikins Commodity Index действует как барометр для измерения перекупленности или перепроданности финансового инструмента.
- Percent Return (Процент доходности). Индикатор Percent Return помогает нам отслеживать рост цены и определять, является ее динамика положительной или отрицательной.
#Fetch market data market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end)) market_data["time"] = pd.to_datetime(market_data["time"],unit='s') #Add simple moving average technical indicator market_data.ta.sma(length=5,append=True) #Add simple moving average technical indicator market_data.ta.sma(length=50,append=True) #Add macd market_data.ta.macd(append=True) #Add awesome oscilator market_data.ta.ao(append=True) #Add aroon market_data.ta.aroon(append=True) #Add chaikins comodity index market_data.ta.cci(append=True) #Add percent return market_data.ta.percent_return(append=True) #Delete missing rows market_data.dropna(inplace=True) #Add the target market_data["target"] = 0 market_data.loc[market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead),"target"] = 1 market_data.loc[market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead),"target"] = 0 #The last rows do not have answers market_data = market_data[:-look_ahead] market_data
Рис. 9. Некоторые из новых дополнительных строк, которые мы добавили к датафрейму.
После отбора признаков наш алгоритм пошагового исключения посчитал важными следующие переменные.
backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(), k_features=(1,market_data.loc[:,predictors].shape[1]), forward=False, verbose=2, scoring="accuracy", cv=5 ).fit(market_data.iloc[:,1:-1],market_data.loc[:,"target"])
backward_feature_selector.k_feature_names_
('close', 'tick_volume', 'spread', 'SMA_5', 'SMA_50', 'MACDh_12_26_9', 'AO_5_34')
Формирование торговой стратегии
Теперь мы готовы применить все выученное в единой торговой стратегии.
Начнем с подгонки нашей модели ко всем доступным нам обучающим данным ,используя только те столбцы, которые мы определили как полезные.
predictors = ['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34'] model = LinearDiscriminantAnalysis() model.fit(market_data.loc[:,predictors],market_data.loc[:,"target"])
Далее зададим функции для извлечения рыночных данных из нашего терминала MetaTrader5.
def get_prices(): start = datetime(2024,6,1) end = datetime.now() data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,start,end)) #Add simple moving average technical indicator data.ta.sma(length=5,append=True) data.ta.sma(length=50,append=True) #Add awesome oscilator data.ta.ao(append=True) #Add macd data.ta.macd(append=True) #Delete missing rows data.dropna(inplace=True) data['time'] = pd.to_datetime(data['time'],unit='s') data.set_index('time',inplace=True) data = data.loc[:,['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']] data = data.iloc[-2:,:] return(data)
Затем нам понадобится другой метод получения прогнозов от ЛДА-модели.
#Get signals LDA model def ai_signal(input_data,_model): #Get a forecast forecast = _model.predict(input_data) return forecast[1]
Теперь можно построить нашу торговую стратегию.
#Now we define the main body of our Python Moving Average Crossover Trading Bot if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal(get_prices(),model) #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 mt5.Buy(symbol,volume) #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 mt5.sell(symbol,volume) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Прогноз AI: Sell
time: 2024-06-25 14:35:37.954923
-------
Рис. 10. Наша торговая стратегия в действии.
Реализация на MQL5
В продолжение используем MQL5 API для разработки с нуля собственного классификатора. Создание пользовательского классификатора на языке MQL5 дает множество преимуществ. Как автор, я твердо убежден, что собственные решения MQL5 обеспечивают непревзойденную гибкость.
Если бы мы экспортировали модель в формат ONNX, нам понадобилась бы отдельная модель для каждого рынка, на котором мы хотим торговать. Кроме того, торговля на разных таймфреймах потребовала бы нескольких моделей ONNX для каждого рынка. Создавая свой классификатор непосредственно на MQL5, мы получаем возможность торговать на любом рынке без таких ограничений.
Создадим новый проект.
Рис. 11. Создание советника для реализации нашей стратегии.
Наша первая задача — задать несколько глобальных переменных для использования в программе.
//Global variables int ma_5,ma_50; double bid, ask; double min_volume; double ma_50_reading[],ma_5_reading[]; int size; double current_prediction; int state = -1; matrix ohlc; vector target; double b_nort = 0; double b_one = 0; double b_two = 0; long min_distance,atr_stop;
Кроме того, у нас будут входные данные, которые конечный пользователь сможет настраивать.
//Inputs int input lot_multiple = 20; int input positions = 2; double input sl_width = 0.4;
Наконец, импортируем торговую библиотеку, которая поможет нам управлять своими позициями.
//Libraries #include <Trade\Trade.mqh> CTrade Trade;
Затем нам понадобится задать вспомогательные функции, которые помогут извлекать данные, помечать обучающие данные, обучать модель и получать от нее прогнозы. Начнем определение функции для извлечения обучающих данных и обозначения цели для нашего классификатора.
//+----------------------------------------------------------------------+ //|This function is responsible for getting our training data ready | //+----------------------------------------------------------------------+ void get_training_data(void) { //How much data are we going to use? size = 100; //Copy price data ohlc.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,size); //Get indicator data ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE); ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE); CopyBuffer(ma_50,0,0,size,ma_50_reading); CopyBuffer(ma_5,0,0,size,ma_5_reading); ArraySetAsSeries(ma_50_reading,true); ArraySetAsSeries(ma_5_reading,true); //Label the target target = vector::Zeros(size); for(int i = 0; i < size; i++) { if(ma_5_reading[i] > ma_50_reading[i]) { target[i] = 1; } else if(ma_5_reading[i] < ma_50_reading[i]) { target[i] = 0; } } //Feedback Print("Done getting training data."); }
Наша модель использует для прогнозирования три коэффициента. Этим коэффициентам нужна оптимизация. Воспользуемся рассчитанным на начинающих уравнением обновления для настройки коэффициентов. Измеряя ошибку в прогнозах нашей модели, будем итеративно изменять коэффициенты, чтобы свести ошибку к минимуму и повысить точность системы. Но прежде чем мы сможем начать оптимизацию модели, необходимо сначала определить, как наша модель делает прогнозы.
//+----------------------------------------------------------------------+ //|This function is responsible for making predictions using our model | //+----------------------------------------------------------------------+ double model_predict(double input_one,double input_two) { //We simply return the probability that the shorter moving average will rise above the slower moving average double prediction = 1 / (1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two)))); return prediction; }
Теперь, когда наша модель научилась прогнозировать, можно измерить ошибку в этих прогнозах и начать процесс оптимизации. Первоначально все три коэффициента будут установлены на 0. Теперь будем итеративно и небольшими шагами настраивать коэффициенты для минимизации общей ошибки в системе.
//+----------------------------------------------------------------------+ //|This function is responsible for training our model | //+----------------------------------------------------------------------+ bool train_model(void) { //Update the coefficients double learning_rate = 0.3; for(int i = 0; i < size; i++) { //Get a prediction from the model current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]); //Update each coefficient b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (1 - current_prediction) * 1; b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_5_reading[i]; b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_50_reading[i]; Print(current_prediction); } //Show updated coefficient values Print("Updated coefficient values"); Print(b_nort); Print(b_one); Print(b_two); return(true); }
После успешного обучения модели было бы полезно иметь функцию, извлекающую из нашей модели прогнозы. Эти прогнозы послужат нам торговыми сигналами. Напомним, что прогноз 1 является сигналом на покупку, указывая, что наша модель ожидает подъема скользящего среднего с более коротким периодом выше уровня скользящего среднего с более длинным периодом. И наоборот, прогноз 0 является сигналом на покупку, указывая, что наша модель ожидает падения скользящего среднего с более коротким периодом ниже уровня скользящего среднего с более длинным периодом.
//Get the model's current forecast void current_forecast() { //Get indicator data ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE); ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE); CopyBuffer(ma_50,0,0,1,ma_50_reading); CopyBuffer(ma_5,0,0,1,ma_5_reading); //Get model forecast model_predict(ma_5_reading[0],ma_50_reading[0]); interpret_forecast(); }
Мы хотим, чтобы наш советник действовал, исходя из прогнозов модели. Поэтому напишем функцию для интерпретации прогноза модели и выполнения соответствующих действий: покупать при прогнозе 1 и продавать при прогнозе 0.
//+----------------------------------------------------------------------+ //|This function is responsible for taking action on our model's forecast| //+----------------------------------------------------------------------+ void interpret_forecast(void) { if(current_prediction > 0.5) { state = 1; Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI"); } if(current_prediction < 0.5) { state = 0; Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,0,0,"Volatitlity Doctor AI"); } }
Теперь, когда наше приложение может обучаться на данных, делать прогнозы и действовать на их основе, нужно создать дополнительные функции для управления открытыми позициями. В частности, нам нужно, чтобы наша программа добавляла к каждой позиции скользящие стоп-лоссы и тейк-профиты для управления уровнями риска. Мы не хотим открывать позиции без заданного лимита риска. Большинство торговых стратегий рекомендуют фиксировать размер стоп-лосса на 100 пипсах, но нам нужно убедиться, что уровни стоп-лосса и тейк-профита устанавливаются динамически, исходя из текущей волатильности рынка. Поэтому используем индикатор среднего истинного диапазона (Average True Range, ATR) для расчета, насколько широко или узко необходимо устанавливать наши стоп-уровни. Для определения этих уровне применим несколько индикаторов ATR.
//+----------------------------------------------------------------------+ //|This function is responsible for calculating our SL & TP values | //+----------------------------------------------------------------------+ void CheckAtrStop() { //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 - ((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 + (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 + ((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 - (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); } } } } }
Затем нам понадобится функция, которую мы будем вызывать, как только захотим рассчитать новые значения стоп-лосса и тейк-профита.
//+------------------------------------------------------------------+ //|This function is responsible for updating our SL&TP values | //+------------------------------------------------------------------+ void ManageTrade() { CheckAtrStop(); }
Теперь, когда мы определили свои вспомогательные функции, можно начать вызывать их наших обработчиках событий. При первой загрузке нашей программы нужно запустить процесс обучения. Поэтому будем вызывать вспомогательную функцию, ответственную за обучение эксперта, внутри обработчика событий OnInit.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //Define important global variables min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //Train the model get_training_data(); if(train_model()) { interpret_forecast(); } return(INIT_SUCCEEDED); }
После обучения модели мы сможем приступить к реальной торговле.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //Get updates bid and ask prices bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(PositionsTotal() == 0) { current_forecast(); } if(PositionsTotal() > 0) { ManageTrade(); } }
Рис. 12. Пример выходных данных нашего советника.
Рис. 13. Наш советник в действии.
Заключение
В этой статье продемонстрировано, что с точки зрения вычислений нашей модели проще прогнозировать пересечения скользящих средних, чем непосредственно изменение цены.
Как и во всех своих статьях, я предпочитаю приводить технические пояснения в конце, сначала показывая сам принцип. Для такой точки зрения есть несколько возможных причин. Одна из возможных причин — тот факт, что в зависимости от выбранных периодов скользящие средние могут не пересекаться так часто, как того требуют хаотично меняющие направление цены. Иными словами, за ближайшие два часа цена может успеть подняться, затем опуститься или сменить направление дважды. Однако скользящие средние могут за то же время не пересечься вовсе. Поэтому может быть легче прогнозировать пересечения скользящих средних, поскольку они не так быстро меняют направление, как сама цена. Это лишь одно из возможных объяснений. Вы можете самостоятельно изучить вопрос, сделать собственные выводы и поделиться ими в комментариях к этой статье.
Затем для исключения признаков мы применили обратный отбор — метод, при котором линейная модель итеративно обучается с удалением на каждом этапе одного признака в зависимости от его влияния на ее точность. Такой подход помогает определять и сохранять наиболее информативные признаки, хотя и склонен исключать важные признаки, которые могут показаться неинформативными из-за шума.
Подтвердив значимую связь между двумя скользящими средними, мы изучили возможность интеграции дополнительных технических индикаторов: MACD, Awesome Oscillator, Aroon, Chaikins Commodity Index и Percent Return. Применение этих индикаторов направлено на повышение нашей способности точно прогнозировать пересечения скользящих средних. Однако выбор индикаторов остается своего рода искусством в связи с непредсказуемостью их воздействия на эффективность модели.
В целом наш подход сочетает эмпирическую валидацию и стратегический отбор признаков для количественного подтверждения реальной прогнозируемости пересечений скользящих средних, чтобы никакие усилия, потраченные на попытки улучшить эту торговую стратегию, точно не оказались пустой тратой времени.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15160





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Любая помощь с этой ошибкой
Пакет 'sklearn' PyPI устарел, используйте 'scikit-learn'
а не 'sklearn' для команд pip.
Любая помощь с этой ошибкой
Пакет 'sklearn' PyPI устарел, используйте 'scikit-learn'
а не 'sklearn' для команд pip.