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>
inputint inAmount = 3;
inputint inOffset = 5;
inputint inRange = 0;
bool OrdersBuy[];
bool OrdersSell[];
bool OrdersBuyStop[];
bool OrdersSellStop[];
double STOPS_LEVEL_,FREEZE_LEVEL_;
voidOnInit()
{
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);
}
voidOnTick()
{
VIRTUAL::SelectByIndex(0); strategy ();
VIRTUAL::SelectByIndex(1); VIRTUAL::NewTick(); strategy ();// Добавили тик в выбранное торговое окружение и запустили расчет сратегии// VIRTUAL::SelectByIndex(2); VIRTUAL::NewTick(); strategy ();// Добавили тик в выбранное торговое окружение
}
doubleOnTester() {
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_BYENDfor (int v = 0 ; v <= VIRTUAL::Total(); v++){if (VIRTUAL::SelectByIndex(v)){QuickReport("report_"+(string)v, true, v);}} //сохранить и показать отчетreturn(AccountInfoDouble(ACCOUNT_BALANCE));
}
voidOnDeinit(constint 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 hourvoid strategy (){
string Symb = _Symbol;
MqlTick Tick;
if (SymbolInfoTick(Symb, Tick))
{
double open, sl, tp, point = SymbolInfoDouble(Symb, SYMBOL_POINT);
constdouble 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 до 0for (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 и https://www.mql5.com/ru/forum/455977/page36#comment_51248196OrderSend(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');}//время в мс - в строку
При использовании Virtual и перед запросом времени TimeCurrent() Tick.time Tick.time_msc
нужно переключаться на основной тестер командой VIRTUAL::SelectByIndex(0);. Иначе ответ даст виртуальный тестер, а он заменит ответы этих функций на время последнего тика зашедшего в Virtual, а не только что пришедшего тика. Ниже распечатка Print(TimeCurrent()," ",TimeTradeServer()," ",TimeLocal()," ",Tick.time," ",MscToString(Tick.time_msc));
Мне это нужно было для контроля торговой сессии, на сервере где она ограничена с 00:15 до 23:55. Первый день торгуется нормально, потом возвращается время последнего тика первого дня и все время тестер думает, что он вне торговой сессии. TimeTradeServer() и TimeLocal() совпадают, т.к. не модифицируются в Virtual.
Если переключить на основной тестер ( VIRTUAL::SelectByIndex(0); ), то время получаем от реальных тиков и контроль торговой сессии проходит нормально. Код использовался из примера в описании библиотеки. Вот модифицированный OnTick():
voidOnTick()
{
staticconstint 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(); // торговать основным тестером MQif (VIRTUAL::SelectByIndex(1)) {VIRTUAL::NewTick(); System(); }// торговать виртуальным тестером
}
полный код теста приложил, если интересно проверить. Тестировал на ДЦ Ал-ри с торговой сессией с 00:15 до 23:55, но тики приходят и в закрытое время, поэтому их надо фильтровать.
Библиотека последней версии от 2023.12.17 - сделки/пипсы совпадают в обоих тестерах. Но эта версия не считает комиссии.
Мой вариант библиотеки с комиссиями из поста выше не совпадает по сделкам/пипсам, буду разбираться...
нужно переключаться на основной тестер командой VIRTUAL::SelectByIndex(0);.
voidOnTick()
{
staticconstbool 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();
}
Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.
Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.
Понятно. В общем есть варианты переключения. Главное знать, что переключать надо.
В моей версии забыл отключить проверку 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);}} }
voidOnDeinit(constint 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)); // закрыть незавершенные сделки по цене последнего тика, как в тестере
Проверил замедление Тестера при переходе от моносимвольного режима в мультисимвольный. Делал это не совсем идеально - советник пустышка.
Мультисимвольный советник.
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 миллионов пустых мультивалютных тиков в секунду.
В общее пользование выложен исходник тикового тестера. Разберем его особенности. Лаконичность. Все спрятано в обертку, поэтому весь исходник выглядит так. #property script_show_inputs
Да, в одной из версий добавил, из за отличий. Но потом откатил. Решил, что без них мне нужнее. Хотя можно сделать через переключатель. Добавлю сегодня-завтра.
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 часа.
При использовании 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():
полный код теста приложил, если интересно проверить. Тестировал на ДЦ Ал-ри с торговой сессией с 00:15 до 23:55, но тики приходят и в закрытое время, поэтому их надо фильтровать.
Библиотека последней версии от 2023.12.17 - сделки/пипсы совпадают в обоих тестерах. Но эта версия не считает комиссии.
Мой вариант библиотеки с комиссиями из поста выше не совпадает по сделкам/пипсам, буду разбираться...
нужно переключаться на основной тестер командой VIRTUAL::SelectByIndex(0);.
Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.
Этот макрос переключит на нулевое окружение, в нем выполнит функцию и вернет значение. Затем вернется обратно в то окружение, которое было выбрано до макроса.
В моей версии забыл отключить проверку Slippage для рыночного исполнения. Отключил. Теперь тоже точно совпадает по сделкам. И по свопам и по комиссиям.
GIF-ка:
Так же добавил вызов VIRTUAL::Stop(Tick); с передаваемым тиком. Чтобы в конце теста закрытие было по цене и времени последнего тика, который при включенной CONTROL_TRADE_SESSION был бы отклонен обычной VIRTUAL::Stop(); .
С этим вариантом закрытие будет как в тестере MQ в итоге будет точное совпадение по балансу, как на скриншоте выше.
Используется так:
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);}}
}
Теперь тоже точно совпадает по сделкам. И по свопам и по комиссиям.
Используется так:
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Библиотеки: OnTickMulti
fxsaber, 2024.03.19 09:37
Проверил замедление Тестера при переходе от моносимвольного режима в мультисимвольный. Делал это не совсем идеально - советник пустышка.
Мультисимвольный советник.
Производительность слабо зависит от количества символов, но при этом ниже в три раза.
Сделал мультивалютный Тестер в Virtual и повторил для него замер производительности по той же методике, чтобы можно было сравнить MT5-Tester VS Virtual-Tester.
Подопытная ТС - пустышка, поэтому показатели максимальные из возможных и четкие выводы делать сложно.
Конечно, многократное ускорение налицо (правые столбцы таблиц): 17-83 миллионов пустых мультивалютных тиков в секунду.
ЗЫ Так замерял.
Сделал мультивалютный Тестер в Virtual