Такой же трюк не сработает для мультивалютного варианта?
Сработает, но с меньшим эффектом. Оставил оба режима, потому что сложно предсказать, для каких целей может быть выгоднее один режим расчета, а для каких - другой.
#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>
inputint inAmount = 10;
inputint inOffset = 5;
inputint inRange = 0;
voidOnInit(){}
voidOnTick()
{
strategy ();
}
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 point = SymbolInfoDouble(Symb, SYMBOL_POINT);
constdouble Offset = inOffset * point;
if(TimeHour(TimeCurrent())<23 && TimeHour(TimeCurrent())>0 ){return;} //совершаем операции с 0 до 1 и с 23 до 0OrderSend(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
Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене. Баланс другой.
Сделал корректировки:
virtual TICKET_TYPE OrderSend( conststring&, constint &Type, constdouble &dVolume, constdouble &Price,
....
надо оба вызова проверки закомментировать/удалить://this.IsChange(); //- сразу закроет если ТП/СЛ сработают, тестер МК на тике открытия не закрывает
}
//else// this.Check(); //закроет лимитки по ТП/СЛ открывшиеся на этом же тике, тестер МК на тике открытия не закрывает
И
void Check( void )
{
_VC
if (this.IsChange())// if вместо whileif (this.Netting)
this.CloseBy();
return;
}
Время жизни сделок стало не нулевым.
Далее выяснил, что тестер МУ отклоняет сделки по макс. числу лимитных ордеров для аккаунта = 200
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 для счетчика
Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене.
Виртуал правильно сделал - открыл и сразу закрыл.
В Тестере баг. На тике срабатывания лимитника открывается позиция, у которой SL удовтлетворяет текущему тику. Тестер/Терминал ее не закроет на этом тике по SL. При этом вручную можете его закрыть на этом же тике. Т.е. автоматический SL не срабатывает, а ручной - без проблем.
virtualbool OrderModify( constlong Ticket, constdouble &Price, constdouble &SL, constdouble &TP, constdatetime &Expiration ) {
// this.Check();//закроет лимитки по ТП/СЛ открывшиеся на этом же тике, тестер МК на тике открытия не закрывает
virtualbool OrderDelete( constlong 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>//inputint inAmount = 10;
inputint inOffset = 5;
inputint inRange = 0;
bool OrdersBuy[];
bool OrdersSell[];
bool OrdersBuyStop[];
bool OrdersSellStop[];
voidOnInit()
{
ArrayResize(OrdersBuy, inAmount + 1);
ArrayResize(OrdersSell, inAmount + 1);
ArrayResize(OrdersBuyStop, inAmount + 1);
ArrayResize(OrdersSellStop, inAmount + 1);
}
voidOnTick()
{
strategy ();
}
doubleOnTester() { Print("OnTester main");return(AccountInfoDouble(ACCOUNT_BALANCE)); }
voidOnDeinit(constint 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 hourvoid strategy (){
string Symb = _Symbol;
MqlTick Tick;
if (SymbolInfoTick(Symb, Tick))
{
double 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(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);
}
//проверка лимитных ордеров с ТП=СЛ=цене открытия.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 и 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() // в вирт. тестере не работает - продит множество ордеров с 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.
Вот моя последняя версия библиотеки с ними и с сегодняшними корректировками.
Для авторизации и пользования сайтом MQL5.com необходимо разрешить использование файлов Сookie.
Пожалуйста, включите в вашем браузере данную настройку, иначе вы не сможете авторизоваться.
Вы вошли в аккаунт. Двухфакторная аутентификация отключена. Настройте её заново и отсканируйте новый QR-код или продолжите с отключённой двухфакторной аутентификацией.
https://www.mql5.com/ru/blogs/post/755348
Честно говоря, ожидал большей производительности от Virtual. Видимо, непродуманная в этом плане архитектура.
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Библиотеки: Virtual
fxsaber, 2023.12.10 13:33
Выглядит, как самойбийство такая конструкция на каждом тике в мультивалютном советнике.
Возможность избавиться от дорогих string в пользу SymbolID дала десятикратное ускорение расчета мультивалютников в MT5-тестере.
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Библиотеки: Virtual
fxsaber, 2018.11.16 01:33
Любители сеточных ТС могут увеличить скорость оптимизации почти на порядок, если будут использовать Virtual.
До теста пришла в голову идея, как ускорить Virtual, чтобы количество ордеров почти не влияло на производительность.
Спустя пять лет взялся за реализацию. Тестовый советник.
Этот советник открывает inAmount-количество позиций в самом начале и больше ничего не делает.
Сравнительная таблица длительности расчетов.
Получилось.
Такой же трюк не сработает для мультивалютного варианта?
Такой же трюк не сработает для мультивалютного варианта?
Сработает, но с меньшим эффектом. Оставил оба режима, потому что сложно предсказать, для каких целей может быть выгоднее один режим расчета, а для каких - другой.
Баг на граничных значениях:
Открывает лимитные ордера с ТП=СЛ=цене открытия.

Тест на ДЦ MetaQuotes Demo по EURUSD
Тестер МQ: баланс 999588350.00 LengthTime = 03:43:30.820
Виртуальный тестер:
Ч
Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене. Баланс другой.
Сделал корректировки:
И
Время жизни сделок стало не нулевым.
И при создании объекта Orders считал макс число для аккаунта и установил 0 для счетчикаДалее выяснил, что тестер МУ отклоняет сделки по макс. числу лимитных ордеров для аккаунта = 200
Правки
Стало точно как в тестере МQ
Виртуальный тестер:
Число сделок больше, время жизни всех сделок = 0, т.е. закрылись сразу после активации лимитных ордеров по другой цене.
Виртуал правильно сделал - открыл и сразу закрыл.
В Тестере баг. На тике срабатывания лимитника открывается позиция, у которой SL удовтлетворяет текущему тику. Тестер/Терминал ее не закроет на этом тике по SL. При этом вручную можете его закрыть на этом же тике. Т.е. автоматический SL не срабатывает, а ручной - без проблем.
В Virtual этого бага нет.
Стало точно как в тестере МQ
Не уверен, что нужна такая цель.
Т.е. автоматический SL не срабатывает, а ручной - без проблем.
Часто практикуются скрытые SL. Так вот оказывается, что скрытый (сам делает OrderClose при достижении) будет в Тестере срабатывать чаще, чем явный SL.
Т.е. несовпадение результатов в Тестере при переходах между режимами скрытый/явный SL.
А так же
В итоге получим точное совпадение с тестером МQ по всем перечисленным ниже тестам. Анимация результатов обоих тестеров: (тот, что с комментариями - тестер MQ, отличия только из за перестановки строк сделок закрытых на одной миллисекунде)

Думаю можно такой советник считать полной проверкой всех возможных вариантов торговых операций (кроме OrderCloseBy - может вы добавите такую проверку?)
По дате обновления вижу, что вы еще не добавили в свой код пересчет прибыли в валюту депозита, комиссии в % за лот, ежедневные свопы по текущей цене, о которых писал тут https://www.mql5.com/ru/blogs/post/755500.
Вот моя последняя версия библиотеки с ними и с сегодняшними корректировками.
Стало точно как в тестере МQ
Не уверен, что нужна такая цель.
Лучше конечно как правильнее. Можно сделать переключатель правильно/как в теcтере MQ. Думаю второй вариант тоже будет интересен.