Библиотеки: TesterBenchmark - страница 4

 

Причина основной тормознутости МТ5 - это имитация полного торгового окружения. В подавляющем большинстве советников, такой точной имитации не нужно. Отличным выходом для таких советников будет переход на упрощенный режим тестирования, с простым моделированием торгового окружения. Однако такой возможности нет. Но, если написать свой, упрощенный тестер стратегий, и запустить его в режиме математических вычислений, скорость оптимизации будет огонь! Вот о чем предлагаю подумать я, всем, кто хочет кардинально повысить скорость оптимизации советников.

 
Vasiliy Sokolov:

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

CTrade тоже не хранит историю, но то не помешало реализовать вариант через него.

То что ООП код выполняется медленней процедурного кода, лично мне ни о чем не говорит. Давайте посуществу: какие методы CTrade являются бутылочным горлышком? Почему? Что говорит профилирование этих методов? Как в тестере Вы определяете медленные участки кода? 

Не анализировал причины тормозов СБ, но не думаю, что дело в ООП, т.к. MT4Orders пусть и минимально, но тоже через ООП написан. После заявки в СД разработчики ускорили работу СБ. Подкрутили ли саму СБ или компилятор - не в курсе.

Сам сначала анализирую абсолютные тормоза, затем - относительные

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

 
Vasiliy Sokolov:

Причина основной тормознутости МТ5 - это имитация полного торгового окружения. В подавляющем большинстве советников, такой точной имитации не нужно. Отличным выходом для таких советников будет переход на упрощенный режим тестирования, с простым моделированием торгового окружения. Однако такой возможности нет. Но, если написать свой, упрощенный тестер стратегий, и запустить его в режиме математических вычислений, скорость оптимизации будет огонь! Вот о чем предлагаю подумать я, всем, кто хочет кардинально повысить скорость оптимизации советников.

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

 
MQL5:
i = 5 Pass = 5 OnTester = 8.521 s.: Count = 15925124, 1868926.7 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


MT4Orders:

i = 2 Pass = 2 OnTester = 8.001 s.: Count = 15925124, 1990391.7 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


MQL5 оказался медленнее библиотеки, написанной на том же MQL5! Стал разбираться и нашел причину, сделав такую замену

//  if (!PositionSelect(_Symbol))
  if (!PositionGetTicket(0))

MQL5-вариант советника стал показывать такую производительность

i = 4 Pass = 4 OnTester = 7.931 s.: Count = 15925124, 2007959.1 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


Тупая замена PositionSelect на PositionGetTicket поднимает скорость бэктеста на 7%!

Отставание MT4Orders от максимально оптимизированного чистого MQL5 менее процента.

 
fxsaber:

Места распределились следующим образом

  1. Чистый MQL5 - 100% производительность.

  2. СБ Trade\Trade.mqh - ~84% производительность.
СБ стала меньше отставать.

Подкрутил совсем немного саму СБ (куски СБ, которые использовал советник), не изменяя функционал и универсальность. Стала не 84%, а 97%.

Кто использует СБ, берите на заметку потенциальную возможность ускорения.

 

Стало интересно сравнить производительность одного и того же мультивалютного MT4-советника, сконвертированного разными способами.

Полноценная конвертация

#include <TesterBenchmark.mqh> // https://www.mql5.com/ru/code/18804
#include "Spreader 2.mq5"      // https://www.mql5.com/ru/code/19402


и кустарная (быстрая) конвертация

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Советники: Spreader

fxsaber, 2016.09.03 11:18

// https://www.mql5.com/ru/code/16006
#include <MT4Orders.mqh>
#include <MQL4_to_MQL5.mqh>

#include "Spreader_v2.mq4" // https://www.mql5.com/ru/code/9495

void OnTick()
{
  start();
}

Результаты замера производительности.


Полноценный вариант

i = 1 Pass = 1 OnTester = 3.465 s.: Count = 897532, 259028.0 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


Кустарный

i = 1 Pass = 1 OnTester = 4.815 s.: Count = 897532, 186403.3 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


Кустарный значительно проигрывает. Причина простая - если хочется быстрых таймсерий, то конвертировать в лоб MT4-таймсерии нельзя

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотеки: CPrice

fxsaber, 2016.08.28 10:36

Посмотрите такой вариант

#define DEFINE_TIMESERIE(NAME,FUNC,T)                                                                         \
  class CLASS##NAME                                                                                           \
  {                                                                                                           \
  public:                                                                                                     \
    static T Get(const string Symb,const int TimeFrame,const int iShift)                                      \
    {                                                                                                         \
      T tValue[];                                                                                             \
                                                                                                              \
      return((Copy##FUNC((Symb == NULL) ? _Symbol : Symb, _Period, iShift, 1, tValue) > 0) ? tValue[0] : -1); \
    }                                                                                                         \
                                                                                                              \
    T operator[](const int iPos) const                                                                        \
    {                                                                                                         \
      return(CLASS##NAME::Get(_Symbol, _Period, iPos));                                                       \
    }                                                                                                         \
  };                                                                                                          \
                                                                                                              \
  CLASS##NAME NAME;                                                                                           \
                                                                                                              \
  T i##NAME(const string Symb,const int TimeFrame,const int iShift)                                           \
  {                                                                                                           \
    return(CLASS##NAME::Get(Symb, TimeFrame, iShift));                                                        \
  }

DEFINE_TIMESERIE(Volume,TickVolume,long)
DEFINE_TIMESERIE(Time,Time,datetime)
DEFINE_TIMESERIE(Open,Open,double)
DEFINE_TIMESERIE(High,High,double)
DEFINE_TIMESERIE(Low,Low,double)
DEFINE_TIMESERIE(Close,Close,double)

Можно, как в  MT4, вызывать Open[bar], High[bar], Time[bar], Volume[bar] и т.д. А также iHigh(...), iClose(...) и другие.

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

Высокопроизводительная библиотека iTimeSeries
Высокопроизводительная библиотека iTimeSeries
  • голосов: 23
  • 2017.05.25
  • nicholishen
  • www.mql5.com
Одной из основных проблем с MQL5 до сих пор было удаление встроенных функций для работы с таймсериями. Несмотря на то, что такой подход расширил для программистов возможности разработки, он также замедлил работу из-за обязательного шага по созданию и удалению новых ячеек памяти каждый раз, когда требуется доступ к данным таймсерии.  Рассмотрим...
 
fxsaber:

Кустарный значительно проигрывает. Причина простая - если хочется быстрых таймсерий, то конвертировать в лоб MT4-таймсерии нельзя.

Ошибочный вывод. Причина тормозов совсем в другом. Вставляем в каждый из MT5-вариантов всего одну строку

#define Comment(A)

И замеряем заново

  • Полноценный

i = 1 Pass = 1 OnTester = 2.074 s.: Count = 897532, 432754.1 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 165

  • Кустарный

i = 1 Pass = 1 OnTester = 2.310 s.: Count = 897532, 388542.0 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1653


Полноценный ускорился на 67%, кустарный - 108%! По итогу разрыв теперь небольшой - полноценный быстрее кустарного на ~11%. Там уже, похоже, причина в ООП-оверхеде.


Но главное не в этом. Одной строкой получилось ускорить советники! И это в Оптимизаторе, где Comment никакой роли не играет.

 

Об этом было написано летом вместе с заявкой в СД. Удивительно, что все ровно! Или это логично? Как можно говорить о супер-скорости тестера, когда почти все советники (используют СБ) проваливаются в скорости, съедают деньги в Облаке и т.д.?! Видимо, это нормально.


Исходный код упростил, выкинув работу с Историей и MT4Orders. Оставил только чистый MQL5 и СБ

#include <TesterBenchmark.mqh>

#include <Trade\Trade.mqh> // Закомментировать, чтобы включить вариант на чистом MQL5

input int Interval = 60;

void OnTick()
{
#ifdef  ERR_USER_INVALID_HANDLE // Trade.mqh
  static CTrade Trade;
  static CDealInfo Deal;
  static CPositionInfo Position;

  if (!Position.SelectByIndex(0))
  {
    if (HistorySelect(0, LONG_MAX))
    {
      const int Total = HistoryDealsTotal() - 1;

      if ((Total >= 0) && Deal.SelectByIndex(Total) && (Deal.DealType() == DEAL_TYPE_SELL))
        Trade.Sell(1);
      else
        Trade.Buy(1);
    }
  }
  else if (TimeCurrent() - Position.Time() >= Interval)
    Trade.PositionClose(_Symbol);
#else  // ERR_USER_INVALID_HANDLE
  if (!PositionGetTicket(0))
  {
    if (HistorySelect(0, LONG_MAX))
    {
      const int Total = HistoryDealsTotal() - 1;
      MqlTradeRequest Request = {0};

      Request.action = TRADE_ACTION_DEAL;

      Request.symbol = _Symbol;
      Request.type = ((Total >= 0) && ((ENUM_DEAL_TYPE)HistoryDealGetInteger(HistoryDealGetTicket(Total), DEAL_TYPE) == DEAL_TYPE_SELL)) ?
                     ORDER_TYPE_SELL : ORDER_TYPE_BUY;;

      Request.volume = 1;
      Request.price = SymbolInfoDouble(Request.symbol, (Request.type == ORDER_TYPE_BUY) ? SYMBOL_ASK : SYMBOL_BID);

      MqlTradeCheckResult CheckResult;
      if (OrderCheck(Request, CheckResult))
      {
        MqlTradeResult Result;

        const bool AntiWarning = OrderSend(Request, Result);
      }
    }
  }
  else if (TimeCurrent() - PositionGetInteger(POSITION_TIME) >= Interval)
  {
    MqlTradeRequest Request = {0};
    MqlTradeResult Result;

    Request.action = TRADE_ACTION_DEAL;
    Request.position = PositionGetInteger(POSITION_TICKET);

    Request.symbol = PositionGetString(POSITION_SYMBOL);
    Request.type = (ENUM_ORDER_TYPE)(1 - PositionGetInteger(POSITION_TYPE));

    Request.volume = PositionGetDouble(POSITION_VOLUME);
    Request.price = PositionGetDouble(POSITION_PRICE_CURRENT);

    const bool AntiWarning = OrderSend(Request, Result);
  }
#endif // ERR_USER_INVALID_HANDLE
}


Результат СБ

------
OnTesterInit
i = 0 Pass = 0 OnTester = 1.898 s.: Count = 2007057, 1057458.9 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1730
i = 1 Pass = 1 OnTester = 1.900 s.: Count = 2007057, 1056345.8 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1730
iMin = 0 Results[iMin] = 1.898s.
iMax = 1 Results[iMax] = 1.900s.
Amount = 2 Mean = 1.899 s. - 81.75%
OnTesterDeinit
------
Interval = 4.646 s., Count = 0, 0.0 unit/sec


Результат чистого MQL5

------
OnTesterInit
i = 0 Pass = 0 OnTester = 1.239 s.: Count = 2007057, 1619900.7 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1730
i = 1 Pass = 1 OnTester = 1.243 s.: Count = 2007057, 1614687.9 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1730
iMin = 0 Results[iMin] = 1.239s.
iMax = 1 Results[iMax] = 1.243s.
Amount = 2 Mean = 1.241 s. - 75.10%
OnTesterDeinit
------
Interval = 3.305 s., Count = 0, 0.0 unit/sec

Все в те же 1.5 раза! Но лохамвсем пофиг, продолжаем использовать супер-СБ.

 
fxsaber:

Все в те же 1.5 раза! Но лохамвсем пофиг, продолжаем использовать супер-СБ.

Вот такие правки в СБ

//+------------------------------------------------------------------+
//| Send order                                                       |
//+------------------------------------------------------------------+
bool CTrade::OrderSend(const MqlTradeRequest &request,MqlTradeResult &result)
  {
   static const bool IsTester = (::MQLInfoInteger(MQL_TESTER) || ::MQLInfoInteger(MQL_OPTIMIZATION));
   
   bool res;
   string action="";
   string fmt   ="";
//--- action
   if(m_async_mode)
      res=::OrderSendAsync(request,result);
   else
      res=::OrderSend(request,result);
//--- check
   if(res)
     {
      if(!IsTester && m_log_level>LOG_LEVEL_ERRORS)
         PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result));
     }
   else
     {
      if(!IsTester && m_log_level>LOG_LEVEL_NO)
         PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result));
     }
//--- return the result
   return(res);
  }
//+------------------------------------------------------------------+
//| Select a position on the index                                   |
//+------------------------------------------------------------------+
bool CPositionInfo::SelectByIndex(const int index)
  {
   return(PositionGetTicket(index));
   
   ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
//---
   if(margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      ulong ticket=PositionGetTicket(index);
      if(ticket==0)
         return(false);
     }
   else
     {
      string name=PositionGetSymbol(index);
      if(name=="")
         return(false);
     }
//---
   return(true);
  }

чудесным образом ускоряют советники на СБ. Это сложно?

Честно говоря, торговую СБ, не меняя логики API, переписать надо почти полностью. Что не кусок - то плохо.

Причина обращения: