Анализ нескольких символов с помощью Python и MQL5 (Часть 3): Треугольные курсы валют
Финансовые рынки по своей природе шумные. Трейдеры часто сталкиваются с ненужными просадками, вызванными ложными торговыми сигналами, которые побуждают трейдера преждевременно открывать позиции. В свете этой проблемы было разработано множество различных торговых стратегий и принципов. Большинство этих аксиом по сути предписывают трейдеру ждать, прежде чем действовать, и искать альтернативные источники подтверждения или дополнительные признаки силы.
Эти правила обычно не предусматривают определенного горизонта, в котором должны быть обнаружены сигналы подтверждения, и иногда могут стоить трейдеру хороших уровней входа.
У нас есть явная потребность попытаться сформулировать торговые стратегии, способные измерять силу рынка как можно ближе к реальному времени. Возможно, нам удастся достичь цели, если мы попытаемся воспользоваться преимуществами взаимосвязанных рынков и поискать предсказуемые закономерности, которые имеют для нас смысл. Мы можем продолжать следить за кросс-рыночными моделями, которые, по нашему мнению, предсказуемы, вместо того, чтобы потенциально терять пипсы с каждой интересующей нас сделки и увеличивать потери.
Для читателей, которые, возможно, еще не знакомы с кросс-рыночными моделями, мы предоставим краткое введение в тему, чтобы вы могли понять, почему эти кросс-рыночные модели, как правило, считаются предсказуемыми и потенциально устойчивыми.
Около 90% мировых товаров оцениваются в долларах США. Однако некоторые товары торгуются так часто, что их часто котируют в нескольких валютах одновременно.
Драгоценные металлы, такие как серебро, обычно котируются одновременно в американских долларах и евро. В этой статье в качестве примера мы рассмотрим серебро. Вы можете обнаружить, что ваш брокер предлагает котировки цен на серебро в долларах (XAGUSD) и в евро (XAGEUR) одновременно.
Если мы хотим торговать XAGUSD и знаем, что он принципиально связан с XAGEUR, как мы можем использовать это понимание в своих интересах, чтобы принимать или отклонять предлагаемые нам торговые возможности, не дожидаясь подтверждения бесконечно?
Принимая во внимание преобладающий обменный курс между евро и долларом (EURUSD), можно сформировать треугольную торговую стратегию, которая находит предсказуемые закономерности, формирующиеся между ценой серебра в долларах, евро и справедливым обменным курсом евро к доллару (EURUSD). Наша цель — предоставить читателю торговую стратегию, устойчивую к шуму и помогающую выявить "скрытые" рыночные настроения на основе обычных рыночных котировок, полученных в терминале MetaTrader 5.
Обзор торговой стратегии
Прежде чем углубляться в детали нашей торговой стратегии, рассмотрим понятия базовой и котируемой валюты в торгуемой паре. Если в качестве примера взять EURUSD, то базовой будет EUR (евро). По мере того как обменный курс, отображаемый на графике, удаляется от 0, стоимость базовой валюты увеличивается. Таким образом, если мы смотрим на график обменного курса EURUSD, и график растет, это сигнализирует о том, что нам нужно продать больше американских долларов, чтобы заработать 1 евро на спотовом рынке Форекс.

Рис. 1. Разница между базовой и котируемой валютой в паре
Цена котируемой валюты увеличивается по мере того, как отображаемый на графике обменный курс приближается к 0. Это значит, что нам придется продавать все меньше и меньше американских долларов, чтобы получить 1 евро.

Рис. 2. Котируемая валюта
Мы рассматриваем 3 разных рынка и намерены торговать только на 1. Наша цель — XAGUSD. Если мы хотим понять, в каком направлении может двигаться XAGUSD, начнем с проверки курса EURUSD.
Если курс EURUSD растет, то евро укрепляется, а доллар обесценивается. Можно было бы ожидать, что товар, цена которого выражена как в евро, так и в долларах, может подешеветь в евро, поскольку евро приобрел покупательную способность, и подорожать в долларах, поскольку доллар потерял стоимость на валютном рынке.
Надеемся, что этот мысленный эксперимент дал читателям, которые, возможно, совершенно не знакомы с такими торговыми стратегиями, представление о том, как можно ожидать, что три рынка будут "зеркально отражать" друг друга при определенных предположениях.
Анализируя эффективность этих трех рынков одновременно, мы, по сути, можем создать стратегию, которая не всегда будет нуждаться в ожидании подтверждения. Если наши предположения о взаимосвязанной природе этих рынков верны, то у нас должна быть жизнеспособная структура, которую можно адаптировать к другим рынкам.
Карта связи торговой стратегии представлена на рис. 4. В общем случае, если курс EURUSD падает, можно ожидать, что товары, оцененные в евро, станут дороже, а товары, оцененные в долларах, станут дешевле. Наша стратегия будет проверять курс EURUSD, цену серебра в евро и цену серебра в долларах, пытаясь найти эту конкретную закономерность. Если будет обнаружена кросс-рыночная модель, мы откроем короткую позицию по цене серебра в долларах.

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

