Создание эксперта, торгующего на разных инструментах

Nikolay Kositsin | 30 июня, 2010


Введение

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

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

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


1. Реализация простейшей системы следования за трендом

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

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

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

Вполне естественно, что каждому активу следует поставить в соответствие свои индивидуальные торговые параметры стоплосса, тейкпрофита, объема открываемой позиции и слиппаджа (проскальзывания). Ну и по вполне понятным причинам входные параметры индикатора Triple Exponential Moving Average для каждой торговой фишки должны быть свои, индивидуальные.

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

Было бы на чём торговать! И самое главное - хватило бы ресурсов персонального компьютера для решения такой задачи.

input string            Symb0 = "EURUSD";
input  bool            Trade0 = true; 
input int                Per0 = 15; 
input ENUM_APPLIED_PRICE ApPrice0 = PRICE_CLOSE;
input int             StLoss0 = 1000;
input int           TkProfit0 = 2000;
input double            Lots0 = 0.1;
input int           Slippage0 = 30;

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

А поскольку эксперт работает сразу с двенадцатью финансовыми активами, то и вызовов этих функций внутри блока OnTick() тоже должно быть двенадцать.

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

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

Для передачи торговых сигналов из одной функции в другую в качестве параметров функций следует сделать статические массивы, которые получают свои значения по ссылке.  Вот итоговый вариант предполагаемого кода для функции OnTick().

void OnTick()
  {
//--- объявление массивов переменных для торговых сигналов  
   static bool UpSignal[12], DnSignal[12], UpStop[12], DnStop[12];
  
//--- получение торговых сигналов
   TradeSignalCounter( 0, Symb0,  Trade0,  Per0,  ApPrice0,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 1, Symb1,  Trade1,  Per1,  ApPrice1,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 2, Symb2,  Trade2,  Per2,  ApPrice2,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 3, Symb3,  Trade3,  Per3,  ApPrice3,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 4, Symb4,  Trade4,  Per4,  ApPrice4,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 5, Symb5,  Trade5,  Per5,  ApPrice5,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 6, Symb6,  Trade6,  Per6,  ApPrice6,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 7, Symb7,  Trade7,  Per7,  ApPrice7,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 8, Symb8,  Trade8,  Per8,  ApPrice8,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 9, Symb9,  Trade9,  Per9,  ApPrice9,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(10, Symb10, Trade10, Per10, ApPrice10, UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(11, Symb11, Trade11, Per11, ApPrice11, UpSignal, DnSignal, UpStop, DnStop);
  
//--- совершение торговых операций
   TradePerformer( 0, Symb0,  Trade0,  StLoss0,  TkProfit0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 1, Symb1,  Trade1,  StLoss1,  TkProfit1,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 2, Symb2,  Trade2,  StLoss2,  TkProfit2,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 3, Symb3,  Trade3,  StLoss3,  TkProfit3,  Lots3,  Slippage3,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 4, Symb4,  Trade4,  StLoss4,  TkProfit4,  Lots4,  Slippage4,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 5, Symb5,  Trade5,  StLoss5,  TkProfit5,  Lots5,  Slippage5,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 6, Symb6,  Trade6,  StLoss6,  TkProfit6,  Lots6,  Slippage6,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 7, Symb7,  Trade7,  StLoss7,  TkProfit7,  Lots7,  Slippage7,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 8, Symb8,  Trade8,  StLoss8,  TkProfit8,  Lots8,  Slippage8,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 9, Symb9,  Trade9,  StLoss9,  TkProfit9,  Lots9,  Slippage9,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer(10, Symb10, Trade10, StLoss10, TkProfit10, Lots10, Slippage10, UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer(11, Symb11, Trade11, StLoss11, TkProfit11, Lots11, Slippage11, UpSignal, DnSignal, UpStop, DnStop); 
//---   
  }

Внутри функции TradeSignalCounter() следует всего один раз на старте для каждой фишки получить хэндлы технического индикатора Triple Exponential Moving Average и затем на каждой смене бара вычислять торговые сигналы.

Эта, в общем-то, достаточно простая затея при реализации в программном коде весьма недурно обрастает второстепенными деталями.

bool TradeSignalCounter(int Number,
                        string Symbol_,
                        bool Trade,
                        int period,
                        ENUM_APPLIED_PRICE ApPrice,
                        bool &UpSignal[],
                        bool &DnSignal[],
                        bool &UpStop[],
                        bool &DnStop[])
  {
//--- проверка запрета на торговлю
   if(!Trade)return(true);

//--- объявление переменной для хранения итогового размера массивов переменных
   static int Size_=0;

//--- объявление массива для хранения хэндлов индикаторов как статической переменной
   static int Handle[];

   static int Recount[],MinBars[];
   double TEMA[4],dtema1,dtema2;

//--- инициализация 
   if(Number+1>Size_) // вход в блок инициализации только на первом старте
     {
      Size_=Number+1; // Для этого номера вход в блок закрыт

      //--- изменение размеров массивов переменных
      ArrayResize(Handle,Size_);
      ArrayResize(Recount,Size_);
      ArrayResize(MinBars,Size_);

      //--- определение минимально количества баров, достаточного для расчёта 
      MinBars[Number]=3*period;

      //--- предварительное обнуление ячеек массивов
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;

      //--- используем массив как таймсерию
      ArraySetAsSeries(TEMA,true);

      //--- получение хендла индикатора
      Handle[Number]=iTEMA(Symbol_,0,period,0,ApPrice);
     }

//--- проверка количества баров на достаточность для расчёта 
   if(Bars(Symbol_,0)<MinBars[Number])return(true);

//--- получение торговых сигналов 
   if(IsNewBar(Number,Symbol_,0) || Recount[Number]) // Вход в блок на смене бара или при неудачном копировании данных
     {
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;

      //--- используя хэндлы индикатора, копируем значения индикаторного
      //--- буфера в специально подготовленный для этого статический массив
      if(CopyBuffer(Handle[Number],0,0,4,TEMA)<0)
        {
         Recount[Number]=true;
         //--- так как данные не получены, то следует вернуться 
         //--- в этот блок получения торговых сигналов на следующем тике!
         return(false); // выходим из функции TradeSignalCounter() без получения торговых сигналов
        }

      //--- все операции копирования из индикаторного буфера завершены успешно
      Recount[Number]=false; // можно не возвращаться в этот блок до очередной смены бара

      int Digits_=int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+4);
      dtema2 = NormalizeDouble(TEMA[2] - TEMA[3], Digits_);
      dtema1 = NormalizeDouble(TEMA[1] - TEMA[2], Digits_);

      //---- определение сигналов входа
      if(dtema2 > 0 && dtema1 < 0) DnSignal[Number] = true;
      if(dtema2 < 0 && dtema1 > 0) UpSignal[Number] = true;

      //---- определение сигналов выхода
      if(dtema1 > 0) DnStop[Number] = true;
      if(dtema1 < 0) UpStop[Number] = true;
     }
//---
   return(true);
  } 

В этом плане код функции TradePerformer() получается совсем простым:

bool TradePerformer(int    Number,
                    string Symbol_,
                    bool   Trade,
                    int    StLoss,
                    int    TkProfit,
                    double Lots,
                    int    Slippage,
                    bool  &UpSignal[],
                    bool  &DnSignal[],
                    bool  &UpStop[],
                    bool  &DnStop[])
  {
//---- проверка запрета на торговлю
   if(!Trade)return(true);

//--- закрываем открытые позиции 
   if(UpStop[Number])BuyPositionClose(Symbol_,Slippage);
   if(DnStop[Number])SellPositionClose(Symbol_,Slippage);

//--- открываем новые позиции
   if(UpSignal[Number])
      if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         UpSignal[Number]=false; //На этом баре больше этого торгового сигнала не будет!
//---
   if(DnSignal[Number])
      if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         DnSignal[Number]=false; //На этом баре больше этого торгового сигнала не будет!
//---
   return(true);
  }

Но это только потому, что фактически приказы на выполнение торговых операций упакованы еще в четыре функции:

BuyPositionClose();
SellPositionClose();
BuyPositionOpen();
SellPositionOpen();

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

bool BuyPositionClose(const string symbol,ulong deviation)
  {
//--- объявление структур торгового запроса и результата торгового запроса
   MqlTradeRequest request;
   MqlTradeResult result;
   ZeroMemory(request);
   ZeroMemory(result);

//--- проверка на наличие открытой BUY позиции
   if(PositionSelect(symbol))
     {
      if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) return(false);
     }
   else  return(false);

//--- инициализация структуры торгового запроса MqlTradeRequest для закрывания BUY позиции
   request.type   = ORDER_TYPE_SELL;
   request.price  = SymbolInfoDouble(symbol, SYMBOL_BID);
   request.action = TRADE_ACTION_DEAL;
   request.symbol = symbol;
   request.volume = PositionGetDouble(POSITION_VOLUME);
   request.sl = 0.0;
   request.tp = 0.0;
   request.deviation=(deviation==ULONG_MAX) ? deviation : deviation;
   request.type_filling=ORDER_FILLING_FOK;
//---
   string word="";
   StringConcatenate(word,
                     "<<< ============ BuyPositionClose():   Закрываем Buy позицию по ",
                     symbol," ============ >>>");
   Print(word);

//--- отправка приказа на закрывание позиции на торговый сервер
   if(!OrderSend(request,result))
     {
      Print(ResultRetcodeDescription(result.retcode));
      return(false);
     }
//---
   return(true);
  }

Собственно говоря, вот и весь мультивалютный эксперт (Exp_TEMA.mq5)!

Помимо рассмотренных функций, в нём присутствуют еще две дополнительных пользовательских функции:

bool IsNewBar(int Number, string symbol, ENUM_TIMEFRAMES timeframe);

string ResultRetcodeDescription(int retcode);

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

Эксперт готов, пора приступать к тестированию! Каких-то серьезных отличий в тестировании мультивалютного эксперта от его одновалютного собрата практически не наблюдается. Определяем настройки на закладке "Настройки" Тестера стратегий:

Рисунок 1. Вкладка "Настройки" Тестера стратегий

Рисунок 1. Вкладка "Настройки" Тестера стратегий

В случае необходимости корректируем значения входных параметров на закладке "Входные параметры":

Рисунок 2. Вкладка "Параметры" Тестера стратегий

Рисунок 2. Вкладка "Параметры" Тестера стратегий

и после этого жмем кнопку "Старт" в Тестере стратегий на закладке "Настройки":

Рисунок 3. Запуск тестирования советника

Рисунок 3. Запуск тестирования советника

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

После завершения теста в Тестере стратегий открываем закладку "Результаты":

 Рисунок 4. Результаты тестирования

Рисунок 4. Результаты тестирования

 и делаем анализ полученных данных, используя при этом содержимое закладок "График":

Рисунок 5. График динамики баланса и средств

Рисунок 5. График динамики баланса и средств

и "Журнал":

Рисунок 6. Журнал Тестера стратегий

Рисунок 6. Журнал Тестера стратегий

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

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

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


2. Резонансы на финансовых рынках и их использование в торговых системах

Идея учета корреляционных связей между разными финансовыми активами, в общем-то, не нова, и было бы интересно реализовать алгоритм, который основывался бы именно на анализе подобных закономерностей. В данной статье я реализую мультивалютный автомат по мотивам статьи Василия Якимкина "Резонансы - новый класс технических индикаторов" из журналов "Валютный спекулянт" 04, 05 за 2001 год.

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

С учетом этих требований хорошо подходит классический индикатор стохастик. Хотя, в принципе, нет никакой разницы и в использовании других индикаторов. В качестве трендового направления будем считать знак разности между значением самого стохастика Stoh и его сигнальной линией Sign.

Рис.1 Стохастический осциллятор

Рисунок 7. Определение направления тренда

Для знака переменной dStoh возникает целая таблица возможных комбинаций и их интерпретаций на направление действующего тренда:

Рис.2 Таблица торговых сигналов и их интерпретация

Рисунок 8. Комбинации знака переменной dStoh и направление тренда

В ситуации, когда два сигнала по активам EUR/JPY и USD/JPY имеют противоположное значение, следует определять их сумму, и если эта сумма больше нуля, то считать оба сигнала положительными, в противном случае - отрицательными.

Таким образом, для открывания лонгов используем ситуацию, когда тренд растущий, а для выхода, когда тренд падающий, или когда сигналы по индикатору основного актива EUR/USD отрицательные. Также выходим из лонгов, если на основном активе сигналов нет, а по остальным активам сумма значений переменной dStoh меньше нуля. Для шортов всё абсолютно аналогично, но ситуация противоположная.

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

 Рис.3 Индикатор Мультистохастик

Рисунок 9. Индикатор MultiStochastic

Зеленые бары дают сигналы открытия и удержания лонгов, а красные - шортов соответственно.

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

В этом эксперте я сделал торговлю всего тремя фишками, поэтому код функции OnTick() стал донельзя примитивным:

void OnTick()
  {
//----+    
  //----+ Объявление массивов переменных для торговых сигналов  
  static bool UpSignal[], DnSignal[], UpStop[], DnStop[];
  
  //----+ Получение торговых сигналов
  TradeSignalCounter(0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop);
                             
  //----+ Совершение торговых операций
   TradePerformer( 0, SymbolA0,  Trade0,  StopLoss0,  0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 1, SymbolA1,  Trade1,  StopLoss1,  0,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 2, SymbolA2,  Trade2,  StopLoss2,  0,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
//----+   
  }

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

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

bool TradeSignalCounter(int Number,
                       bool Trade,
                       int Kperiod,
                       int Dperiod,
                       int slowing,
                       ENUM_MA_METHOD ma_method,
                       ENUM_STO_PRICE price_,
                       string SymbolA,
                       string SymbolB,
                       string SymbolC,
                       bool& UpSignal[],
                       bool& DnSignal[],
                       bool& UpStop[],
                       bool& DnStop[])
  {
//--- проверка запрета на торговлю
   if (!Trade)return(true);
//--- объявление переменной для хранения размеров массивов переменных
   static int Size_ = 0;
//--- объявление массивов для хранения хэндлов индикаторов как статических переменных
   static int Handle[];
   static int Recount[], MinBars[];
//---
   double dUpSignal_[1], dDnSignal_[1], dUpStop_[1], dDnStop_[1];
   
//--- изменение размеров массивов переменных
   if (Number + 1 > Size_)
    {
     uint size = Number + 1;
     //---
     if (ArrayResize(Handle, size) == -1
       || ArrayResize(Recount, size) == -1
         || ArrayResize(UpSignal, size) == -1
           || ArrayResize(DnSignal, size) == -1
             || ArrayResize(UpStop, size) == -1
               || ArrayResize(DnStop, size) == -1
                 || ArrayResize(MinBars, size) == -1)
      {
       string word = "";
       StringConcatenate(word, "TradeSignalCounter( ", Number,
            " ): Ошибка!!! Не удалось изменить размеры массивов переменных!!!");            
       int error = GetLastError();
       ResetLastError();
       //---
       if (error > 4000)
        {
         StringConcatenate(word, "TradeSignalCounter( ", Number, " ): Код ошибки ", error);
         Print(word); 
        }
  
       Size_ = -2;
       return(false);
      }
      
     Size_ = size;
     Recount[Number] = false;
     MinBars[Number] = Kperiod + Dperiod + slowing;
     
     //--- получение хэндла индикатора
     Handle[Number] = iCustom(SymbolA, 0, "MultiStochastic_Exp",
                              Kperiod, Dperiod, slowing, ma_method, price_, SymbolA, SymbolB, SymbolC);
    }
   
   //--- проверка количеств баров на достаточность для расчёта 
   if (Rates_Total(SymbolA, SymbolB, SymbolC) < MinBars[Number])return(true);
   
   //--- проверка синхронизации таймсерий
   if (!SynchroCheck(SymbolA, SymbolB, SymbolC))return(true);
   
   //--- получение торговых сигналов 
   if (IsNewBar(Number, SymbolA, 0) || Recount[Number])
    {
     DnSignal[Number] = false;
     UpSignal[Number] = false;
     DnStop  [Number] = false;
     UpStop  [Number] = false;
     
     //--- используя хэндлы индикаторов, копируем значения индикаторных 
     //--- буферов в специально подготовленные для этого статические массивы
     if (CopyBuffer(Handle[Number], 1, 1, 1, dDnSignal_) < 0){Recount[Number] = true; return(false);}
     if (CopyBuffer(Handle[Number], 2, 1, 1, dUpSignal_) < 0){Recount[Number] = true; return(false);}     
     if (CopyBuffer(Handle[Number], 3, 1, 1, dDnStop_  ) < 0){Recount[Number] = true; return(false);}
     if (CopyBuffer(Handle[Number], 4, 1, 1, dUpStop_  ) < 0){Recount[Number] = true; return(false);}
     
     //--- конвертируем полученные значения в значения логических переменных торговых команд
     if (dDnSignal_[0] == 300)DnSignal[Number] = true;
     if (dUpSignal_[0] == 300)UpSignal[Number] = true;
     if (dDnStop_  [0] == 300)DnStop  [Number] = true;
     if (dUpStop_  [0] == 300)UpStop  [Number] = true;
     
     //--- все операции копирования из индикаторных буферов завершены успешно
     //--- можно не возвращаться в этот блок до очередной смены бара
     Recount[Number] = false;
    }
//---
   return(true);
  }

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


Заключение

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