Библиотеки: Virtual - страница 53

 
На данном примере применение Virtual позволило получить идентичный результат в два раза быстрее.

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

 

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

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

fxsaber, 2023.12.10 13:33

Выглядит, как самойбийство такая конструкция на каждом тике в мультивалютном советнике.

for (uint i = OrdersTotal(); (bool)i--;)
  if (OrderSelect(i, SELECT_BY_POS) &&
      (OrderType() == Type) &&
      (OrderMagicNumber() == Magic) &&
      (OrderSymbol() == Symb))
  {
    OrderModify(...);
  }

Возможность избавиться от дорогих string в пользу SymbolID дала десятикратное ускорение расчета мультивалютников в MT5-тестере.

MT5-Tester MT5-Tester + Virtual MT5-Tester + Virtual + SymbolID
11.421 sec. 01.720 sec. 01.157 sec.
 

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

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

fxsaber, 2018.11.16 01:33

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

До теста пришла в голову идея, как ускорить Virtual, чтобы количество ордеров почти не влияло на производительность.

Спустя пять лет взялся за реализацию. Тестовый советник.

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

#define VIRTUAL_TESTER // Запуск в виртуальном торговом окружении OnTickMulti
#define VIRTUAL_LIMITS_TP_SLIPPAGE // Лимитники и TP исполняются по первой цене акцепта - положительные проскальзывания
#define VIRTUAL_CLOSEALL_BYEND // Закрывает принудительно все ордера в конце тестирования
#define VIRTUAL_ALTERNATIVE // Альтернативная скорость расчетов
#include <fxsaber\Virtual\Virtual.mqh> // https://www.mql5.com/ru/code/22577

input int inRange = 0;           // 1 .. 5
input int inAmount = 1;          // Amount of positions
input int iTakeProfit  = 100000; // TakeProfit in pips

void OnInit()
{  
  const double Price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
  
  for (int i = 0; i < inAmount; i++)
    OrderSend(_Symbol, OP_BUY, 0.1, Price, 100, 0, Price + iTakeProfit * _Point);
}

void OnTick() {}

double OnTester() { return(AccountInfoDouble(ACCOUNT_BALANCE)); }


Этот советник открывает inAmount-количество позиций в самом начале и больше ничего не делает.


Сравнительная таблица длительности расчетов.

inAmount  MT5-Tester MT5-Tester + Virtual MT5-Tester + Virtual (Alternative)
 1 00:03.813 00:02.435 00:02.187
 3 00:06.882 00:02.593 00:02.155
 5 00:08.485 00:02.827 00:02.185
 10 00:12.593 00:03.364 00:02.163
 30 00:26.484 00:05.393 00:02.170
 50 00:40.434 00:07.317 00:02.204
 100 01:14.323 00:12.048 00:02.191
Получилось.
 
fxsaber #:

Получилось.

Такой же трюк не сработает для мультивалютного варианта?

 
Andrey Khatimlianskii #:

Такой же трюк не сработает для мультивалютного варианта?

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

 

Баг на граничных значениях:

 #include <MT4Orders.mqh>
 #define MAX_ORDERS 20000 // предлагаю сделать динамический массив для активных ордеров, как для истории. В данном тесте 2000 было мало. 