Рис. 4. Визуализация торговых правил для открытия длинной позиции по XAGUSD
Скорее всего, существует множество сложных факторов, которые толкают цену на серебро вверх или вниз. Мы лишь пытаемся обобщить эти сложные отношения, используя более простые.
Обзор периода тестирования на истории
Мы будем тестировать нашу стратегию с первого ноября 2023 года по первое января 2025 года. Оставшиеся у нас данные с 1 ноября 2022 года по конец октября 2023 года будут использованы для обучения нашего приложения. В будущих версиях статьи мы заменим нашу простую модель взаимосвязанной природы рынков на модели, которые наш компьютер может изучать самостоятельно. Поэтому, хотя мы не будем использовать раздел обучения в этой статье, мы воспользуемся им в будущих обсуждениях.

Рис. 5. Период тестирования на истории
Начало работы в MQL5
Наша стратегия требует, чтобы мы следили за тремя рынками одновременно. Поэтому давайте начнем с создания системных констант, чтобы отслеживать каждый необходимый нам рынок. Это позволяет нам легко переключаться между этими рынками и сравнивать их показатели.
//+------------------------------------------------------------------+ //| Baseline Model.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System Constants | //+------------------------------------------------------------------+ #define SYMBOL_ONE "XAGUSD" //--- Our primary symbol, the price of Silver in USD #define SYMBOL_TWO "XAGEUR" //--- Our secondary symbol, the price of Silver in EUR #define SYMBOL_THREE "EURUSD" //--- Our EURUSD exchange rate. #define FETCH 24 //--- How many bars of data should we fetch? #define TF_1 PERIOD_H1 //--- Our intended time frame #define VOLUME SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10 //--- Our trading volume
Мы будем использовать векторный тип MQL5 для извлечения рыночных данных и их быстрого преобразования.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point;
Торговая библиотека позволяет нам открывать и управлять нашими сделками.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Язык MQL5 создан, чтобы помочь нам эффективно торговать. Каждое событие, которое, по вашему мнению, может произойти на рынке, сопоставляется с определенными событиями. Получение новых цен — это событие. При регистрации события вызывается обработчик событий OnTikc(). Функции внутри этого обработчика событий будут выполнены.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- New prices have been quoted new_quotes_received(); } //+------------------------------------------------------------------+
Всякий раз, когда мы получаем новые котировки, мы сначала хотим проверить, сформировалась ли полная свеча. В случаях, когда формируется новая свеча, мы обновляем системные переменные и пытаемся найти торговую возможность.
//+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Updates system variables accordingly | //+------------------------------------------------------------------+ void new_quotes_received(void) { static datetime time_stamp; datetime time = iTime(SYMBOL_ONE,TF_1,0); if(time_stamp != time) { time_stamp = time; update(); } }
При первой загрузке нашей системы мы хотим убедиться, что все необходимые нам рынки активированы и легко доступны.
//+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ void setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); }
Как только сформируется новая свеча, мы будем считывать обновленные цены с каждого из отслеживаемых нами рынков. Мы можем измерить рост рынка, разделив его текущую стоимость на прошлую стоимость. Если в результате деления получается количество меньше 1, то рыночная стоимость уменьшается. В противном случае рынок растет.
//+------------------------------------------------------------------+ //| Update our system setup | //+------------------------------------------------------------------+ void update(void) { //--- Fetch updated prices xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH); xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH); eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH); //--- Calculate the growth in market prices eurusd_growth = eurusd[0] / eurusd[FETCH - 1]; xageur_growth = xageur[0] / xageur[FETCH - 1]; xagusd_growth = xagusd[0] / xagusd[FETCH - 1]; //--- Update system variables SymbolSelect(SYMBOL_ONE,true); bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK); //--- Check if we need to setup a new position if(PositionsTotal() == 0) find_setup(); //--- Check if we need to manage our positions if(PositionsTotal() > 0) manage_setup(); //--- Give feedback on the market growth Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth); }
Наши торговые настройки можно представить как определенную конфигурацию, в которой мы хотим увидеть рынки относительно друг друга. По сути, мы хотим увидеть движения на рынке XAGUSD, подкрепленные движениями на рынках EURUSD и XAGEUR соответственно. Правила, которые мы указали ниже, эквивалентны тому, что показано на рисунках 4 и 5.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } }
После открытия наши сделки будут контролироваться стоп-лоссом, что поможет нам сохранить прибыль.
//+------------------------------------------------------------------+ //| Manage setup | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select our open position if(PositionSelect(SYMBOL_ONE)) { double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); //--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } //--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); } } }Наконец, мы отменим определения системных констант, которые мы определили ранее.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef TF_1 #undef SYMBOL_ONE #undef SYMBOL_TWO #undef SYMBOL_THREE #undef VOLUME #undef FETCH
Наше приложение будет готово, когда мы соберем все компоненты нашей системы вместе.
//+------------------------------------------------------------------+ //| Baseline Model.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System Constants | //+------------------------------------------------------------------+ #define SYMBOL_ONE "XAGUSD" //--- Our primary symbol, the price of Silver in USD #define SYMBOL_TWO "XAGEUR" //--- Our secondary symbol, the price of Silver in EUR #define SYMBOL_THREE "EURUSD" //--- Our EURUSD exchange rate. #define FETCH 24 //--- How many bars of data should we fetch? #define TF_1 PERIOD_H1 //--- Our intended time frame #define VOLUME SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10 //--- Our trading volume //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point; //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- New prices have been quoted new_quotes_received(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Updates system variables accordingly | //+------------------------------------------------------------------+ void new_quotes_received(void) { static datetime time_stamp; datetime time = iTime(SYMBOL_ONE,TF_1,0); if(time_stamp != time) { time_stamp = time; update(); } } //+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ void setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); } //+------------------------------------------------------------------+ //| Update our system setup | //+------------------------------------------------------------------+ void update(void) { //--- Fetch updated prices xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH); xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH); eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH); //--- Calculate the growth in market prices eurusd_growth = eurusd[0] / eurusd[FETCH - 1]; xageur_growth = xageur[0] / xageur[FETCH - 1]; xagusd_growth = xagusd[0] / xagusd[FETCH - 1]; //--- Update system variables SymbolSelect(SYMBOL_ONE,true); bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK); //--- Check if we need to setup a new position if(PositionsTotal() == 0) find_setup(); //--- Check if we need to manage our positions if(PositionsTotal() > 0) manage_setup(); //--- Give feedback on the market growth Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth); } //+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } } //+------------------------------------------------------------------+ //| Manage setup | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select our open position if(PositionSelect(SYMBOL_ONE)) { double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); //--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } //--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); } } } //+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef TF_1 #undef SYMBOL_ONE #undef SYMBOL_TWO #undef SYMBOL_THREE #undef VOLUME #undef FETCH
Как я уже говорил, наше тестирование на истории будет проводиться с первого ноября 2023 года по январь 2025 года. Для проведения нашего теста мы будем использовать таймфрейм H1. Мы надеемся, что H1 предоставит нам больше торговых возможностей, чем более крупные таймфреймы, такие как Daily, и при этом не будет сбивать нас с толку таким количеством шума, когда мы торгуем даже на меньших таймфреймах, таких как M1.

