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

 
Раз уж всё добавлять, тогда добавить и неидеальное исполнение с лагом в Х мс, иногда у себя прогоняю тесты с ним.
 
Forester #:
Да, в одной из версий добавил, из за отличий. Но потом откатил. Решил, что без них мне нужнее. Хотя можно сделать через переключатель. Добавлю сегодня-завтра.
Expiration тоже легко добавить.
И Slippage наверное тоже просто, еще не думал. Да и не знаю как должно быть. Это, если следующий тик < запрошенной цены покупки + Slippage, то разрешаем?

Добавил FreezeLevel/StopLevel.

Добавил Expiration - без проблем.

Добавил контроль числа открытых лимитных ордеров и сравнение с ACCOUNT_LIMIT_ORDERS. Работает хорошо, кроме варианта, если используется OrderDelete(). Если много ордеров, то из за OrderDelete () бывает сбой, причину не выяснил. Если OrderDelete() не используется, то совпадает с тестером МК.
Этот контроль можно отключать через //#define DISABLE_ACCOUNT_LIMIT_ORDERS

Добавил Slippagе. Терминал их использует в Request и  Instant Execution. Но в тестере похоже, что  Slippagе не закодировано. https://www.mql5.com/ru/forum/459334/page2#comment_51685246
Я закодировал, но т.к. сравнить не с чем, то можно считать это экспериментальной/непроверенной опцией.

Попытался добавить задержку исполнения. Самая сложная задачка оказалась. Нужно после каждой команды ждать например 100мс, а следующую на том же тике отпралять после нее, цены к тому времени могут измениться.
Если на одном тике отправлять только 1 команду, то совпадение с тестером МК есть. Если 2 или больше (например, чтобы выставить сетку), то не совпадает. К тому же тестером МК сетки лучше не выставлять. Там закодированы только синхронные OrderSend (), т.е. ожидая после каждого OrderSend () по 100 мс, сетка на 20 ордеров выставится через 2 секунды.
Асинхроный режим сделать просто: не делать пауз между ордерами. Но сравнивать не с чем, т.е. тоже будет экспериментальная опция.
Т.к. совпадения нету, код относящийся к DELAY закомментировал. Сделал только при OrderSend (), при модификации и удалении ордеров задержку не добавлял.


Новый код проверки всех опций: тестируйте за 1 день. Это стресс тест, очень много сделок,  у меня  29-30мая 2023 на EURUSD на MQДемо около 14 тыс сделок за 2 часа.

 // отличие  - подключаем вирт тестер через SelectByIndex(), а не #define VIRTUAL_TESTER
 
 #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 DISABLE_ACCOUNT_LIMIT_ORDERS


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

#define REPORT_BROWSER            // Создание отчета с запуском браузера - требует разрешения DLL.
#define USE_highcharts //- You can download and try out all Highcharts products for free. Once your project/product is ready for launch, purchase a commercial license. https://shop.highcharts.com/
#include <MT4Orders_QuickReport.mqh>

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

bool OrdersBuy[];
bool OrdersSell[];
bool OrdersBuyStop[];
bool OrdersSellStop[];
double STOPS_LEVEL_,FREEZE_LEVEL_;
void OnInit()
{
  VIRTUAL::Create(); // Создали хэндл виртуального торгового окружения. 0 - реальное торговое окружение
  ArrayResize(OrdersBuy, inAmount + 1);
  ArrayResize(OrdersSell, inAmount + 1);
  ArrayResize(OrdersBuyStop, inAmount + 1);
  ArrayResize(OrdersSellStop, inAmount + 1);
  STOPS_LEVEL_ =  NormalizeDouble((double)::SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL ) * _Point, _Digits);
  FREEZE_LEVEL_ = NormalizeDouble((double)::SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL) * _Point, _Digits);     

}

void OnTick()
{
   VIRTUAL::SelectByIndex(0);    strategy ();

   VIRTUAL::SelectByIndex(1); VIRTUAL::NewTick(); strategy ();// Добавили тик в выбранное торговое окружение и запустили расчет сратегии
   
  // VIRTUAL::SelectByIndex(2); VIRTUAL::NewTick(); strategy ();// Добавили тик в выбранное торговое окружение

}