//#define ORDER_COMMISSION 1 // Задание комиссии (OrderCommission() = OrderLots() * (ORDER_COMMISSION)), включая динамический вариант.
//#define ORDER_COMMISSION_PERCENT 0.0016 // 0.0016% за лот. Будет работать, если ORDER_COMMISSION в пунктах не объявлен
#define ORDER_COMMISSION_PERCENT_NUMS 2 // 1 - комиссия берется при входе, 2 - комиссия берется 2 раза и при входе и при выходе
#define CALC_SWAP 0 // расчитывать своп в указанное время ролловера в секундах от 0:00. Например ролловер в 3:00:00 = 3*60*60 = 10800. Значения свопов для каждого инструмента берутся из спецификации символа на текущий момент. Свопы регулярно меняются, вчера они модли быть другими, т.е. результат может не совпадать с вчерашним тестированием.
#define TO_ACCOUNT_CURRENCY //- пересчитать прибыль в валюту депозита. Иначе, считает в валюте профита инструмента 
#define CONTROL_TRADE_SESSION //будет проверять каждый тик на открытость рынка, чтобы не было сделок, если рынок закрыт, а тики есть. Можно не использовать, если котировочная и торговая сессии совпадают.
//#define CONTROL_EQUITY // расчитывать эквити на каждом тике, замедление от 10% до 400% при пересчете в валюту аккаунта, если много сделок и живут они долго


#define VIRTUAL_TESTER // Запуск в виртуальном торговом окружении
#define VIRTUAL_LIMITS_TP_SLIPPAGE // Лимитники и TP исполняются по первой цене акцепта - положительные проскальзывания
#define VIRTUAL_CLOSEALL_BYEND // Закрывает принудительно все ордера в конце тестирования
#include <fxsaber\Virtual\Virtual.mqh>

#define REPORT_TESTER             // В тестере будут автоматически записываться отчеты
#define REPORT_BROWSER            // Создание отчета с запуском браузера - требует разрешения DLL.
#include <Report.mqh>

input int inAmount = 10;
input int inOffset = 5;
input int inRange = 0;

void OnInit(){}
void OnTick()
{
   strategy ();
}

int TimeHour     ( datetime time ){return((int)((time / 3600) % 24));}//current hour in day. 3600 sec in hour


void strategy (){
  string Symb = _Symbol;
  MqlTick Tick;
  
  if (SymbolInfoTick(Symb, Tick))
  {    
    double point = SymbolInfoDouble(Symb, SYMBOL_POINT);
    const double Offset = inOffset * point;

    if(TimeHour(TimeCurrent())<23 && TimeHour(TimeCurrent())>0 ){return;} //совершаем операции с 0 до 1 и с 23 до 0
    OrderSend(Symb, OP_BUYLIMIT, 10,  Tick.ask -  Offset, 0,Tick.ask -  Offset, Tick.ask -  Offset, NULL, 1100);
    OrderSend(Symb, OP_SELLLIMIT, 10, Tick.bid +  Offset, 0,Tick.bid +  Offset, Tick.bid +  Offset, NULL, 1100);
  }
 }

Открывает лимитные ордера с ТП=СЛ=цене открытия.
Тест на ДЦ MetaQuotes Demo по EURUSD

Тестер МQ: баланс 999588350.00 LengthTime = 03:43:30.820

Виртуальный тестер:
Ч

Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене. Баланс другой.

Сделал корректировки:

  
    virtual TICKET_TYPE OrderSend( const string&, const int &Type, const double &dVolume, const double &Price,

....
надо оба вызова проверки закомментировать/удалить:

        //this.IsChange(); //- сразу закроет если ТП/СЛ сработают, тестер МК на тике открытия не закрывает
      }
      //else
      //  this.Check(); //закроет лимитки по ТП/СЛ открывшиеся на этом же тике, тестер МК на тике открытия не закрывает

И

  void Check( void )
  {
    _VC
   
    if (this.IsChange())// if вместо while
      if (this.Netting)
        this.CloseBy();

    return;
  }

Время жизни сделок стало не нулевым.

Далее выяснил, что тестер МУ отклоняет сделки по макс. числу лимитных ордеров для аккаунта = 200

Правки

virtual TICKET_TYPE OrderSend( 

...
    if(Type > 1 ){if(MAX_LIMIT_ORDERS == Current_LIMIT_ORDERS){ return Res; }else{ Current_LIMIT_ORDERS++; }}
  bool IsChange( void )
  {
...
    int PrevType;//для счетчика макс числа ордеров
    
    for (int i = 0; i < this.AmountOrders; i++)
    {
      PrevType = this.Orders[i].GetType();
      
      Res |= this.Orders[i].IsChange(this.CurrentTick) && !this.Orders[i].IsClosed();

      if (this.Orders[i].IsClosed())
      {
...
        this.AddHistoryOrder(this.Orders[i]);// можно нормализовать не тут, а до переноса в историю, тогда можно не нормализовать профит выше отдельно
      }
      else
      {
      
      if(this.Orders[i].GetType() != PrevType){ Current_LIMIT_ORDERS--; }// счетчик макс числа ордеров
....
И при создании объекта Orders считал макс число для аккаунта и установил 0 для счетчика
       this.MAX_LIMIT_ORDERS = (int) AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);
       this.Current_LIMIT_ORDERS = 0;
Стало точно как в тестере МQ
 
Forester #:

Виртуальный тестер:

Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене.

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


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


В Virtual этого бага нет.

Стало точно как в тестере МQ

Не уверен, что нужна такая цель.

 
fxsaber #:

Т.е. автоматический SL не срабатывает, а ручной - без проблем.

Часто практикуются скрытые SL. Так вот оказывается, что скрытый (сам делает OrderClose при достижении) будет в Тестере срабатывать чаще, чем явный SL.

Т.е. несовпадение результатов в Тестере при переходах между режимами скрытый/явный SL.

 

А так же

  virtual bool OrderModify( const long Ticket, const double &Price, const double &SL, const double &TP, const datetime &Expiration )  {
    //  this.Check();//закроет лимитки по ТП/СЛ открывшиеся на этом же тике, тестер МК на тике открытия не закрывает
  virtual bool OrderDelete( const long Ticket ) {
....
      if (Res)
      {
        Current_LIMIT_ORDERS--;// счетчик макс числа ордеров
        
        //this.IsChange();// закроет лимитки по ТП/СЛ открывшиеся на этом же тике, тестер МК на тике открытия не закрывает
     


В итоге получим точное совпадение с тестером МQ по всем перечисленным ниже тестам.  Анимация результатов обоих тестеров: (тот, что с комментариями - тестер MQ, отличия только из за перестановки строк сделок закрытых на одной миллисекунде)


Думаю можно такой советник считать полной проверкой всех возможных вариантов торговых операций (кроме OrderCloseBy - может вы добавите такую проверку?)

 #include <MT4Orders.mqh>
 #define MAX_ORDERS 20000 // предлагаю сделать динамический массив для активных ордеров, как для истории. В данном тесте 2000 было мало. 

//#define ORDER_COMMISSION 1 // Задание комиссии (OrderCommission() = OrderLots() * (ORDER_COMMISSION)), включая динамический вариант.
//#define ORDER_COMMISSION_PERCENT 0.0016 // 0.0016% за лот. Будет работать, если ORDER_COMMISSION в пунктах не объявлен
#define ORDER_COMMISSION_PERCENT_NUMS 2 // 1 - комиссия берется при входе, 2 - комиссия берется 2 раза и при входе и при выходе
#define CALC_SWAP 0 // расчитывать своп в указанное время ролловера в секундах от 0:00. Например ролловер в 3:00:00 = 3*60*60 = 10800. Значения свопов для каждого инструмента берутся из спецификации символа на текущий момент. Свопы регулярно меняются, вчера они модли быть другими, т.е. результат может не совпадать с вчерашним тестированием.
#define TO_ACCOUNT_CURRENCY //- пересчитать прибыль в валюту депозита. Иначе, считает в валюте профита инструмента 
#define CONTROL_TRADE_SESSION //будет проверять каждый тик на открытость рынка, чтобы не было сделок, если рынок закрыт, а тики есть. Можно не использовать, если котировочная и торговая сессии совпадают.
//#define CONTROL_EQUITY // расчитывать эквити на каждом тике, замедление от 10% до 400% при пересчете в валюту аккаунта, если много сделок и живут они долго


#define VIRTUAL_TESTER // Запуск в виртуальном торговом окружении
#define VIRTUAL_LIMITS_TP_SLIPPAGE // Лимитники и TP исполняются по первой цене акцепта - положительные проскальзывания
#define VIRTUAL_CLOSEALL_BYEND // Закрывает принудительно все ордера в конце тестирования
#include <fxsaber\Virtual\Virtual.mqh>

#define REPORT_TESTER             // В тестере будут автоматически записываться отчеты
#define REPORT_BROWSER            // Создание отчета с запуском браузера - требует разрешения DLL.
//#include <Report.mqh>
#include <MT4Orders_QuickReport.mqh>//

input int inAmount = 10;
input int inOffset = 5;
input int inRange = 0;

bool OrdersBuy[];
bool OrdersSell[];
bool OrdersBuyStop[];
bool OrdersSellStop[];

void OnInit()
{
  ArrayResize(OrdersBuy, inAmount + 1);
  ArrayResize(OrdersSell, inAmount + 1);
  ArrayResize(OrdersBuyStop, inAmount + 1);
  ArrayResize(OrdersSellStop, inAmount + 1);
}


void OnTick()
{
   strategy ();
}


double OnTester() { Print("OnTester main");return(AccountInfoDouble(ACCOUNT_BALANCE)); }

void OnDeinit(const int  reason ){
   Print("OnDeinit main");
   QuickReport("report", true);
  // for (int v = 0 ; v <= VIRTUAL::Total(); v++){ if (VIRTUAL::SelectByIndex(v)){ Print("AccountBalance = ",AccountBalance(), "   AccountEquity = ",AccountEquity()); }}
}

int TimeHour     ( datetime time ){return((int)((time / 3600) % 24));}//current hour in day. 3600 sec in hour


void strategy (){
  string Symb = _Symbol;
  MqlTick Tick;
  
  if (SymbolInfoTick(Symb, Tick))
  {    
    double sl, tp, point = SymbolInfoDouble(Symb, SYMBOL_POINT);
    const double Offset = inOffset * point;

    ArrayInitialize(OrdersBuy, false);
    ArrayInitialize(OrdersSell, false);
    ArrayInitialize(OrdersBuyStop, false);
    ArrayInitialize(OrdersSellStop, false);

    for (uint i = OrdersTotal(); (bool)i--;)
      if (OrderSelect(i, SELECT_BY_POS))         
      {
        ulong Magic = OrderMagicNumber();
        if(Magic > 0 && Magic < 100 ){
           switch (OrderType())
           {
             case OP_BUY:
               OrderModify(OrderTicket(), OrderOpenPrice(), 0, Tick.bid + Magic * Offset, 0);
               OrdersBuy[Magic] = true;
               
               break;
             case OP_SELL:
               OrderModify(OrderTicket(), OrderOpenPrice(), 0, Tick.ask - Magic * Offset, 0);
               OrdersSell[Magic] = true;
               
               break;
             case OP_BUYLIMIT:
               OrderModify(OrderTicket(), Tick.ask - Magic * Offset, 0, 0, 0);
               OrdersBuy[Magic] = true;
               
               break;
             case OP_SELLLIMIT:          
               OrderModify(OrderTicket(), Tick.bid + Magic * Offset, 0, 0, 0);
               OrdersSell[Magic] = true;
               
               break;
           }
         }
         
        if(Magic > 100 && Magic < 200 ){
           Magic = Magic - 100;
           switch (OrderType())
           { 
             case OP_BUY:
               OrderModify(OrderTicket(), OrderOpenPrice(), 0, Tick.bid + Magic * Offset, 0);
               OrdersBuyStop[Magic] = true;
               
               break;
             case OP_SELL:
               OrderModify(OrderTicket(), OrderOpenPrice(), 0, Tick.ask - Magic * Offset, 0);
               OrdersSellStop[Magic] = true;
               
               break;
             case OP_BUYSTOP:
               OrderModify(OrderTicket(), Tick.ask + Magic * Offset, 0, 0, 0);
               OrdersBuyStop[Magic] = true;
               
               break;
             case OP_SELLSTOP:          
               OrderModify(OrderTicket(), Tick.bid - Magic * Offset, 0, 0, 0);
               OrdersSellStop[Magic] = true;
               
               break;
           }
           
         }
         if(Magic == 1001 ){//проверка полного закрытия
           OrderClose(OrderTicket(), OrderLots(), (OrderType()==OP_BUY ? Tick.bid : Tick.ask), 0 ) ;//проверка полного закрытия - работает
         }
         
         //проверка частичного закрытия на разных тиках
         if(Magic == 1002 ){
           double Lots = OrderLots();
           if(Lots==10){//первичный лот - закрыть 25%
               double LotsDel1=NormalizeDouble(Lots/4, 2);// закрыть 25% от первоначального лота
               OrderClose(OrderTicket(), LotsDel1, (OrderType()==OP_BUY ? Tick.bid : Tick.ask), 0 ) ;//проверка частичного закрытия
           }else{ //остаток закрыть на следущем тике
              OrderClose(OrderTicket(), Lots, (OrderType()==OP_BUY ? Tick.bid : Tick.ask), 0 ) ;//проверка полного закрытия
           }
         }
            
         //проверка частичного закрытия OrderClose() на том же самом тике. В вирт.  тестере не работает - продит множество ордеров с 1/2 лота. 
         //Т.к. после первого OrderSend остатку создастся новый тикет и старый тикет не будет найден при втором OrderSend, в итоге создастся 3-й тикет.
         if(Magic == 1003 ){
           double Lots = OrderLots();
           double LotsDel1=NormalizeDouble(Lots/3, 2);// закрыть 33% от первоначального лота
           OrderClose(OrderTicket(), LotsDel1,                         (OrderType()==OP_BUY ? Tick.bid : Tick.ask), 0 ) ;//проверка частичного закрытия
           OrderClose(OrderTicket(), NormalizeDouble(Lots-LotsDel1,2), (OrderType()==OP_BUY ? Tick.bid : Tick.ask), 0 ) ;//проверка полного закрытия остатка на том же тике - тут сбой
         }
         
         if(Magic == 1004 ){
           //OrderCloseBy(); //проверить бы
         }
         if(Magic == 1005 ){
            OrderDelete(OrderTicket());
         }

      }
     
    if(TimeHour(TimeCurrent())<23 && TimeHour(TimeCurrent())>0 ){return;} //совершаем операции с 0 до 1 и с 23 до 0
 
    for (int i = 1; i <= inAmount; i++)
    {
      if (!OrdersBuy[i])
        OrderSend(Symb, OP_BUYLIMIT, 10, Tick.ask - i * Offset, 0, 0, 0, NULL, i);

      if (!OrdersSell[i])
        OrderSend(Symb, OP_SELLLIMIT, 10, Tick.bid + i * Offset, 0, 0, 0, NULL, i);
      // 
      if (!OrdersBuyStop[i])
        OrderSend(Symb, OP_BUYSTOP, 10, Tick.ask + i * Offset, 0, 0, 0, NULL, i+100);

      if (!OrdersSellStop[i])
        OrderSend(Symb, OP_SELLSTOP, 10, Tick.bid - i * Offset, 0, 0, 0, NULL, i+100);
      
    }  
//проверка лимитных ордеров с ТП=СЛ=цене открытия.
    OrderSend(Symb, OP_BUYLIMIT, 11,  Tick.ask -  Offset, 0,Tick.ask -  Offset, Tick.ask -  Offset, NULL, 1100);
    OrderSend(Symb, OP_SELLLIMIT, 11, Tick.bid +  Offset, 0,Tick.bid +  Offset, Tick.bid +  Offset, NULL, 1100);
    
//проверка закрытия лимитных ордеров OrderDelete()
    OrderSend(Symb, OP_BUYLIMIT, 12,  Tick.ask -  Offset, 0,Tick.ask -  Offset, Tick.ask -  Offset, NULL, 1005);
    OrderSend(Symb, OP_SELLLIMIT, 12, Tick.bid +  Offset, 0,Tick.bid +  Offset, Tick.bid +  Offset, NULL, 1005);
      

//обычные ордера с ТП/СЛ = Offset Не будут модифицироваться, сработают по ТП/СЛ
    sl=Tick.bid-Offset; tp=Tick.ask+Offset;
    OrderSend(Symb, OP_BUY,  0.01, Tick.ask, 0, sl, tp,  NULL, 1000);//
    sl=Tick.ask+Offset; tp=Tick.bid-Offset;
    OrderSend(Symb, OP_SELL, 0.1, Tick.bid, 0, sl, tp,  NULL, 1000);//

//проверка ТП/СЛ на границе спреда. В виртуальном тестере - ок. У тестера MQ бывают сбои на 1-х сделках теста https://www.mql5.com/ru/forum/455977/page36#comment_51246904&nbsp;&nbsp; и   https://www.mql5.com/ru/forum/455977/page36#comment_51248196
    OrderSend(Symb, OP_BUY,  10, Tick.ask, 0, Tick.bid, Tick.bid,  NULL, 1000);//ТП/СЛ на границе спреда 
    OrderSend(Symb, OP_SELL, 10, Tick.bid, 0, Tick.ask, Tick.ask,  NULL, 1000);//ТП/СЛ на границе спреда 
  
//проверка полного закрытия OrderClose()
    OrderSend(Symb, OP_BUY,  10, Tick.ask, 0, 0, 0,  NULL, 1001);
    OrderSend(Symb, OP_SELL, 10, Tick.bid, 0, 0, 0,  NULL, 1001);


    //проверка частичного закрытия OrderClose() // в вирт.  тестере не работает - продит множество ордеров с 1/2 лота
    sl=Tick.bid-Offset; tp=Tick.ask+Offset;
    OrderSend(Symb, OP_BUY,  10, Tick.ask, 0, 0, 0,  NULL, 1002);
    sl=Tick.ask+Offset; tp=Tick.bid-Offset;
    OrderSend(Symb, OP_SELL, 10, Tick.bid, 0, 0, 0,  NULL, 1002);// 

    //проверка частичного закрытия OrderClose() на том же самом тике. В вирт.  тестере не работает - продит множество ордеров с 1/2 лота. 
    //Т.к. после первого OrderSend остатку создастся новый тикет и старый тикет не будет найден при втором OrderSend, в итоге создастся 3-й тикет.
/*
    sl=Tick.bid-Offset; tp=Tick.ask+Offset;
    OrderSend(Symb, OP_BUY,  0.4, Tick.ask, 0, 0, 0,  NULL, 1003);
    sl=Tick.ask+Offset; tp=Tick.bid-Offset;
    OrderSend(Symb, OP_SELL, 0.4, Tick.bid, 0, 0, 0,  NULL, 1003);// 
*/
  }
    

}



По дате обновления вижу, что вы еще не добавили в свой код пересчет прибыли в валюту депозита, комиссии в % за лот, ежедневные свопы по текущей цене, о которых писал тут https://www.mql5.com/ru/blogs/post/755500.

Вот моя последняя версия библиотеки с ними и с сегодняшними корректировками.

Файлы:
Virtual.zip  57 kb
 
fxsaber #:

Стало точно как в тестере МQ

Не уверен, что нужна такая цель.

Лучше конечно как правильнее. Можно сделать переключатель правильно/как в теcтере MQ. Думаю второй вариант тоже будет интересен.

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