English 中文 Español Deutsch 日本語 Português
preview
Переосмысливаем классические стратегии на языке Python: Пересечения скользящих средних

Переосмысливаем классические стратегии на языке Python: Пересечения скользящих средних

MetaTrader 5Торговые системы | 20 декабря 2024, 11:29
815 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Введение

Многие из сегодняшних торговых стратегий были созданы в совершенно иных рыночных условиях. Решающее значение имеет оценка их актуальности на современных рынках, где преобладают алгоритмы. В данной статье рассмотрена стратегия пересечений скользящих средних для оценки ее эффективности в современной финансовой среде.

В статье будут рассмотрены следующие вопросы:

  • Существуют ли количественные данные в пользу дальнейшего использования стратегии?
  • В чём преимущества этой стратегии по сравнению с прямым ценовым анализом?
  • Сохраняет ли эта стратегия свою эффективность в современном алготрейдинге?
  • Существуют ли другие индикаторы, которые могут повысить точность стратегии?
  • Можно ли эффективно использовать ИИ, чтобы прогнозировать пересечения скользящих средних до их появления?

Метод применения скользящих средних широко изучался в последние десятилетия. Фундаментальная концепция использования этих индикаторов для выявления трендов и торговых сигналов стала основой технического анализа, хотя ее точное происхождение остается неясным.

Обычно стратегия пересечения рассматривает два скользящих средних с разным периодом, но важнейшее условие — чтобы один период был длиннее другого. Когда скользящее среднее с более коротким периодом пересекает скользящее среднее с менее коротким периодом вверх, это сигнализирует о потенциально бычьем тренде, а при пересечении вниз — о медвежьем.

Специалисты по техническому анализу десятилетиями используют эту стратегию для определения точек входа и выхода, оценки настроения рынка и в других целях. Для определения текущей эффективности стратегии подвергнем ее современному количественному испытанию. Ниже подробно описан наш подход.


Пересечения скользящих средних

Рис. 1. Пример пересечений скользящих средних в приложении к валютной паре CADJPY.

Общие сведения

Мы отправимся в увлекательное путешествие, в котором соединим своей терминал MetaTrader5 со средой Python. Для начала запросим данные M15 по валютной паре EURUSD с 1 января 2020 года по 25 июня 2024 года. Такой обширный набор данных даст нам полное представление о поведении рынка в последнее время.

Нашим следующим шагом будет установка двух целей. Первая измерит точность прогнозирования непосредственного изменения цен и послужит нашей отправной точкой. Этот ориентир поможет нам сравнить, насколько хорошо мы прогнозируем пересечения скользящих средних. Попутно поищем дополнительные технические индикаторы для повышения точности. Наконец, мы попросим полученные компьютерные модели определить ключевые переменные для прогнозирования пересечений скользящих средних. Если модель не отдает приоритет двум нашим скользящим средним, это может означать, что наши первоначальные предположения были неверными.

Прежде чем погрузиться в цифры, рассмотрим возможные результаты:

  1. Преимущество прямого прогнозирования цен: если прямое прогнозирование изменений цен обеспечивает точность не ниже точности, которую дают пересечения скользящих средних, это свидетельствует об отсутствии каких-либо преимуществ их использования, ставя под сомнение обоснованность стратегии.

  2. Преимущество прогнозирования пересечений: если при прогнозировании пересечений скользящих средних мы достигнем более высокой точности, это побудило бы нас искать больше данных для дальнейшего улучшения своих прогнозов, подчеркивая потенциальную ценность стратегии.

  3. Неактуальность скользящих средних: если наши модели не идентифицируют одно из скользящих средних как решающее для прогнозирования пересечений, это означает, что другие переменные могут быть более значимыми, и говорит об отсутствии предполагаемой связи между двумя скользящими средними.

  4. Актуальность скользящих средних: если одно или оба скользящих средних помечены как важные для прогнозирования пересечений, это подтверждает значимую связь между ними, что позволяет строить надежные модели для обоснованных прогнозов.

С помощью этого анализа нам будет проще понять сильные и слабые стороны использования в торговой стратегии пересечений скользящих средних, что приведет нас к более эффективным методам прогнозирования.


Эксперимент. Пересечения скользящих средних все еще надежны?

Начнем с импорта необходимых нам библиотек 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. Кривая обучения нашей небольшой нейросети.

Исключение признаков

В большинстве проектов машинного обучения все входные данные редко непосредственно относятся к целевой переменной. Как правило, только часть доступных входных данных подходит для прогнозирования целевых уровней. Исключение неподходящих данных обеспечивает ряд преимуществ, например:

  1. Повышение эффективности вычислений во время обучения модели и конструирования признаков.
  2. Повышение точности модели, особенно если удаленные признаки были зашумленными.

Далее нам нужно определить, есть ли между скользящими средними значимая связь. Применим для проверки предполагаемой связи алгоритмы исключения признаков. Если этим алгоритмам не удается удалить скользящие средние из списка входных данных, это означает наличие значимой связи. И наоборот, если они успешно удаляют эти признаки, это говорит об отсутствии значимой связи между скользящими средними и пересечением скользящих средних.

Воспользуемся методом отбора признаков, известным как "пошаговое исключение". Этот метод начинается с подгонки линейной модели с использованием всех доступных входных данных и последующим измерением точности модели. Затем, удаляя по одному признаку, каждый раз отмечается воздействие на точность модели. На каждом шаге удаляется признак, вызывающий наименьшее снижение точности, пока признаки не кончатся. На этом этапе алгоритм автоматически отбирает выявленные им важнейшие признаки, рекомендуя их к использованию.

Стоит упомянуть один существенный недостаток удаления признаков, который заключается в том, что при наличии в наборе данных зашумленных и неважных столбцов могут оказаться неинформативными важные столбцы. Следовательно, алгоритм пошагового исключения может непреднамеренно исключить важный признак, поскольку из-за шума в системе он покажется неинформативным.

Теперь посмотрим, какие столбцы наш компьютер считает важными. Начнем с импорта библиотеки под названием mlxtend, содержащей реализации алгоритма пошагового исключения.

from mlxtend.feature_selection import SequentialFeatureSelector

Затем применим этот алгоритм к нашему набору данных. Обратим особое внимание на 3 из переданных нами параметров:

  1. "k_features=" указывает алгоритму, сколько столбцов выбрать. Мы можем велеть алгоритму выбирать только столбцы, которые он считает необходимыми, передав интервал от 1 до общего количества столбцов в наборе данных.
  2. "forward=" указывает алгоритму, следует ему использовать прямое или обратное пошаговое исключение; мы предпочитаем обратное, поэтому для этого параметра установим значение "False".
  3. "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 скользящих средних в свой список важных признаков. Для нас это отличная новость, поскольку это подтверждает, что наша торговая стратегия не является просто результатом ложной регрессии.

Разработка функций

Теперь, когда мы установили значимую связь между двумя нашими скользящими средними, требующую дальнейших усилий по ее совершенствованию, выясним, могут ли дополнительные технические индикаторы повысить точность прогнозирования пересечений скользящих средних. Именно здесь машинное обучение является более искусством, чем наукой, поскольку сложно предсказать, какие входные данные окажутся полезными. Наш подход будет включать добавление нескольких признаков, которые, по нашему мнению, могут быть полезны, и оценку их фактического влияния.

Мы соберем рыночные данные с прежнего рынка, но на этот раз включим дополнительные индикаторы:

  1. Moving Average Convergence Divergence (MACD, схождение и расхождение скользящих средних). MACD представляет собой мощный технический индикатор, следующий за трендом, с помощью которого мы можем эффективнее отслеживать изменения основных рыночных режимов.
  2. Awesome Oscillator (AO, Чудесный осциллятор). Индикатор AO славится очень надежными сигналами на выход, когда какой-либо тренд меняет импульс.
  3. Aroon. Индикатор Aroon используют для выявления начала новых трендов.
  4. Chaikins Commodity Index (Товарный индекс Чайкина). Индикатор The Chaikins Commodity Index действует как барометр для измерения перекупленности или перепроданности финансового инструмента.
  5. 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, мы получаем возможность торговать на любом рынке без таких ограничений.

Создадим новый проект.

Советник на 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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Robert Mark Salmon
Robert Mark Salmon | 15 июл. 2024 в 09:10

Любая помощь с этой ошибкой


Пакет 'sklearn' PyPI устарел, используйте 'scikit-learn'

а не 'sklearn' для команд pip.

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 15 июл. 2024 в 13:08
Robert Mark Salmon #:

Любая помощь с этой ошибкой


Пакет 'sklearn' PyPI устарел, используйте 'scikit-learn'

а не 'sklearn' для команд pip.

Забавно, что я как раз устанавливал scikit-learn в виртуальной среде, команда 'scikit-learn' - это то, что нужно, я выполнил эту команду всего несколько минут назад:



Pip install scikit learn

sportoman
sportoman | 6 апр. 2025 в 07:09
Hello! I have a question again: how are the indicator settings chosen? The optimal period, for example, will be different for each instrument. What doesn't work with MA_5 and MA_50 on one instrument may work perfectly on another.
Возможности Мастера MQL5, которые вам нужно знать (Часть 26): Скользящие средние и показатель Херста Возможности Мастера MQL5, которые вам нужно знать (Часть 26): Скользящие средние и показатель Херста
Показатель Херста — это мера того, насколько сильно временной ряд автокоррелирует в долгосрочной перспективе. Предполагается, что он отражает долгосрочные свойства временного ряда и поэтому имеет определенный вес в анализе временных рядов даже за пределами экономических/финансовых временных рядов. Однако мы сосредоточимся на его потенциальной пользе для трейдеров, изучив, как этот показатель можно объединить со скользящими средними для формирования потенциально надежного сигнала.
Нейросети в трейдинге: Гибридный торговый фреймворк с предиктивным кодированием (Окончание) Нейросети в трейдинге: Гибридный торговый фреймворк с предиктивным кодированием (Окончание)
Продолжаем рассмотрение гибридной торговой системы StockFormer, которая объединяет предиктивное кодирование и алгоритмы обучения с подкреплением для анализа финансовых временных рядов. Основой системы служат три ветви Transformer с механизмом Diversified Multi-Head Attention (DMH-Attn), позволяющим выявлять сложные паттерны и взаимосвязи между активами. Ранее мы познакомились с теоретическими аспектами фреймворка и реализовали механизмы DMH-Attn, а сегодня поговорим об архитектуре моделей и их обучении.
Разработка системы репликации (Часть 57): Анализируем тестовый сервис Разработка системы репликации (Часть 57): Анализируем тестовый сервис
И заключительный момент: хотя он и не включен в эту статью, я объясню код сервиса, который будет использоваться в следующей, поскольку мы будем использовать этот же код в качестве трамплина для того, что мы на самом деле разрабатываем. Так что, наберитесь терпения и ждите следующей статьи, ведь с каждым днем все становится еще интереснее.
Алгоритм черной дыры — Black Hole Algorithm (BHA) Алгоритм черной дыры — Black Hole Algorithm (BHA)
Алгоритм черной дыры (Black Hole Algorithm, BHA) использует принципы гравитации черных дыр для оптимизации решений. В статье мы рассмотрим, как BHA притягивает лучшие решения, избегая локальных экстремумов, и почему этот алгоритм стал мощным инструментом для решения сложных задач. Узнайте, как простые идеи могут привести к впечатляющим результатам в мире оптимизации.