double OnTester() { 
   Print("OnTester main");
 #ifdef VIRTUAL_CLOSEALL_BYEND
   for (int v = 1 ; v <= VIRTUAL::Total(); v++){if (VIRTUAL::SelectByIndex(v)){ VIRTUAL::Stop();}} // VIRTUAL::NewTick();последний тик для стопов // закрыть незавершенные сделки
 #endif //VIRTUAL_CLOSEALL_BYEND
   for (int v = 0 ; v <= VIRTUAL::Total(); v++){if (VIRTUAL::SelectByIndex(v)){QuickReport("report_"+(string)v, true, v);}} //сохранить и показать отчет
   return(AccountInfoDouble(ACCOUNT_BALANCE)); 
}

void OnDeinit(const int  reason ){
   Print("OnDeinit main");

   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 open, 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(Magic == 1300 && OrderType() > 1 ){
            OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), OrderTakeProfit(), OrderExpiration() - 60);
         }

      }
     
    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);

    }


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

    // проверка экспирации отложенных ордеров
    open = Tick.ask -  Offset;
    OrderSend(Symb, OP_BUYLIMIT,  11, open, 0, open - STOPS_LEVEL_ - (100*point), open + STOPS_LEVEL_ + (100*point), NULL, 1300, Tick.time + 300); // истечение через 5 минут. И будет модифицироваться по -1 минуте за тик
    open = Tick.bid +  Offset;
    OrderSend(Symb, OP_SELLLIMIT, 11, open, 0, open + STOPS_LEVEL_ + (100*point), open - STOPS_LEVEL_ - (100*point), NULL, 1300, Tick.time + 300);
    open = Tick.ask +  Offset;
    OrderSend(Symb, OP_BUYSTOP,   11, open, 0, open - STOPS_LEVEL_ - (100*point), open + STOPS_LEVEL_ + (100*point), NULL, 1300, Tick.time + 300);
    open = Tick.bid -  Offset;
    OrderSend(Symb, OP_SELLSTOP,  11, open, 0, open + STOPS_LEVEL_ + (100*point), open - STOPS_LEVEL_ - (100*point), NULL, 1300, Tick.time + 300);


//проверка лимитных ордеров с ТП=СЛ= +-STOPS_LEVEL_
    open = Tick.ask -  Offset;
    OrderSend(Symb, OP_BUYLIMIT,  11, open, 0, open - STOPS_LEVEL_, open + STOPS_LEVEL_, NULL, 1200);
    open = Tick.bid +  Offset;
    OrderSend(Symb, OP_SELLLIMIT, 11, open, 0, open + STOPS_LEVEL_, open - STOPS_LEVEL_, NULL, 1200);
    open = Tick.ask +  Offset;
    OrderSend(Symb, OP_BUYSTOP,   11, open, 0, open - STOPS_LEVEL_, open + STOPS_LEVEL_, NULL, 1200);
    open = Tick.bid -  Offset;
    OrderSend(Symb, OP_SELLSTOP,  11, open, 0, open + STOPS_LEVEL_, open - STOPS_LEVEL_, NULL, 1200);


//проверка лимитных ордеров без ТП=СЛ
    open = Tick.ask -  Offset;
    OrderSend(Symb, OP_BUYLIMIT, 11,  open, 0, 0, 0, NULL, 1100);
    open = Tick.bid +  Offset;
    OrderSend(Symb, OP_SELLLIMIT, 11, open, 0, 0, 0, NULL, 1100);


//проверка закрытия лимитных ордеров OrderDelete()
// до inAmount=4 совпадает, после 5 не совпадает
    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);
    
/**/

    // тест Slippage=3 virtual работет правильно, MQ отклоняет все сделки, хоть цена отличается до Slippage=3 пт - баг MQ https://www.mql5.com/ru/forum/459334/page2#comment_51685246
    // Slippage должен работать с Request и Instant Execution. В Market и Exchange режимах Slippage не проверяется.
    //OrderSend(Symb, OP_BUY,  10, Tick.ask-point*2, 3, Tick.bid, Tick.bid,  NULL, 1000);//Slippage ТП/СЛ на границе спреда 
    //OrderSend(Symb, OP_SELL, 10, Tick.bid-point*1, 3, Tick.ask, Tick.ask,  NULL, 1000);//Slippage ТП/СЛ на границе спреда 
    
    //Print (MscToString(Tick.time_msc), " a: ",Tick.ask, " b: ", Tick.bid);
    //проверка DELAY - не совпадает с MQ, стопы быстро меняются и сделки отклоняются
 //   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);
    //проверка DELAY 2 - совпадает с MQ
    //OrderSend(Symb, OP_BUY,  10, Tick.ask, 0, Tick.bid-20*_Point, Tick.bid+20*_Point,  NULL, 1000);
    //OrderSend(Symb, OP_SELL, 10, Tick.bid, 0, Tick.ask+20*_Point, Tick.ask-20*_Point,  NULL, 1000);


/*
    //проверка частичного закрытия 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);// 
*/
  }
    
  /*if (SymbolInfoTick("USDCHF", Tick)){ 
    double sl, tp, point = SymbolInfoDouble(Symb, SYMBOL_POINT);
    const double Offset = inOffset * point;
    sl=Tick.bid-Offset; tp=Tick.ask+Offset;
    OrderSend("USDCHF", OP_BUY,  0.01, Tick.ask, 0, sl, tp,  NULL, 1000);//
  }*/
}

   string MscToString(long ms){return (string)(datetime)(ms/1000)+"."+IntegerToString(ms%1000, 3, '0');}//время в мс - в строку
   
Бета-версия платформы MetaTrader 5 build 4120: экспорт торгового отчета и новые методы машинного обучения в MQL5
Бета-версия платформы MetaTrader 5 build 4120: экспорт торгового отчета и новые методы машинного обучения в MQL5
  • 2024.01.10
  • www.mql5.com
В четверг 21 декабря 2023 года будет выпущена обновленная версия платформы MetaTrader 5 в бета-режиме...
Файлы:
Virtual.zip  60 kb
 

При использовании Virtual и перед запросом времени
TimeCurrent()
Tick.time
Tick.time_msc

нужно переключаться на основной тестер командой VIRTUAL::SelectByIndex(0);.
Иначе ответ даст виртуальный тестер, а он заменит ответы этих функций на время последнего тика зашедшего в Virtual, а не только что пришедшего тика. Ниже распечатка
 Print(TimeCurrent(),"  ",TimeTradeServer(),"  ",TimeLocal(),"  ",Tick.time,"  ",MscToString(Tick.time_msc));

PH    0    11:47:09.119    Core 1    2022.05.30 23:55:00   2022.05.30 23:54:57  2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:54:57  2022.05.30 23:54:57.596
LK    0    11:47:09.119    Core 1    2022.05.30 23:55:00   2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00.140
NH    0    11:47:09.119    Core 1    2022.05.30 23:55:00   isSessionTrade check
FN    0    11:47:09.119    Core 1    2022.05.30 23:55:00   Market closed. OnTick return
PQ    0    11:47:09.119    Core 1    2022.05.30 23:55:00   2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00  2022.05.30 23:55:00.140
и так все дни  до конца теста
HO    0    11:47:09.119    Core 1    2022.05.31 21:58:17   2022.05.30 23:55:00  2022.05.31 21:58:17  2022.05.31 21:58:17  2022.05.30 23:55:00  2022.05.30 23:55:00.140
...
DG    0    11:47:09.119    Core 1    2022.06.01 23:59:58   2022.05.30 23:55:00  2022.06.01 23:59:58  2022.06.01 23:59:58  2022.05.30 23:55:00  2022.05.30 23:55:00.140

Мне это нужно было для контроля торговой сессии, на сервере где она ограничена с 00:15 до 23:55. Первый день торгуется нормально, потом возвращается время последнего тика первого дня и все время тестер думает, что он вне торговой сессии.
TimeTradeServer() и TimeLocal() совпадают, т.к. не модифицируются в Virtual.

Если переключить на основной тестер ( VIRTUAL::SelectByIndex(0); ), то время получаем от реальных тиков и контроль торговой сессии проходит нормально.
Код использовался из примера в описании библиотеки. Вот модифицированный OnTick():