Рис. 6. Даты, которые мы будем использовать для тестирования нашей стратегии XAGUSD
Наши торговые условия будут подобраны таким образом, чтобы имитировать реальную торговлю. Мы хотим выбрать случайную задержку, чтобы каждый тик основывался на реальных тиках. Это обеспечивает надежное моделирование рыночных условий, наблюдавшихся в прошлом.

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

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

Рис. 9. Подробный анализ эффективности нашей торговой стратегии
Улучшение первоначальных показателей
Что касается нашей первоначальной стратегии, то здесь есть еще много возможностей для совершенствования. Мы попытаемся исправить нестабильное поведение нашей первоначальной стратегии и выровнять периодические колебания, которые мы наблюдали от прибыльных периодов до устойчивых убыточных периодов. Кратко опишем изменения, внесенные в систему для получения лучших результатов от нашей торговой стратегии:
| Предлагаемое улучшение | Предполагаемая цель |
|---|---|
| Интеграция технических индикаторов для дополнительного подтверждения | Мы можем отсортировать значительный рыночный шум, используя дополнительное подтверждение от наших технических индикаторов в терминале MetaTrader 5, чтобы сократить задержку в принятии решений. |
| Построение индивидуальных статистических моделей для каждого отслеживаемого рынка | Наличие модели рынка может помочь нам предвидеть изменения в направлении рынка или волатильности и даже впоследствии сократить размер нашей позиции в неопределенные времена. |
Чтобы приступить к работе, нам сначала нужно получить исторические рыночные котировки, чтобы у нас были данные для обучения нашей модели. При использовании статистических моделей мы изначально не можем быть уверены, какой набор входных данных даст наилучшую модель. Поэтому вообще-то хорошей практикой будет заложить в основу как можно больше различных функций, а затем уже сузить свой выбор.
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- File name string file_name = "XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv"; //--- Amount of data requested input int size = 3000; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { //---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i=size;i>=1;i--) { if(i == size) { FileWrite(file_handle,"Time","XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close","XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close","EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close","Open Squared","High Squared","Low Squared","Close Squared","Open Cubed","High Cubed","Low Cubed","Close Cubed","Open Squre Root","High Square Root","Low Square Root","Close Square Root","Open Growth","High Growth","Low Grwoth","Close Growth","O / H","O / L","O / C","H / L","Log Open Growth","Log High Grwoth","Log Low Growth","Log Close Grwoth","Sin H / L","Cos O / C"); } else { FileWrite(file_handle, iTime("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGUSD",PERIOD_CURRENT,i), iHigh("XAGUSD",PERIOD_CURRENT,i), iLow("XAGUSD",PERIOD_CURRENT,i), iClose("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGEUR",PERIOD_CURRENT,i), iHigh("XAGEUR",PERIOD_CURRENT,i), iLow("XAGEUR",PERIOD_CURRENT,i), iClose("XAGEUR",PERIOD_CURRENT,i), iOpen("EURUSD",PERIOD_CURRENT,i), iHigh("EURUSD",PERIOD_CURRENT,i), iLow("EURUSD",PERIOD_CURRENT,i), iClose("EURUSD",PERIOD_CURRENT,i), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),3), MathSqrt(iOpen("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iHigh("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iLow("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iClose("XAGUSD",PERIOD_CURRENT,i)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)), (iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), (iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), (iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (MathSin(iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i))), (MathCos(iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i))) ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Анализ данных в Python
После сбора обучающих данных мы готовы приступить к построению статистической модели данных. Сначала импортируем необходимые нам библиотеки Python.
#Import libraries we need import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt
Теперь маркируем данные. Не забывайте, что мы торгуем на таймфрейме H1, поэтому давайте установим в качестве метки изменение уровней цен за 24 часа (1 торговый день).
#Clean up the data LOOK_AHEAD = 24 data = pd.read_csv("../XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv") data["Target"] = data["XAGUSD Close"].shift(-LOOK_AHEAD) - data["XAGUSD Close"] data.dropna(inplace=True) data.reset_index(inplace=True,drop=True)
Это самый важный шаг во всем процессе, которому мы вместе следуем. Первоначально наши данные будут содержать рыночные котировки, которые совпадают с периодом нашего тестирования на истории. Это нежелательно для практиков, стремящихся получить объективную оценку стоимости своей торговой стратегии.
Поэтому мы удалим все рыночные данные, которые пересекаются с предполагаемым периодом тестирования на истории. Как вы помните, на рис. 6 период нашего тестирования на истории явно начинается с 1 ноября 2023 года, а на рис. 10 наши тренировочные данные заканчиваются 31 октября 2023 года.
#Drop the dates corresponding to our backtest _ = data.iloc[-((24 * 365) - 918):,:] #Keep the dates before our backtest data = data.iloc[:-((24 * 365) - 918),:] data

Рис. 10. Убедитесь, что обучающие данные, которые вы используете для своих статистических моделей, не содержат утечки информации из более позднего периода
Если мы визуализируем рост, происходящий на рынках XAGUSD и XAGEUR, мы увидим, что спред между этими двумя рынками увеличивается и уменьшается с течением времени. Это может потенциально указывать на наличие арбитражных возможностей между этими двумя рынками. В противном случае, если бы не существовало никаких арбитражных возможностей, то красная и зеленая линии должны были бы идеально перекрывать друг друга, не расходясь, с начала периода и до сегодняшнего дня. Излишне говорить, что это не так. На какое-то время эти два рынка явно разъединяются, прежде чем наступает их последующая коррекция.
plt.title("Comparing XAGUSD & XAGEUR Growth") plt.plot((data['XAGUSD Close'] / data.loc[0,"XAGUSD Close"]) / (data['XAGUSD Close'].max() - data['XAGUSD Close'].min()),color="red") plt.plot((data['XAGEUR Close'] / data.loc[0,"XAGEUR Close"]) / (data['XAGEUR Close'].max() - data['XAGEUR Close'].min()),color="green") plt.ylabel("Commodity Growth") plt.xlabel("Time") plt.legend(["XAGUSD","XAGEUR"]) plt.grid()

Рис. 11. Визуализация наших двух финансовых рынков, наложенных друг на друга, открывает нам потенциальные возможности арбитража
Обозначим наши входные данные и цель.
X = data.iloc[:,1:-1].columns y = "Target"
Подготовимся к нанесению дерева градиентного бустинга (Gradient Boosting Tree) на наши данные и его экспорту в формат ONNX. Деревья градиентного бустинга известны своей способностью обнаруживать взаимодействия, происходящие в заданном наборе данных. Мы надеемся воспользоваться их мощными возможностями обнаружения закономерностей для улучшения нашей торговой стратегии.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorTypea from sklearn.ensemble import GradientBoostingRegressor
Давайте теперь построим специальные статистические модели для каждого интересующего нас рынка. Надеемся, это поможет нам отсортировать ложные прорывы. Подгоним каждую модель под данные и экспортируем ее в формат ONNX, чтобы впоследствии мы могли импортировать все три из них в наше приложение MetaTrader 5.
Начнем с построения модели рынка XAGUSD.
model = GradientBoostingRegressor() model.fit(data.loc[:,["XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close"]],data.loc[:,y]) initial_types = [("float_input",FloatTensorType([1,4]))] xagusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(xagusd_model_proto,"../XAGUSD State Model.onnx")
Во-вторых, мы последуем за рынком XAGEUR.
model = GradientBoostingRegressor() model.fit(data.loc[:,["XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close"]],data.loc[:,"XAGEUR Target"]) initial_types = [("float_input",FloatTensorType([1,4]))] xageur_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(xageur_model_proto,"../XAGEUR State Model.onnx")
И наконец, мы экспортируем нашу статистическую модель рынка EURUSD.
model = GradientBoostingRegressor() model.fit(data.loc[:,["EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close"]],data.loc[:,"EURUSD Target"]) initial_types = [("float_input",FloatTensorType([1,4]))] eurusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(eurusd_model_proto,"../EURUSD State Model.onnx")
Наша система будет придавать больший вес тем рыночным движениям, которые поддерживаются всеми тремя нашими моделями одновременно. Сначала мы хотим рассмотреть паттерны, которые мы описали на рис. 3 и рис. 4, формирующиеся на рынке, а затем наши три модели, прогнозирующие, что наши паттерны не исчезнут быстро, но сохранятся с течением времени. Это даст нам разумную степень уверенности в силе, поддерживающей те рыночные движения, которые мы ожидаем осуществить.
Реализация улучшений на MQL5
Теперь мы можем приступить к внедрению улучшений в первоначальную версию нашей торговой стратегии. Во-первых, мы добавим в наш арсенал дополнительные технические индикаторы, чтобы дать системе более точные указания и возможности обнаружения трендов. Хорошей стратегией для решения этой задачи является пересечение скользящих средних. Однако мы будем использовать более отзывчивую версию стратегии, которая минимизирует задержку торговых сигналов. Читатели, желающие узнать больше об этой версии стратегии пересечения скользящих средних, могут найти подробности здесь.
#define XAGUSD_MA_PERIOD 8
Загрузите только что созданные нами модели ONNX в качестве системных ресурсов.
//+------------------------------------------------------------------+ //| System resources | //+------------------------------------------------------------------+ #resource "\\Files\\XAGUSD State Model.onnx" as uchar xagusd_onnx_buffer[] #resource "\\Files\\XAGEUR State Model.onnx" as uchar xageur_onnx_buffer[] #resource "\\Files\\EURUSD State Model.onnx" as uchar eurusd_onnx_buffer[]
Нам понадобятся новые глобальные переменные, соответствующие индикаторам скользящей средней, которые мы вводим, а также отдельные компоненты, необходимые для завершения нашей модели ONNX.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point; int xagusd_f_ma_handler,xagusd_s_ma_handler; double xagusd_f[],xagusd_s[]; vectorf model_output = vectorf::Zeros(1); long onnx_model; vectorf xageur_model_output = vectorf::Zeros(1); long xageur_onnx_model; vectorf eurusd_model_output = vectorf::Zeros(1); long eurusd_onnx_model;
Некоторые из наших функций необходимо переработать, чтобы соответствовать нашим новым ожиданиям. Для начала, когда наше торговое приложение больше не используется, нам необходимо безопасно высвободить ресурсы, выделенные нашим моделям ONNX и двум скользящим средним.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(onnx_model); OnnxRelease(xageur_onnx_model); OnnxRelease(eurusd_onnx_model); IndicatorRelease(xagusd_f_ma_handler); IndicatorRelease(xagusd_s_ma_handler); Print("System deinitialized"); }
Кроме того, наша процедура настройки также должна учитывать наши модели ONNX и наши технические индикаторы. Нам необходимо убедиться, что желаемые нами индикаторы и модели загружены и указаны правильно. В противном случае мы прервем процедуру инициализации и сообщим пользователю о том, что пошло не так.
//+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ bool setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); //--- Setup the moving averages xagusd_f_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_OPEN); xagusd_s_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_CLOSE); if((xagusd_f_ma_handler == INVALID_HANDLE) || (xagusd_s_ma_handler == INVALID_HANDLE)) { Comment("Failed to load our technical indicators correctly. ", GetLastError()); return(false); } //--- Setup our statistical models onnx_model = OnnxCreateFromBuffer(xagusd_onnx_buffer,ONNX_DEFAULT); xageur_onnx_model = OnnxCreateFromBuffer(xageur_onnx_buffer,ONNX_DEFAULT); eurusd_onnx_model = OnnxCreateFromBuffer(eurusd_onnx_buffer,ONNX_DEFAULT); if(onnx_model == INVALID_HANDLE) { Comment("Failed to create our XAGUSD ONNX model correctly. ",GetLastError()); return(false); } if(xageur_onnx_model == INVALID_HANDLE) { Comment("Failed to create our XAGEUR ONNX model correctly. ",GetLastError()); return(false); } if(eurusd_onnx_model == INVALID_HANDLE) { Comment("Failed to create our EURUSD ONNX model correctly. ",GetLastError()); return(false); } ulong input_shape[] = {1,4}; ulong output_shape[] = {1,1}; if(!(OnnxSetInputShape(onnx_model,0,input_shape))) { Comment("Failed to specify XAGUSD model input shape. ",GetLastError()); return(false); } if(!(OnnxSetInputShape(xageur_onnx_model,0,input_shape))) { Comment("Failed to specify XAGEUR model input shape. ",GetLastError()); return(false); } if(!(OnnxSetInputShape(eurusd_onnx_model,0,input_shape))) { Comment("Failed to specify EURUSD model input shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(onnx_model,0,output_shape))) { Comment("Failed to specify XAGUSD model output shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(xageur_onnx_model,0,output_shape))) { Comment("Failed to specify XAGEUR model output shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(eurusd_onnx_model,0,output_shape))) { Comment("Failed to specify EURUSD model output shape. ",GetLastError()); return(false); } Print("System initialized succefully"); //--- If we have gotten this far, everything went fine. return(true); }
Нам также понадобится специальная функция для извлечения прогнозов из нашей модели ONNX. Наша функция сначала подготовит входные данные модели ONNX, а затем вызовет функцию OnnxRun, чтобы получить прогноз от нашей модели.
//+------------------------------------------------------------------+ //| Fetch a prediction from our model | //+------------------------------------------------------------------+ void model_predict(void) { vectorf model_inputs = { (float) iOpen(SYMBOL_ONE,TF_1,1), (float) iClose(SYMBOL_ONE,TF_1,1)}; OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output); Print(StringFormat("Model forecast: %d",model_output)); }
Нашу процедуру поиска торговых настроек также необходимо пересмотреть. Первый шаг, который необходимо сделать, — это получить прогноз с помощью нашей модели, после чего мы запрашиваем дополнительное подтверждение от пересечений наших скользящих средних. Напомним, что в нашей версии пересечения скользящих средних одна скользящая средняя располагается на цене открытия, а вторая — на цене закрытия, причем обе скользящие средние имеют один и тот же период. Когда скользящая средняя открытия находится наверху, мы интерпретируем это как сигнал на продажу. Если нет, то мы открываем длинную позицию.
Таким образом, нашим конечным условием будет то, что наш регрессор с градиентным усилением (Gradient Boosted Regressor) ожидает, что ценовое движение будет соответствовать обоим наблюдаемым нами сигналам. Если это так, мы будем считать это высоковероятной торговой конфигурацией и удвоим размер лота. В противном случае мы будем вести консервативную торговлю.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { model_predict(); //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { if(xagusd_s[0] < xagusd_f[0]) { if(model_output[0] < 0) { //--- If all our systems align, we have a high probability trade setup Trade.Sell(VOLUME * 2,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Otherwise, we should trade conservatively Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { if(xagusd_s[0] > xagusd_f[0]) { if(model_output[0] > 0) { Trade.Buy(VOLUME * 2,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } } }
Давайте теперь повторим тот же тест, который мы провели, но на этот раз мы будем использовать нашу усовершенствованную версию торговой стратегии. Как вы помните, даты нашего тестирования на истории не пересекаются с датами, которые мы использовали для обучения наших статистических моделей.

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

Рис. 13. В идеале эти настройки должны быть одинаковыми для обоих тестов
Давайте теперь проанализируем полученные нами результаты. В нашем первоначальном тестировании на исторических данных мы продемонстрировали коэффициент Шарпа 0,14, тогда как наша пересмотренная стратегия демонстрирует коэффициент Шарпа 1,85. Это существенное улучшение нашего коэффициента Шарпа, а это значит, что нам удалось фактически стать более прибыльными, при этом ответственно принимая на себя дополнительные риски. Низкие коэффициенты Шарпа связаны с высокой дисперсией при низкой доходности.
Более того, наш средний убыток снизился примерно со USD 115 за сделку до USD 109 за сделку, в то время как наша средняя прибыль, с другой стороны, выросла в среднем с USD 188 до USD 213. Для нас это хороший знак. Наша общая прибыль также выросла с USD 395 в первой итерации стратегии до USD 1 449 в текущей итерации. И все это при меньшем количестве сделок, чем в нашей вручную настроенной версии стратегии.

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

Рис. 15. Визуализация кривой прибылей и убытков, полученной с помощью усовершенствованной версии торговой стратегии
Заключение
Надеюсь, что после прочтения этой статьи вы сможете создать алгоритмическую стратегию для торговли на взаимосвязанных рынках. Мы получили представление о том, как объединить свои профессиональные знания в определенной области для поиска и прибыльной торговли на треугольных рынках. Используя статистические модели, читатель может лучше находить нужные ему торговые настройки. Используя взаимосвязанную природу определенных рынков, мы всегда можем в реальном времени получать индикаторы истинной силы рынка.
| Имя файла | Описание |
|---|---|
| Baseline_Model | Первоначальная версия треугольной торговой стратегии. |
| Second Version | Переработанная и более прибыльная версия торговой стратегии. |
| EURUSD State Model | Статистическая модель рынка EURUSD. |
| XAGEUR State Model | Статистическая модель рынка XAGEUR. |
| XAGUSD State Model | Статистическая модель рынка XAGUSD. |
| Triangular Exchange Rates | Jupyter notebook, который мы использовали для анализа наших рыночных данных и построения наших статистических моделей рынка. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17258
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Знакомство с языком MQL5 (Часть 18): Введение в паттерн "Волны Вульфа"
Квантовые вычисления и градиентный бустинг в торговле EUR/USD
Нейросети в трейдинге: Двусторонняя адаптивная временная корреляция (Основные компоненты)
Знакомство с языком MQL5 (Часть 17): Создание советников для разворотов тренда
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования