English Deutsch 日本語
preview
Формулировка динамического советника на нескольких парах (Часть 1): Корреляция и обратная корреляция валютных пар

Формулировка динамического советника на нескольких парах (Часть 1): Корреляция и обратная корреляция валютных пар

MetaTrader 5Примеры | 7 марта 2025, 11:46
257 0
Hlomohang John Borotho
Hlomohang John Borotho

Введение

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

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

Что будет охвачено:

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

В трейдинге корреляция относится к взаимосвязи между изменениями цен различных валютных пар. Когда две валютные пары положительно коррелируют, они, как правило, движутся в одном направлении. Например, GBPUSD и EURUSD часто положительно коррелируют, что означает, что когда GBPUSD растет, EURUSD также имеет тенденцию к росту. Это связано с тем, что обе пары используют доллар США в качестве валюты котировки, и любое значительное ослабление или укрепление доллара США, скорее всего, окажет одинаковое влияние на обе пары.

С другой стороны, обратная корреляция возникает, когда две валютные пары движутся в противоположных направлениях. Классическим примером является соотношение между GBPUSD и USDCAD. Когда пара GBPUSD движется вверх (бычий тренд), пара USDCAD часто движется вниз (медвежий тренд). Это происходит потому, что в первой паре (GBPUSD) валютой котировки является доллар США, в то время как во второй паре (USDCAD) доллар США является базовой валютой. По мере ослабления доллара США пара GBPUSD растет, в то время как пара USDCAD имеет тенденцию к падению.

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

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

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

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

Матрица Forex

Представление

#include <Trade\Trade.mqh>
CTrade trade;

Импортирует торговую библиотеку MetaTrader 5 и создает экземпляр класса "Trade", позволяющий вам управлять торговыми операциями, такими как открытие, закрытие и изменение ордеров.

int handles[];

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

MqlTick previousTick, currentTick;

Эти переменные хранят тиковые данные для цен символов. "previousTick" содержит последние тиковые данные, в то время как "currentTick" хранит текущие тиковые данные.

inputstring Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY";
inputstring Base_Quote = "USD";
inputint Currecy_Main = 0;

Эти входные данные позволяют пользователю настраивать советник:

  • `Symbols`: список валютных пар, разделенных запятыми, которые советник будет отслеживать и по которым он будет торговать.
  • `Base-Quote`: Валюта для определения корреляций.
  • `Currency-Main`: Индекс, указывающий на основную валютную пару, используемую для генерации сигналов.
string symb_List[];
string Formatted_Symbs[];
int Num_symbs = 0;
  • `symb-List`: Массив, содержащий необработанный список символов, подлежащих обработке.
  • `Formatted-Symbs`: Массив, в котором хранятся обработанные символы.
  • `Num-symb`: Содержит общее количество символов, которые будут использоваться после анализа входных данных "Symbols".
intOnInit(){
    string sprtr = ",";
    ushort usprtr;
    usprtr = StringGetCharacter(sprtr, 0);
    StringSplit(Symbols, usprtr, symb_List);
    Num_symbs = ArraySize(symb_List);
    ArrayResize(Formatted_Symbs, Num_symbs);

Функция "OnInit" вызывается один раз при загрузке советника, задавая начальные значения и конфигурации. Затем мы определяем запятую как разделитель (`sprtr`), который будет использоваться для разделения входной строки. Функция "StringGetCharacter()" преобразует разделитель в "ushort" (короткое значение без знака), что необходимо для функции "StringSplit()". Функция "StringSplit()" разбивает входные данные Symbols (строка, разделенная запятыми) на массив отдельных символов. Массив `Symb-List[]` содержит проанализированные символы. Размер массива `Formatted-Symbs[]` изменяется в соответствии с количеством анализируемых символов. Мы будем использовать данный массив для дальнейшей обработки, такой как добавление какого-либо форматирования или корректировок, необходимых для торговой логики.

for(int i = 0; i < Num_symbs; i++){
       Formatted_Symbs[i] = symb_List[i];
    }

Мы перебираем количество символов и переносим символы из массива `symbol-List[]` в массив `Formatted-Symbols[]`. На данном этапе дополнительное форматирование не выполняется.

ArrayResize(handles, ArraySize(Formatted_Symbs));

Здесь мы изменяем размер массива `handles[]` таким образом, чтобы он соответствовал размеру массива `Formatted-Symbols[]`. Каждый элемент в `handles[]` будет содержать хэндл RSI для соответствующего символа.

for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      handles[i] = iRSI(Formatted_Symbs[i], PERIOD_CURRENT, 14, PRICE_CLOSE);
    }

Данный цикл инициализирует хэндл индикатора RSI для каждого символа.



void OnTick(){
   
   if(isNewBar()){
      for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
         Sig_trade(Formatted_Symbs[Currecy_Main], handles[Currecy_Main]);
      }
   }
}

Здесь мы сначала проверяем, есть ли у нас новый бар, а затем запускаем цикл, чтобы определить индекс основной валюты, который будет генерировать сигналы. Затем просто вызываем функцию `Sig-trade()`, которая несет в себе торговую логику, функция принимает строковый параметр для символа и целочисленный параметр для хэндла RSI.

void Sig_trade(string symb, int handler){   
   double rsi[];
   CopyBuffer(handler, MAIN_LINE, 1, 1, rsi);
   
   bool RSIBuy = rsi[0] < 30;
   bool RSISell = rsi[0] > 70;
   
   // Check if the current symbol is a base USD pair
   bool isBaseUSD = StringSubstr(symb, 0, 3) == Base_Quote;
   bool isQuoteUSD = StringSubstr(symb, 3, 3) == Base_Quote;
   string Bcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_BASE);
   string Qcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_PROFIT);

Будем использовать довольно простую стратегию, которая заключается в том, что мы используем стратегию RSI для покупки, когда RSI опускается ниже уровня 30, и продажи, когда он пробивается выше уровня 70.

  • `StringSubstr(symb, 0, 3) == Base-Quote`: Извлекаются первые три знака символа валютной пары ( symb ) и проверяется, совпадают ли они с "USD". Определяет, является ли данный символ базовой парой USD.
  • `StringSubstr(symb, 3, 3) == Base-Quote`: Извлекает три знака, начиная с четвертой позиции символа валютной пары ( symb ), и проверяет, равны ли они "USD". Определяет, является ли данный символ котируемой парой USD.
  • `Bcurr = SymbolInfoString(symb, SYMBOL-CURRENCY-BASE)`: Извлекает базовую валюту символа (первую валюту в паре) и сохраняет ее в переменной `Bcurr`.
  • `Qcurr = SymbolInfoString(symb, SYMBOL-CURRENCY-PROFIT)`: Извлекает валюту котировки символа (вторую валюту в паре) и сохраняет ее в переменной `Qcurr`.
for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong posTicket = PositionGetTicket(i);
      if(PositionGetString(POSITION_SYMBOL) == symb){
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            if(RSISell){
               trade.PositionClose(posTicket);
            }
            RSIBuy = false;
         }elseif(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            if(RSIBuy){
               trade.PositionClose(posTicket);
            }
            RSISell = false;
         }
      }
   }

Чтобы управлять всеми позициями, мы перебираем все открытые позиции в цикле. Функция `Positions Total()` возвращает общее количество открытых на данный момент позиций. Цикл начинается с последней позиции (`Positions Total() - 1`) и повторяется в обратном направлении. Мы используем обратную итерацию, чтобы избежать проблем при изменении списка открытых позиций во время выполнения цикла. Мы используем функцию "PositionGetTicket()" для извлечения номера тикета позиции в индексе "i". Тикет - это уникальный идентификатор для закрытия или изменения определенной позиции.

Затем используем функцию `PositionGetString()`, чтобы получить символ текущей позиции. Далее сравниваем этот символ с "symb" (анализируемым символом). Если они совпадают, то позиция является актуальной. Затем проверяем, является ли текущая позиция ордером на покупку, с помощью функции `PositionGetInteger()`. Если позиция предназначена для покупки, а индекс RSI указывает на продажу (значение "RSISell" - true), позиция закрывается с помощью команды "trade.PositionClose(posTicket)". После этого назначаем `RESIBuy = false`, чтобы гарантировать, что дальнейшие сделки на покупку не будут открыты, поскольку текущий сигнал указывает на продажу.

То же самое относится и к позициям на продажу. Проверяем, является ли текущая позиция ордером на продажу. Если позиция представляет собой продажу, а индикатор RSI подает сигнал на покупку (значение "RSIBuy" - true), позиция закрывается с помощью команды "trade.PositionClose(posTicket)". Также, присваиваем значение "RSISell = false", чтобы предотвратить открытие новых сделок на продажу, поскольку сигнал предназначен для позиции на покупку. Используем приведенный выше код для управления всеми открытыми позициями, поскольку не используем stop loss и take profit. Таким образом, код будет открывать и закрывать позиции исключительно в зависимости от RSI.

for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string currSymb = Formatted_Symbs[i];
      
      // Get base and quote currencies for the looped symbol
      string currBaseCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_BASE);
      string currQuoteCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_PROFIT);

Чтобы фактически открывать сделки по всем валютным парам, мы перебираем размер (количество элементов) в массиве `Formatted-Symbs[]`. Цикл повторяется по каждому элементу (валютной паре) в массиве `Formatted-Symbs`. Мы сохраняем текущий символ на графике в переменной `currSymb` из массива `Formatted-Symbs`. Данный символ будет использоваться для получения соответствующей информации о валютной паре.

if(RSIBuy){
         
         if(currQuoteCurr == Base_Quote){
            trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation");
         }
         
         if(currBaseCurr == Base_Quote){
            trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation");
         }
      }

Здесь мы проверяем и исполняем только в том случае, если индикатор RSI генерирует сигнал на покупку (обычно, когда значение RSI находится ниже определенного порога, что указывает на состояние перепроданности). Затем проверяем, соответствует ли валюта котировки текущего символа указанной валюте "Базовой котировки". Если соответствует, то открывается ордер на покупку по данной валютной паре. Логика, лежащая в основе этого, заключается в том, что когда вы покупаете валютную пару, вы покупаете базовую валюту и продаете валюту котировки. Поэтому, если ваша торговая стратегия является бычьей на парах, где котировка является вашей "Базовой котировкой", вы покупаете эту пару.

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

      if(RSISell){
         
         if(currBaseCurr == Base_Quote){
            trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation");
         }
         if(currQuoteCurr == Base_Quote){
            trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation");
         }
      }

Здесь мы рассматриваем логику при обнаружении сигнала на продажу от RSI. Этот блок выполняется, если индикатор RSI генерирует сигнал на продажу (обычно, когда RSI находится выше определенного порога, что указывает на состояние перекупленности). Проверяем, соответствует ли базовая валюта текущего символа указанной "Базовой котировке` с помощью `currBaseCurr == Base-Quote`. Если соответствует, то по данной валютной паре исполняется ордер на продажу. Логика остается прежней, если вы настроены медвежьим образом по парам, где базовой валютой является указанная `Базовая котировка`. Затем вам надо продать эту пару. Продажа этой пары означает, что вы продаете базовую валюту и покупаете валюту котировки.

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

bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   staticdatetime last_time=0;
//--- current time
   datetime lastbar_time= (datetime) SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE); 

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

Выше приведена функция "isNewBar", позволяющая советнику не запускать и не исполнять несколько ордеров.

Результаты работы тестера стратегий

Тестер стратегий

Основываясь на результатах тестирования, приведенных выше, мы можем подтвердить, что система успешно открывает сделки в соответствии с указанными валютными корреляциями и обратными корреляциями. Как обсуждалось ранее, когда генерируется сигнал на покупку и основной валютой является котируемая пара USD, все котируемые пары USD в массиве "Formatted-Symbs" исполняют ордера на покупку, в то время как базовые пары USD исполняют ордера на продажу. Такое поведение соответствует ожидаемой функциональности, демонстрируя, что система эффективно реализует логику корреляции, к достижению которой мы стремились.

Для эффективного и корректного использования системы крайне важно рассмотреть возможность использования группировки на основе одной пары. Например, при торговле парами, основанными на EUR, такими как EURUSD, EURGBP и EURJPY, установка "EUR" в качестве входных данных "Базовой котировки" позволит базовой валюте управлять торговой логикой при обнаружении сигнала. Этот подход может быть применен к другим валютным группам, таким как USDEUR, или любым другим пользовательским парам, которые разрешает ваш брокер, гарантируя, что логика системы корректно согласуется с вашей торговой стратегией.

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

Заключение

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

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


Кривая эквити

BackTest

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15378

Прикрепленные файлы |
Cоздание стратегии возврата к среднему на основе машинного обучения Cоздание стратегии возврата к среднему на основе машинного обучения
В данной статье предлагается очередной оригинальный подход к созданию торговых систем на основе машинного обучения, с использованием кластеризации и разметки сделок для стратегий возврата к среднему.
MQL5-советник, интегрированный в Telegram (Часть 3): Отправка скриншотов графиков с подписями из MQL5 в Telegram MQL5-советник, интегрированный в Telegram (Часть 3): Отправка скриншотов графиков с подписями из MQL5 в Telegram
В этой статье мы создадим советник MQL5, который кодирует скриншоты графиков в виде графических данных и отправляет их в чат Telegram посредством HTTP-запросов. Внедрив кодирование и передачу изображений, мы улучшим существующую систему MQL5-Telegram путем добавления визуальной торговой аналитики непосредственно в Telegram.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Бильярдный алгоритм оптимизации — Billiards Optimization Algorithm (BOA) Бильярдный алгоритм оптимизации — Billiards Optimization Algorithm (BOA)
Метод BOA, вдохновленный классической игрой в бильярд, моделирует процесс поиска оптимальных решений, как игру с шарами, стремящимися попасть в лузы, олицетворяющие наилучшие результаты. В данной статье мы рассмотрим основы работы BOA, его математическую модель и эффективность в решении различных оптимизационных задач.