void OnTick()
{
  static const int handle = VIRTUAL::Create(); // Создали хэндл виртуального торгового окружения. 0 - реальное торговое окружение

  VIRTUAL::SelectByIndex(0); // переключить на основной тестер надо тут
  MqlTick Tick;
  if (!SymbolInfoTick(_Symbol, Tick) ) { return; }
  Print(TimeCurrent(),"  ",TimeTradeServer(),"  ",TimeLocal(),"  ",Tick.time,"  ",MscToString(Tick.time_msc));
  
  static TRADE_SESSIONS TradeSession(_Symbol);//получение времени откр/закр рынка и сохраниение в массивы по дням недели
  if(!TradeSession.isSessionTrade(TimeCurrent())){Print("Market closed. OnTick return");return;}//

  //VIRTUAL::SelectByIndex(0);  // не тут переключать, а до запросов времени 
  System(); // торговать основным тестером MQ
  if (VIRTUAL::SelectByIndex(1))  {VIRTUAL::NewTick();  System(); }// торговать виртуальным тестером
}

полный код теста приложил, если интересно проверить. Тестировал на ДЦ Ал-ри  с торговой сессией с 00:15 до 23:55, но тики приходят и в закрытое время, поэтому их надо фильтровать.

Библиотека последней версии от 2023.12.17 - сделки/пипсы совпадают в обоих тестерах. Но эта версия не считает комиссии.

Мой вариант библиотеки с комиссиями из поста выше не совпадает по сделкам/пипсам, буду разбираться...


Файлы:
test3.mq5  12 kb
 
Forester #:

нужно переключаться на основной тестер командой VIRTUAL::SelectByIndex(0);.

void OnTick()
{
  static const bool Init = VIRTUAL::SelectByHandle(VIRTUAL::Create()); // Создали хэндл виртуального торгового окружения. 0 - реальное торговое окружение
  static TRADE_SESSIONS TradeSession(_Symbol);//получение времени откр/закр рынка и сохраниение в массивы по дням недели
  
  if(!_V(0, TradeSession.isSessionTrade(TimeCurrent()))){Print("Market closed. OnTick return");return;}//

  VIRTUAL::NewTick();
  System();
}

Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.

 
fxsaber #:

Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.

Понятно. В общем есть варианты переключения. Главное знать, что переключать надо.

В моей версии забыл отключить проверку Slippage для рыночного исполнения. Отключил. Теперь тоже точно совпадает по сделкам. И по свопам и по комиссиям.
GIF-ка:



Так же добавил вызов VIRTUAL::Stop(Tick); с передаваемым тиком. Чтобы в конце теста закрытие было по цене и времени последнего тика, который при включенной CONTROL_TRADE_SESSION был бы отклонен обычной VIRTUAL::Stop(); .
С этим вариантом закрытие будет как в тестере MQ в итоге будет точное совпадение по балансу, как на скриншоте выше.
Используется так:
void OnDeinit(const int  reason ){
  VIRTUAL::SelectByIndex(0); // чтобы получить правильное время и тик, а не последний из Virtual
  MqlTick Tick;
  if (!SymbolInfoTick(_Symbol, Tick) ) {Print("no tick"); return; }
  Print("OnDeinit ",Tick.time," ",Tick.bid," ",Tick.ask);
   for (int v = 1 ; v <= VIRTUAL::Total(); v++){if (VIRTUAL::SelectByIndex(v)){ VIRTUAL::Stop(Tick); }} // закрыть незавершенные сделки по цене последнего тика, как в тестере
   for (int v = 0 ; v <= VIRTUAL::Total(); v++){if (VIRTUAL::SelectByIndex(v)){QuickReport("report_"+(string)v, true, v);}}
}
Файлы:
Virtual.zip  60 kb
 
Forester #:
Теперь тоже точно совпадает по сделкам. И по свопам и по комиссиям.
Отличный результат!
 
Forester #:
Используется так:
Так проще для понимания.
void OnDeinit(const int  reason ){
  MqlTick Tick;
  if (!_V(0, SymbolInfoTick(_Symbol, Tick))) {Print("no tick"); return; }
  for (int v = 1 ; v <= VIRTUAL::Total(); v++) _VI(v, VIRTUAL::Stop(Tick)); // закрыть незавершенные сделки по цене последнего тика, как в тестере
 

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

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

fxsaber, 2024.03.19 09:37

Проверил замедление Тестера при переходе от моносимвольного режима в мультисимвольный. Делал это не совсем идеально - советник пустышка.

Мультисимвольный советник.

Configuration (OnTickMulti) TimeLength (OnTickMulti) TimeLength (OnTick) AmountTicks   Performance (OnTickMulti) Performance (OnTick) 
EURUSD
3455901 1079331 9927153  2.87  9.19
EURUSD, GBPUSD
6617085 2308363
21236893  3.20  9.19
EURUSD, GBPUSD, USDJPY
9188781 3615685 33223649  3.61  9.18
EURUSD, GBPUSD, USDJPY, USDCHF
11517804 4737226 43583160  3.78  9.20
EURUSD, GBPUSD, USDJPY, USDCHF, AUDUSD
16058223 5912804 54341296  3.38  9.19

Производительность слабо зависит от количества символов, но при этом ниже в три раза.


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

Configuration (OnTickMulti) TimeLength (OnTickMulti) TimeLength (OnTick) AmountTicks   Performance (OnTickMulti) Performance (OnTick) 
EURUSD
33979 34156 2826484 83.18 82.75
EURUSD, GBPUSD
267224 73290 6113964 22.87 83.42
EURUSD, GBPUSD, USDJPY
492689 120432 9901643 20.09 82.21
EURUSD, GBPUSD, USDJPY, USDCHF
699477 154982 12813818 18.31 82.67
EURUSD, GBPUSD, USDJPY, USDCHF, AUDUSD
916237 193619 15658000 17.08 80.87


Подопытная ТС - пустышка, поэтому показатели максимальные из возможных и четкие выводы делать сложно.

Конечно, многократное ускорение налицо (правые столбцы таблиц): 17-83 миллионов пустых мультивалютных тиков в секунду.


ЗЫ Так замерял.

#include <fxsaber\Virtual\Virtual.mqh> // https://www.mql5.com/ru/code/22577

input datetime inFrom = D'2024.01.01';

#define TOSTRING(A) " " + #A + " = " + (string)(A)

#define BENCH(A)                                             \
  {                                                          \
    const ulong StartTime = GetMicrosecondCount();           \
    AmountTicks = 0;                                         \
    A;                                                       \
    const ulong Length = GetMicrosecondCount() - StartTime;  \
    const double Performance = (double)AmountTicks / Length; \
    Print(#A + ":" + TOSTRING(Length) + " mcs." +            \
          TOSTRING(AmountTicks) +                            \
          TOSTRING(Performance) + " ticks/mcs.");            \
  } 

void SummarySingles( const string &Symbols[], const TICKS_ARRAY &TicksArray[], const STRATEGY_MULTI StrategyMulti )
{
  for (uint i = ArraySize(Symbols); (bool)i--;)
    VIRTUAL::TesterMulti(Symbols[i], TicksArray[i], StrategyMulti);
}
 
void OnStart()
{
  const string Symbols[] = {"EURUSD"};
//  const string Symbols[] = {"EURUSD", "GBPUSD"};
//  const string Symbols[] = {"EURUSD", "GBPUSD", "USDJPY"};
//  const string Symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "USDCHF"};
//  const string Symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "USDCHF", "AUDUSD"};

  ArrayPrint(Symbols);
  
  TICKS_ARRAY TicksArray[];
  
  const int Size = ::ArrayResize(TicksArray, ::ArraySize(Symbols));

  for (int i = 0; i < Size; i++)
    Print(Symbols[i] + ": " + (string)CopyTicksRange(Symbols[i], TicksArray[i].Ticks, COPY_TICKS_ALL, inFrom * 1000));    
    
  for (int i = 0; i < 3; i++)
  {
    Print("--------");
    BENCH(VIRTUAL::TesterMulti(Symbols, TicksArray, OnTickMulti));
    BENCH(SummarySingles(Symbols, TicksArray, OnTickMulti));
  }
}

int AmountTicks = 0;

void OnTickMulti( const string &Symb, const int &Index )
{
  AmountTicks++;
}
 
fxsaber #:

Сделал мультивалютный Тестер в Virtual

Добавлен VirtualTester и TimeTradeServer.
Свой тиковый Тестер.
Свой тиковый Тестер.
  • www.mql5.com
В общее пользование выложен исходник  тикового тестера. Разберем его особенности. Лаконичность. Все спрятано в обертку, поэтому весь исходник выглядит так. #property script_show_inputs
Причина обращения: