Преодоление ограничений машинного обучения (Часть 2): Отсутствие воспроизводимости
Я часто получаю ободряющие отзывы от наших читателей, но повторяющаяся тема в личных сообщениях и комментариях — это трудности, с которыми некоторые сталкиваются при попытке воспроизвести результаты, представленные в наших статьях. Поначалу это меня озадачило, но после некоторых размышлений я нашел вероятное объяснение.
Глобальный финансовый рынок функционирует как масштабная и децентрализованная сеть. В мире существует множество брокеров, и ежедневно регистрируются новые, однако нет единого международного органа, который бы регулировал деятельность этих брокеров или координировал их ценовые потоки. Каждый брокер может свободно получать цены из предпочитаемых им собственных источников или сервисов данных, таких как Reuters.
Следовательно, если сравнить динамику EURUSD у двух брокеров — назовем их Брокер A и Брокер Б, — можно обнаружить, что одна и та же валютная пара движется в противоположных направлениях в один и тот же момент времени. Например, Брокер А может сообщить о росте пары EURUSD на 0,12% за день, в то время как Брокер Б зафиксирует снижение на -0,65% за тот же день.
Суть проблемы: расхождения в данных между брокерами
Для этой дискуссии я случайным образом выбрал двух брокеров, которыми лично пользуюсь для самостоятельной торговли. В соответствии с правилами нашего сообщества, которые запрещают рекламу брокеров, их имена удалены и заменены на «Брокер А» и «Брокер Б».
Используя библиотеку Python для MetaTrader 5, я запросил у обоих брокеров ежедневные исторические данные по паре EURUSD за четыре года. При проверке я заметил, что временные метки не совпадают: данные одного брокера охватывают период с сентября 2019 года, а данные другого — только с августа 2020 года. Тем не менее, оба сервиса вернули ровно 1460 рядов ежедневных данных, корректно удовлетворив наш запрос.
Учитывая децентрализованный характер брокерских компаний, вполне ожидаемо, что их рабочие часовые пояса могут различаться. Однако менее очевидны последствия перехода на летнее время, признанных государственных праздников и других незначительных расхождений, которые могут еще больше исказить выравнивание временных меток.
Затем мы рассчитали 10-дневную доходность по паре EURUSD на обоих брокерах и обнаружили, что числовые характеристики символа EURUSD не совпадают друг с другом. Средняя доходность по паре EURUSD за 10 дней с Брокером A составила 0,000267, а с Брокером Б средняя доходность составила -0,000352. Это означает разницу примерно в 232% в ожидаемой доходности одного и того же базового актива.
Ситуация усугубляется тем, что ожидаемая доходность от Брокера А, по-видимому, сопряжена с риском, на 21% большим, чем ожидаемая доходность от Брокера Б. На это нас натолкнул тот факт, что разница в доходности между Брокерами выросла на одинаковую величину — 21%.
Примечание для начинающих: Следует донести до читателя, что колебания доходности считаются финансовым риском. Любой вводный учебник по теории финансового портфеля может продемонстрировать это читателям, которые, возможно, ранее не были знакомы с этим принципом.
В статистике мы можем выяснить, движутся ли две переменные синхронно или независимо друг от друга, измерив уровень их корреляции. Стандартизированные показатели корреляции варьируются от 1 до -1. Показатель 1 означает, что переменные движутся строго в одном направлении, а показатель -1 означает, что переменные движутся строго в противоположных направлениях. Когда мы сравнивали Коэффициент корреляции Пирсона между двумя брокерами, я, автор статьи, честно говоря, ожидал, что коэффициенты корреляции будут близки к 1. Однако данные показали уровень корреляции всего 0,41.
Это говорит о том, что любые предположения о том, что ценовые уровни символа EURUSD будут двигаться согласованно у разных брокеров, по всей видимости, математически необоснованны. Напротив, результаты нашего теста показывают, что более чем в половине случаев рынок EURUSD движется в разных направлениях у разных брокеров.
Другие важные числовые характеристики котировок двух брокеров лишь усугубили глубину проблем, на которые обращается внимание читателя в этой статье. В нашем предыдущем обсуждении ограничений ИИ мы показали читателю некоторые подводные камни, связанные с метриками, обычно используемыми для построения регрессионных моделей, такими как RMSE. Читатель может найти статью, ссылка на которую находится здесь.
Вкратце, мы советовали читателю не рассматривать RMSE как самостоятельный показатель, а интерпретировать его с осторожностью, сравнивая отношение эффективности модели, которую вы собираетесь использовать (остаточная сумма квадратов, RSS), с ошибкой, возникающей при использовании простой модели, которая всегда прогнозирует среднюю доходность рынка (полная сумма квадратов, TSS). Суть в том, что читатели могут удивиться, насколько сложно превзойти более простую модель. Отношение RSS к TSS показывает, насколько эффективно мы превосходим простую модель.
Можно было бы ожидать, что для одного и того же символа это соотношение останется практически постоянным, даже при работе с разными брокерами. Однако наша способность превзойти модель, прогнозирующую среднюю доходность рынка, улучшилась на 7% просто за счет смены брокера. Это означает, что прогнозировать доходность EURUSD за 10 дней примерно на 7% проще непосредственно с помощью Брокера B, чем с помощью Брокера A!
Статистики часто сравнивают центр распределения с его стандартным отклонением, чтобы узнать больше о характеристиках хвоста данного распределения. Если переосмыслить эту операцию и применить ее к 10-дневной доходности на паре EURUSD, мы получим численный метод для сравнения того, какой брокер, как правило, обеспечивает сверхвысокую доходность. Исходя из этой логики, 10-дневная доходность по паре EURUSD, полученная Брокером Б, оказалась завышена на 147%.
К настоящему моменту проблема, с которой мы сталкиваемся, должна быть ясна: важные числовые характеристики одного и того же символа не гарантируют единообразия у разных брокеров. В результате прибыльность любой данной торговой стратегии не всегда может быть надежно воспроизведена между брокерами.
Торговые стратегии, интегрирующие модели искусственного интеллекта, созданные с использованием ONNX API или даже изначально на MQL5, могут постоянно не соответствовать ожиданиям инвесторов, если только дополнительное время, необходимое для уникальной адаптации искусственного интеллекта к предполагаемому брокеру, не станет широко распространенной практикой. Хотя эта работа отнимает много времени, она, безусловно, имеет решающее значение.
Пока вы читаете эту статью, мы будем шаг за шагом воссоздавать производственный цикл, которому, возможно, следует большинство разработчиков MQL5. Мы хотим использовать эту статью, чтобы проиллюстрировать, что когда разработчик создает и оптимизирует свое приложение, используя своего частного брокера, в нашем случае Брокера Б, но его клиент развертывает это приложение у другого брокера, Брокера A, то проблема не так уж далека от разработчика и его клиента. Любой разработчик, следующий такому производственному циклу, скорее всего, получит неоднозначные отзывы о своей продукции.
Чтобы избежать такого неудовлетворительного уровня эффективности, разработчикам MQL5, которые хотят предоставлять надежные услуги, возможно, придется осознать, что их клиентам может быть выгоднее всего использовать стратегии и приложения, адаптированные к конкретным брокерам, для обеспечения безопасности потребителей, которых мы хотим обслуживать на Marketplace.
Приступаем
Сначала нужно импортировать наши стандартные цифровые библиотеки.
#Load our libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import MetaTrader5 as mt5
Определим, на каком таймфрейме и валютной паре мы сконцентрируемся, а также сколько рядов данных нам понадобится.
#Let us define certain constants TF = mt5.TIMEFRAME_D1 DATA = (365 * 4) START = 1 PAIR = "EURUSD"
Запустите свой терминал.
#Log in to the terminal if mt5.initialize(): print('Logged in successfully') else: print('Failed To Log In')
Вход выполнен успешно.
Проанализируем сложность прогнозирования пары EURUSD у брокера A.
EURUSD_BROKER_A = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA)) #Store the data we retrieved from broker A EURUSD_BROKER_A.to_csv("EURUSD BROKER A.csv")
Теперь повторим ту же процедуру для брокера B.
#I have manually changed brokers using the MT5 terminal, you should also do the same on your side EURUSD_BROKER_B = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA)) #Store the data we retrieved from broker B EURUSD_BROKER_B.to_csv("EURUSD BROKER B.csv")
Отлично! Теперь, когда мы собрали исторические данные по паре EURUSD от обоих брокеров, давайте начнем изучать эмпирические свойства этих наборов данных, чтобы увидеть, согласуется ли символ EURUSD у разных брокеров. Нам нужно определить, как далеко в будущее мы будем строить прогнозы.
#Our forecasting horizon HORIZON = 10
Считываем в обоих наборах данных.
EURUSD_BROKER_A = pd.read_csv("EURUSD BROKER A.csv") EURUSD_BROKER_B = pd.read_csv("EURUSD BROKER B.csv")
В наборах данных столбец времени в настоящее время записывается в секундах. Мы бы предпочли иметь удобочитаемые столбцы времени в формате дата-месяц-год. Давайте разработаем метод, позволяющий это сделать.
def format_data(f_data): #First make a copy of the data, so we always preserve the original data f_data_copy = f_data.copy() #Format the time correctly, form seconds to human readable formats f_data_copy['time'] = pd.to_datetime(f_data_copy['time'],unit='s') return(f_data_copy)
Отформатируем наши наборы данных.
A = format_data(EURUSD_BROKER_A) B = format_data(EURUSD_BROKER_B)
Переименуем все столбцы соответствующим образом, чтобы к названию каждого столбца добавлялась буква брокера, предоставившего нам данные. Все столбцы от Брокера A или Б будут заканчиваться на A или Б соответственно. Давайте теперь внимательно изучим исторические данные по паре EURUSD, которые мы получили от обоих брокеров. Обратите внимание на тот факт, что оба набора содержат ровно 1 460 строк ежедневных данных, что означает, что каждый брокер корректно вернул ежедневные данные ровно за 4 года. Какие еще различия может заметить читатель? Вы обратили внимание на тиковый объем?
# Rename all columns (except the join key) B = B.rename(columns=lambda col: col + ' B' if col != 'id' else col) A = A.rename(columns=lambda col: col + ' A' if col != 'id' else col)

Рис. 1: Исторические ежедневные данные по паре EURUSD, которые мы получили от Брокера A, с временными метками за сентябрь 2019 года

Рис. 2: Исторические дневные данные по паре EURUSD, которые мы получили от Брокера Б, не совпадают с временными метками на рис. 1, но оба они относятся ровно к 4 годам
Давайте теперь объединим эти два набора данных.
combined = pd.concat([A,B],axis=1) Создадим столбец, полный только 0.
combined['Null'] = 0
Определите входные данные.
inputs = ['open A','high A','low A','close A','tick_volume A','spread A','open B','high B','low B','close B','tick_volume B','spread B']
Рассчитаем доходность по паре EURUSD за 10 дней.
#Label the data combined['A Target'] = combined['close A'].shift(-HORIZON) - combined['close A'] combined['B Target'] = combined['close B'].shift(-HORIZON) - combined['close B'] #Drop the last HORIZON rows of data combined = combined.iloc[:-HORIZON,:]
Тиковый объем сообщает нам, сколько изменений цен мы периодически наблюдаем. Периоды интенсивной торговли будут отмечаться высоким тиковым объемом, что указывает на высокую рыночную активность, а обратное означает, что рынок был относительно спокойным с небольшой активностью. У Брокера А, судя по данным о тиковом объеме, наблюдается долгосрочный восходящий тренд, что говорит о том, что открытый интерес инвесторов, по-видимому, растет с течением времени. На графике иногда наблюдаются большие скачки, которые могут соответствовать особенно напряженным периодам, когда открытый интерес к паре EURUSD приближается к максимуму.
plt.title('Broker A Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume A'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid() 
Рис. 3: Тиковый объем по паре EURUSD, полученный от Брокера A
При сравнении тикового объема, представленного нам Брокером Б на рис. 4, с рис. 3, отчетливо видны большие различия в заявленных уровнях активности. У Брокера Б практически нет тренда в тиковом объеме по сравнению с Брокером A. Рис. 4 плотный, со случайными всплесками, которые не выглядят такими периодичными, как всплески, которые мы наблюдали на рис. 3.
plt.title('Broker B Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume B'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid() 
Рис. 4: Ежедневный тиковый объем по паре EURUSD, полученный от Брокера Б
Когда мы рассматриваем среднюю доходность, на которую может рассчитывать инвестор, держа EURUSD у каждого брокера, узнаем, что оба брокера предлагают разные варианты одного и того же символа. В противном случае, если эти два брокера предлагали нам идентичные версии одного и того же символа, то разве у нас не должны быть одинаковые уровни ожидаемой доходности?
#What's the average 10-Day EURUSD return from both brokers delta_return = str(((combined.iloc[:,-2:].mean()[0]-combined.iloc[:,-2:].mean()[1]) / combined.iloc[:,-2:].mean()[0]) * 100) t = 'The Expected 10-Day EURUSD Return Differes by ' + delta_return[:5] + '% Between Our Brokers' sns.barplot(combined.iloc[:,-2:].mean(),color='black') plt.axhline(0,color='grey',linestyle='--') plt.title(t) plt.ylabel('Return')

Рис. 5: Средняя рыночная доходность между брокерами находится по разные стороны от 0
Давайте построим график доходности, генерируемой двумя рынками, наложив их друг на друга. Я буду масштабировать каждую из доходностей так, чтобы обе линии были центрированы на 0, а смещение от 0 показывало, на сколько стандартных отклонений мы отклонились от среднерыночной доходности. Мы сразу видим, что есть много моментов, когда две линии находятся по разные стороны от линии 0, и все же есть другие моменты, когда две линии следуют друг за другом. Напомним, что, говоря простым языком, мы склонны предполагать, что эти две линии всегда следуют друг за другом, но рис. 6 показывает, что это верно лишь в некоторых случаях.
plt.plot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red') plt.plot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black') plt.grid() plt.axhline(0,color='black',linestyle='--') plt.ylabel('Std. Deviations From Expected 10-Day EURUSD Return') plt.xlabel('Historical Days') plt.title('EURUSD Returns from Different Brokers May Not Always Allign') plt.legend(['Broker A','Broker B'])

Рис. 6: Визуализация 10-дневной доходности по паре EURUSD, генерируемой двумя разными брокерами
Сравнение величины разницы в доходности, предлагаемой брокерами, позволяет оценить, какой брокер более рискованный, а какой брокер предлагает нам более надежную доходность. По этому показателю версия EURUSD, используемая Брокером A, несет в себе больший риск, связанный с ее доходностью, по сравнению с версией Брокера Б.
#The variance of returns is not the same across both brokers, broker A is riskier delta_var = str(((combined.iloc[:,-2:].var()[0]-combined.iloc[:,-2:].var()[1]) / combined.iloc[:,-2:].var()[0]) * 100) t = 'Broker A EURUSD Returns Appear to Carry '+ delta_var[:5]+'% Additional Risk.' sns.barplot(combined.iloc[:,-2:].var(),color='black') plt.axhline(np.min(combined.iloc[:,-2:].var()),color='red',linestyle=':') plt.title(t) plt.ylabel('Vriance of Returns')

Рис. 7: Доходность Брокера А сопряжена с риском на 21% большим, чем доходность Брокера В. На данный момент вы все еще считаете эти инструменты "одинаковыми"?
Когда мы обращаем наше внимание на самую крупную просадку, зафиксированную обоими брокерами, нам по-прежнему не удается получить последовательных наблюдений. Самая большая просадка, продемонстрированная на обоих рынках, отличалась примерно на 37% у наших двух брокеров. Все это, по-видимому, наводит на мысль о том, что Брокер Б разумно защищает своих клиентов от волатильности рынка EURUSD, предлагая более ограниченную картину валютного рынка.
#Broker A also demonstrated the largest drawdown ever in our 4 year sample window delta = (((combined.iloc[:,-2:].min()[0]-combined.iloc[:,-2:].min()[1]) / combined.iloc[:,-2:].min()[0]) *100) delta_s = str(delta) t = 'The Largest Negative 10-Day EURUSD Return Grew By: ' + delta_s[:5] + ' %' sns.barplot(combined.iloc[:,-2:].min(),color='black') plt.axhline(np.max(combined.iloc[:,-2:].min()),color='red',linestyle=':') plt.title(t) plt.ylabel('Return')

Рис. 8: Брокер А продемонстрировал самую большую просадку доходности на 36,79%, значительно опередив самую большую просадку у Брокера Б
Наложение распределения доходности по паре EURUSD за 10 дней, сгенерированной обоими брокерами, показывает, что оба брокера на самом деле не предлагают одинаковый взгляд на рынок. Как мы объясняли во введении к нашему обсуждению, каждый брокер волен получать информацию о своих ценах из любого выбранного им источника. Эта децентрализованная схема ценообразования означает, что каждый брокер может предлагать произвольно разные точки зрения на любой конкретный рынок.
sns.histplot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black') sns.histplot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red') plt.xlabel('Std. Deviations From The Expected Return') plt.ylabel('Frequency') plt.title('Comparing The Distribution of 10-Day EURUSD Returns Between 2 Brokers') plt.grid() plt.legend(['Broker A','Broker B'])

Рис. 9: Сравнение распределения доходов, генерируемых двумя рынками
Кроме того, при анализе уровней корреляции между брокерами, мы обнаруживаем, что рыночные цены слабо коррелируют друг с другом. Это означает, что, как мы уже говорили ранее, более чем в половине случаев цены между этими двумя конкретными брокерами могут двигаться в противоположных направлениях.
sns.heatmap(combined.loc[:,inputs].corr(),annot=True)

Рис. 10: Визуализация уровней корреляции показывает нам, что большую часть времени оба инструмента брокера движутся практически независимо друг от друга
Давайте теперь посмотрим, остаются ли наши прогностические возможности неизменными у разных брокеров.
from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score from sklearn.linear_model import Ridge
Создадим объект для проверки временных рядов.
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON) Реализуем метод, который вернет нам новую модель для использования.
def get_model(): return(Ridge())Разделим данные и проследим за тем, чтобы они не перемешивались.
train , test = train_test_split(combined,shuffle=False,test_size=0.5)
Запишем наши уровни ошибок при использовании столбца, который мы намеренно заполнили только нулями. Это заставит модель всегда предсказывать среднее значение целевого показателя. Напомним, что когда все входные данные равны 0, линейная модель предсказывает пересечение. Проще говоря, эта модель информирует нас о том, насколько хорошо мы можем работать на этом рынке, если всегда прогнозируем среднюю рыночную доходность. Неспособность превзойти эту модель говорит о том, что у нас нет навыков.
Этот эталон называется TSS. Мы представили определение TSS во введении к нашему обсуждению. Наша цель здесь - измерить TSS у обоих брокеров, а затем сравнить наши возможности, чтобы превзойти этот показатель у других брокеров.
broker_a_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv))) broker_a_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[0:(len(inputs)//2)]],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv))) broker_b_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv))) broker_b_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[(len(inputs)//2):]],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))
Удивительно, но нам легче превзойти TSS на Брокере Б, чем на Брокере A! Это означает, что прогнозирование 10-дневной доходности EURUSD не всегда одинаково эффективно при переходе от одного брокера к другому.
res = [(broker_a_rss/broker_a_tss),(broker_b_rss/broker_b_tss)] eff = str(((res[0] - res[1])/res[1]) * 100) t = 'The EURUSD Appears ' + eff[0:4] + '% Easier To Forecast With Broker B' sns.barplot(res,color='black') plt.axhline(np.min(res),color='red',linestyle=':') plt.ylabel('5-Fold Cross Valiated Ratio of RSS/TSS ') plt.title(t) plt.xticks([0,1],['Broker A','Broker B'])

Рис. 11: Будущую доходность EURUSD за 10 дней легче спрогнозировать, используя Брокера Б
Поскольку мы определили, на какого брокера мы хотим ориентироваться, выберем входные данные, которые мы получили от Брокера Б.
b_inputs = inputs[len(inputs)//2:] Теперь разработаем новую модель все вместе.
from sklearn.ensemble import GradientBoostingRegressor model = GradientBoostingRegressor()
Подгоним модель по всем имеющимся у нас от Брокера Б данным.
model.fit(train.loc[:,b_inputs[:-2]],train['B Target'])Теперь подготовимся к экспорту нашей модели в формат ONNX, чтобы можно было легко интегрировать нашу модель искусственного интеллекта в наше MQL5-приложение.
import skl2onnx,onnxОпределим количество входных данных, которые принимает наша модель ONNX.
initial_types = [('float_input',skl2onnx.common.data_types.FloatTensorType([1,4]))]Преобразуем модель ONNX в прототип ONNX.
onnx_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12) Сохраним прототип ONNX на диск.
onnx.save(onnx_proto,"EURUSD GBR D1.onnx") Начинаем на MQL5
Теперь, когда у нас готова модель ONNX, можем приступить к созданию нашего MQL5-приложения. Сначала загрузим необходимые нам библиотеки.//+------------------------------------------------------------------+ //| EURUSD.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/ru/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/ru/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System Constants Definitions | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Нам также понадобятся системные константы, чтобы убедиться, что наше приложение отражает важные параметры, которые мы определили ранее в нашем обсуждении, такие как 10-дневный период доходности.
//+------------------------------------------------------------------+ //| System Constants Definitions | //+------------------------------------------------------------------+ #define ONNX_INPUT_SHAPE 4 #define ONNX_OUTPUT_SHAPE 1 #define SYSTEM_TIME_FRAME PERIOD_D1 #define RETURN_PERIOD 10 #define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)Загрузим файл ONNX как системный ресурс, чтобы он был скомпилирован с нашим приложением.
//+------------------------------------------------------------------+ //| System Resources | //+------------------------------------------------------------------+ #resource "\\Files\\Broker Manipulation\\EURUSD GBR D1.onnx" as const uchar onnx_proto[];
Для реализации нашей торговой стратегии нам потребуется несколько глобальных переменных.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ long model; int position_timer; double bid,ask; double o,h,l,c; bool bullish; double sl_width;
Когда наша система будет инициализирована в первый раз, мы настроим нашу модель ONNX, а затем сбросим важные глобальные переменные для нашей торговой стратегии.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT); ulong input_shape[] = {1,ONNX_INPUT_SHAPE}; ulong output_shape[] = {1,ONNX_OUTPUT_SHAPE}; if(model == INVALID_HANDLE) { Comment("Failed To Load EURUSD Auto-Encoder-Decoder: ",GetLastError()); return(INIT_FAILED); } if(!OnnxSetInputShape(model,0,input_shape)) { Comment("Failed To Set EURUSD Auto-Encoder-Decoder Input Shape: ",GetLastError()); return(INIT_FAILED); } else if(!OnnxSetOutputShape(model,0,output_shape)) { Comment("Failed To Set EURUSD Auto-Encoder-Decoder Output Shape: ",GetLastError()); return(INIT_FAILED); } position_timer = 0; sl_width = 30; //--- return(INIT_SUCCEEDED); }
Если мы больше не используем нашу торговую стратегию, освободим ресурсы, которые потреблялись нашей моделью ONNX.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(model); }
Всякий раз при получении обновленных цен мы сохраняем новые уровни цен один раз в день, а затем, если у нас нет открытых позиций, получаем прогноз из нашей модели и затем торгуем в соответствии с ним. В противном случае, если у нас уже есть открытая сделка, постараемся по возможности установить стоп-лосс, пока будем отсчитывать 10-дневный период удержания для каждой сделки.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- static datetime time_stamp; datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0); if(time_stamp != current_time) { time_stamp = current_time; ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); o = iOpen(Symbol(),SYSTEM_TIME_FRAME,1); h = iHigh(Symbol(),SYSTEM_TIME_FRAME,1); l = iLow(Symbol(),SYSTEM_TIME_FRAME,1); c = iClose(Symbol(),SYSTEM_TIME_FRAME,1); bullish = (o < c) && (c > iClose(Symbol(),SYSTEM_TIME_FRAME,2)); if(PositionsTotal() == 0) { position_timer = 0; find_setup(); } else if(PositionsTotal() > 0) { if(PositionSelect(Symbol())) { long position_type = PositionGetInteger(POSITION_TYPE); double current_sl = PositionGetDouble(POSITION_SL); double new_sl; //--- Buy Trades if(position_type == POSITION_TYPE_BUY) { new_sl = bid - ((h-l)*sl_width); if(new_sl > current_sl) Trade.PositionModify(Symbol(),new_sl,0); } //--- Sell Trades else if(position_type == POSITION_TYPE_SELL) { new_sl = ask + ((h-l)*sl_width); if(new_sl < current_sl) Trade.PositionModify(Symbol(),new_sl,0); } } if(position_timer < RETURN_PERIOD) position_timer+=1; else Trade.PositionClose(Symbol()); } } }
Наконец, эта функция получит прогноз из нашей модели, а затем проверит, есть ли у нас действительная торговая возможность.
//+------------------------------------------------------------------+ //| Find A Trading Setup | //+------------------------------------------------------------------+ void find_setup(void) { vectorf model_inputs(ONNX_INPUT_SHAPE); model_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TIME_FRAME,0); model_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TIME_FRAME,0); model_inputs[2] = (float) iLow(Symbol(),SYSTEM_TIME_FRAME,0); model_inputs[3] = (float) iClose(Symbol(),SYSTEM_TIME_FRAME,0); vectorf model_output(ONNX_OUTPUT_SHAPE); if(!OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output)) { Comment("Failed To Get A Prediction From Our Model: ",GetLastError()); return; } else { Comment("Prediction: ",model_output[0]); vector open,close; open.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_OPEN,1,2); close.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_CLOSE,1,2); if(open.Mean() < close.Mean()) { if((model_output[0] > 0) && (bullish)) Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid - ((h-l) * sl_width)),0); } else if(open.Mean() > close.Mean()) { if((model_output[0] < 0) && (!bullish)) Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask + ((h-l) * sl_width)),0); } } }
Не забудьте отменить определение всех системных констант, которые вы создаете в своем приложении.
//+------------------------------------------------------------------+ //| Undefine System Constants | //+------------------------------------------------------------------+ #undef ONNX_INPUT_SHAPE #undef ONNX_OUTPUT_SHAPE #undef SYSTEM_TIME_FRAME #undef TRADING_VOLUME #undef RETURN_PERIOD //+------------------------------------------------------------------+
Периоды дат, которые мы будем использовать для нашего тестирования на истории, взяты за основу из периода обучения нашей модели. Эти даты будут неизменными как для наших тестов на Брокере A, так и для тестирования на Брокере Б. Напомним, что Брокер Б символизирует брокера, которого разработчик MQL5 использует для создания своего приложения, в то время как Брокер A символизирует брокера, с которым его клиенты могут в конечном итоге развернуть приложение.

Рис. 12: Выберем даты ввода для нашего тестового периода
Обе настройки, указанные на рис. 12 выше и рис. 13 ниже, будут записаны в обоих проводимых нами тестах.

Рис. 13: Мы также выберем сложные параметры моделирования, чтобы получить реалистичное представление о возможностях нашей стратегии
Как мы видим на рис. 14, наша стратегия выглядит многообещающей, когда мы тестируем ее с Брокером Б. Она хорошо обрабатывает данные вне выборки, и побуждает нас тратить больше времени на доработку стратегии, чтобы добиться от нее наилучшей эффективности. Однако суть, которую мы пытаемся донести до читателя, заключается в том, что, возможно, было бы наивно всегда думать, что улучшения, достигнутые с помощью одного брокера, будут значимо соответствовать улучшениям, достигнутым с помощью любого другого брокера.

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

Рис. 15: Применяя нашу стратегию с Брокером А, мы не смогли воспроизвести тенденцию к росту баланса счета, над которой мы усердно работали
Мы также можем более подробно рассмотреть результаты нашей работы с Брокером Б на рис. 16 и сравнить их с результатами работы нашей модели с Брокером A на рис. 17.

Рис. 16: Подробный анализ наших торговых показателей, когда мы фокусируемся на предполагаемом брокере
Очевидно, что разработка стратегии, которая будет эффективно работать с несколькими брокерами, безусловно, не является тривиальным делом. По мере того как модели машинного обучения становятся более сложными, они также становятся более чувствительными к незначительным изменениям в своих входных данных. Эти различия в числовых характеристиках инструмента могут иметь разрушительные последствия для нашей способности делиться и осмысленно воспроизводить торговые стратегии.

Рис. 17: Подробный анализ стратегии, пытающейся работать с брокером, на котором она не была обучена
Заключение
Децентрализованный характер глобальных финансовых рынков накладывает реальные ограничения, которые затрудняют нашему сообществу воспроизведение результатов друг друга. Брокеры не дают никаких гарантий, что их цены будут совпадать, а это означает, что недостатки, которые вы используете у своего брокера, могут отсутствовать у моего, даже при использовании одной и той же стратегии на одном и том же инструменте.
В зависимости от того, какую роль вы предпочитаете играть в нашем сообществе, эти идеи могут иметь практическое значение:
- Если вы предпочитаете пользоваться разделом “Фриланс” на веб-сайте MQL5, укажите своего брокера при запросе приложений и попросите разработчиков создать демо-счета с вашим брокером, чтобы можно было получать индивидуальные решения. Избегайте случайных и общих запросов, таких как "Требуется торговое приложение по паре EURUSD", потому что, как мы уже видели, для вас может быть безопаснее изложить всё, как можно более подробно.
- Пользователи, которые часто покупают приложения на Marketplace, теперь понимают, почему продукты, ориентированные на конкретного брокера, могут принести больше пользы, чем те, которые претендуют на универсальность.
- Подписчики торговых сигналов могут добиться максимального удовлетворения, селективно выбирая провайдеров торговых сигналов, использующих одного и того же брокера, что гарантирует, что заявленная и реализованная доходность всегда совпадают.
- Наконец, мои коллеги-разработчики MQL5 получают более четкое представление о том, что нам может потребоваться для предоставления последовательных продуктов и надежных сервисов, которые будут радовать наших клиентов.
Осознавая эти проблемы, мы можем работать над созданием более воспроизводимых решений, ориентированных на конкретного брокера, которые принесут пользу всем членам нашего разнообразного и инклюзивного сообщества. Эта статья была задумана как иллюстрация опасностей, связанных с попытками использовать одну и ту же модель ONNX на разных брокерах. Будучи разработчиками MQL5, я считаю, что мы должны придерживаться более высоких стандартов практики и избегать подвергать наших клиентов подобным опасностям.
| Название файла | Описание файла |
|---|---|
| Requesting Broker Data.ipynb | Jupyter Notebook, который мы использовали для получения исторических ежедневных данных по паре EURUSD от наших двух брокеров. |
| Analyzing Broker Data.ipynb | Jupyter Notebook, который мы использовали для проверки согласованности в исторических ежедневных данных по паре EURUSD от наших двух брокеров. |
| EURUSD.mq5 | Советник, который мы создали для оценки нашей прибыльности, по одной и той же модели на двух разных брокерах. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18133
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Возможности Мастера MQL5, которые вам нужно знать (Часть 63): Использование паттернов каналов Демарка и конвертов
Торговля LLM-агента со встроенной философией топ-трейдеров
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования