Новая версия платформы MetaTrader 5 build 6230: больше возможностей AI - страница 24

Alain Verleyen #:
Параметр m3.code не инициализируется в момент вызова функции print() и может принимать любое значение.
Я знаю, что не следует использовать неинициализированные значения. Мне просто любопытно, почему там оказывается именно то значение, которое будет присвоено после print().
void OnInit()
  {
   Main3 m3;
   Inclosure in = {10, 100};
   m3 = in;
   m3.print();
   m3.code = 1212;
  }

GodBolt (x86-64 clang 23.1.0 -O3):

.LCPI0_0:
        .quad   0x4024000000000000
.LCPI0_1:
        .quad   0x4059000000000000
OnInit():
        lea     rdi, [rip + .L.str]
        movsd   xmm0, qword ptr [rip + .LCPI0_0]
        movsd   xmm1, qword ptr [rip + .LCPI0_1]
        mov     esi, 1212
        mov     al, 2
        jmp     printf@PLT

.L.str:
        .asciz  "x %.2f, y %.2f, code %i"
trader6_1 #:
GodBolt (x86-64 clang 23.1.0 -O3):

ИИ назвал это агрессивной оптимизацией. Думаю, вопрос можно считать закрытым. Главное - не забывать, что

Файлы:
screenshot.png  66 kb
Botan626 #:

поиск в этом окне стоит сделать без учета регистра для поля Описание, если в слове есть заглавная буква, поиск через строчную букву не находит символ по описанию

вот пример того о чём я говорю



b6295, если в Тестере запустить такой "советник"
void OnStart() {} // Script.


то в логе появляется такая запись

2026.10.31 19:50:37.654 Tester  "Examples\Moving Average\Moving Average.ex5" X64
2026.10.31 19:50:37.659 MQL5    wrong program type, loading of Moving Average failed
2026.10.31 19:50:39.239 Tester  GAZP_BidAsk: history data begins from 2026.10.01 00:00
2026.10.31 19:50:39.239 Tester  GAZP_BidAsk: ticks data begins from 2026.10.01 00:00
2026.10.31 19:50:39.239 Core 1  connecting to 127.0.0.1:3000
2026.10.31 19:50:39.241 Core 1  connected
2026.10.31 19:50:39.250 Core 1  authorized (agent build 6295)
2026.10.31 19:50:39.251 Tester  GAZP_BidAsk,M1 (FINAM-AO): testing of Experts\Examples\Moving Average\Moving Average.ex5 from 2026.10.16 00:00 to 2026.10.17 00:00
2026.10.31 19:50:39.258 Core 1  common synchronization completed
2026.10.31 19:50:39.269 Core 1  test Experts\Examples\Moving Average\Moving Average.ex5 on GAZP_BidAsk,M1 thread finished
2026.10.31 19:50:39.269 Core 1  prepare for shutdown
2026.10.31 19:50:39.269 Core 1  shutdown finished
2026.10.31 19:50:39.269 Core 1  login (build 6295)
2026.10.31 19:50:39.269 Core 1  account info found with currency AUD
2026.10.31 19:50:39.269 Core 1  1478 bytes of tester parameters loaded
2026.10.31 19:50:39.269 Core 1  708 bytes of input parameters loaded
2026.10.31 19:50:39.269 Core 1  calculate profit in pips, initial deposit 10000, leverage 1:500
2026.10.31 19:50:39.269 Core 1  successfully initialized
2026.10.31 19:50:39.269 Core 1  682 bytes of total initialization data received
2026.10.31 19:50:39.269 Core 1  Intel Core i7-2700K  @ 3.50GHz, 12205 MB
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk: symbol to be synchronized
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk: symbol synchronized already, 28 bytes received
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk,M1: history cached from 2026.10.01 06:50
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk,M1 (FINAM-AO): generating based on real ticks
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk,M1: testing of Experts\Examples\Moving Average\Moving Average.ex5 from 2026.10.16 00:00 to 2026.10.17 00:00 started with inputs:
2026.10.31 19:50:39.269 Core 1    inExpert=0
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk : real ticks begin from 2026.10.16 00:00:00
2026.10.31 19:50:39.269 Core 1  final balance 10000.00 pips
2026.10.31 19:50:39.269 Core 1  GAZP_BidAsk,M1: 51527 ticks, 983 bars generated. Test passed in 0:00:00.010.
2026.10.31 19:50:39.269 Core 1  112 Mb memory used including 0.94 Mb of history data, 64 Mb of tick data
2026.10.31 19:50:39.269 Core 1  log file "C:\Program Files\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20261031.log" written
2026.10.31 19:50:39.276 Core 1  connection closed

Но идет полный бэктест (можно включить Визуализатор) без советника.


Вроде, это неправильно. Но, возможно, это фишка такая.

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


Для воспроизведения создать кастомный символ.

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

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

fxsaber, 2026.10.22 22:11

#property script_show_inputs

#include <fxsaber\TicksShort\Last2BidAsk.mqh> // https://www.mql5.com/ru/code/61126

input datetime inFrom = D'2026.10.01';
input string inPostFix = "_BidAsk";

#define PRINT(A) Print(#A + " = " + (string)(A))

void OnStart()
{
  const string Name = _Symbol + inPostFix;
  
  MqlTick Ticks[];

  PRINT(CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL, inFrom * 1000));  
  PRINT(LAST2BIDASK::Convert(Ticks));

  CustomSymbolCreate(Name, NULL, _Symbol);
  SymbolSelect(Name, true);

//  CustomSymbolSetInteger(Name, SYMBOL_TRADE_CALC_MODE, SYMBOL_CALC_MODE_FOREX);
//  CustomSymbolSetInteger(Name, SYMBOL_TRADE_EXEMODE, SYMBOL_TRADE_EXECUTION_MARKET);  
  CustomSymbolSetInteger(Name, SYMBOL_CHART_MODE, SYMBOL_CHART_MODE_BID);  

  PRINT(CustomTicksReplace(Name, 0, LONG_MAX, Ticks));
  
  ChartOpen(Name, PERIOD_CURRENT);  
}

И на нем в Тестере запустить такой советник по реальным тикам.

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

#define Bid SymbolInfoDouble(_Symbol, SYMBOL_BID)

void OnTick()
{
  if (!OrdersTotal())
    Print(OrderSend(_Symbol, OP_SELL, 1, Bid, 0, 0, 0));
}


Получите такую картину - закрытие по нулевой цене.

Строка для поиска: Oshibka 173.

b6295, Тестер на биржевом символе акцептирует исполнение TakeProfit для SELL-позиции по нулевой Last-цене. При этом исполняет по Ask-цене, создавая соответствующие отрицательные проскальзывания.

Правильно акцептировать по Ask-цене.


Для воспроизведения запустить в Тестере на кастомном символе отсюда следующий советник.

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

#define Bid SymbolInfoDouble(_Symbol, SYMBOL_BID)

void OnTick()
{
  if (!OrdersTotal() && (OrdersHistoryTotal() < 5))
    Print(OrderSend(_Symbol, OP_SELLLIMIT, 1, Bid + _Point, 0, 0, Bid));
}

// Для видимости миллисекунд.
#define REPORT_TESTER             // В тестере будут автоматически записываться отчеты
#define REPORT_BROWSER            // Создание отчета с запуском браузера - требует разрешения DLL.
#include <Report.mqh> // https://www.mql5.com/ru/code/18801


ЗЫ По этой же причине для BUY-позиции TP никогда не акцептируется - нулевая Last-цена. А правильно акцептироваться по Bid-цене.

Строка для поиска: Oshibka 174.

Для разработчиков:
если корректируете работу тестера для соответствия с реальной, то 30 октября (на счете к которому дал вам доступ) я торговал таким советником:
Несколько версий советника тестировал, чтобы получить максимальную воспроизводимость в реале и в тестере. 
Входы по лимитным ордерам, выставляемым на первом тике минуты с округлением цены (на случай если первые тики в тестере и реале не совпадут по цене  из за пропуска тиков в реале).
При активации ордера - используется тралл, чтобы проверить срабатывания по SL, которые в тестере срабатывают по сильно устаревшим Ask | Bid, что дает положительные проскальзывания, которых нет в реале на актуальных Ask | Bid.
input int StartHour=10;
input int EndHour=18;
input int CancelOrdersHour=19;
enum nums2 {__0=0,__1=1,__2=2,__3=3,__5=5,__7=7,__10=10,__14=14,__20=20,__30=30,__50=50,__70=70,__100=100,__150=150,__200=200,__300=300,__500=500,__700=700,__1000=1000,__1500=1500,__2000=2000,__3000=3000,__5000=5000,__7000=7000,__10000=10000,__15000=15000,__20000=20000,__32000=32000,__50000=50000,__100000=100000,__200000=200000};

input ENUM_ORDER_TYPE otype = 0;
input double lot = 1;
input nums2 SL = 20;//SL in points, 0 to disable
input nums2 TP = 0;//TP in points, 0 to disable
input nums2 Trail = 10;//Trail SL in points
input int TrailStart = -10;//Trail Start in points
input nums2 TrailStep = 5;//Trail Step in points
input int LimitOrdersShift=10;
sinput bool PrintTicks=true;
#include <Trade\Trade.mqh>
CTrade ExtTrade;
double roundToLvl(double &v, double point, double roundLvl, bool up_or_down){
   double rounded=v/point/roundLvl;
   return (up_or_down?ceil(rounded):floor(rounded)) * point * roundLvl;
}
void OnTick(void){
   static MqlTick Tick; static int hour,positions,orders; static double sl=SL*_Point,tp=TP*_Point,LimShift=LimitOrdersShift*_Point,open; static long type=otype; static datetime endMinute=0; static bool M1FirstTick=false;
   if (!SymbolInfoTick(_Symbol, Tick)){ return;}
   
   if(Tick.time > endMinute){// в начале каждой минуты работает, для повышения воспроизводимости в тестере
     endMinute = ((Tick.time/60))*60+59;//последняя секунда текущей минуты > будет началом следующей минуты (+59 для >, это быстрее, чем +60 и >=)
     M1FirstTick=true;
     hour=GetHour(Tick.time);
     if(hour>=CancelOrdersHour){CancelOrders();}
   }else{M1FirstTick=false;}
   
   positions=PositionsTotal();
   
   if(positions==0){//нет позиций - открыть
      if(hour>=StartHour && hour<EndHour){//торговля с 10 до 18 (гарантированно попадем в торговую сессию)
         if(M1FirstTick){

            if(type==ORDER_TYPE_BUY ){ExtTrade.PositionOpen(_Symbol,ORDER_TYPE_BUY ,lot,n(Tick.ask),(SL==0?0:n(Tick.bid-sl)),(TP==0?0:n(Tick.bid+tp)));}
            if(type==ORDER_TYPE_SELL){ExtTrade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,n(Tick.bid),(SL==0?0:n(Tick.ask+sl)),(TP==0?0:n(Tick.ask-tp)));}
            orders=OrdersTotal();
            if(orders==0){
               if(type==ORDER_TYPE_BUY_LIMIT){
                  open=Tick.ask-LimShift; Print("buy  lim ask=",Tick.ask," open=",open);
                  open=roundToLvl(open,_Point,10,0);Print("rounded open=",open);
                  ExtTrade.OrderOpen(_Symbol,ORDER_TYPE_BUY_LIMIT,lot,0,n(open),n(open-(SL*_Point)),0);
               }
               if(type==ORDER_TYPE_SELL_LIMIT){
                  open=Tick.bid+LimShift; Print("sell lim bid=",Tick.bid," open=",open);
                  open=roundToLvl(open,_Point,10,1);Print("rounded open=",open);
                  ExtTrade.OrderOpen(_Symbol,ORDER_TYPE_SELL_LIMIT,lot,0,n(open),n(open+(SL*_Point)),0);
               }
            }
         }
      }
   }else{//есть позиция - траллить
      trail(Tick,positions);
   }
   //if(PrintTicks && hour>=StartHour && hour<EndHour){ Print(Tick.time,".",Tick.time_msc%1000," bid:",Tick.bid," ask:",Tick.ask," last:",Tick.last," vol:",Tick.volume," flags:",Tick.flags,Flags(Tick.flags));}
   if(PrintTicks){Print(Tick.time,".",Tick.time_msc%1000," bid:",Tick.bid," ask:",Tick.ask," last:",Tick.last," vol:",Tick.volume," flags:",Tick.flags,Flags(Tick.flags));}
}
void trail(MqlTick &Tick,int &positions){
   static double new_sl,old_sl,old_tp,openPr,trail=Trail*_Point,Trail_Step=TrailStep*_Point,Trail_Start=TrailStart*_Point; static ulong ticket;
      for(int i=0; i<positions; i++){
         ticket=PositionGetTicket(i);//--- получаем тикет очередной позиции, автоматически выбирая позицию для доступа к её свойствам
         if(ticket==0){continue;}
         old_sl=PositionGetDouble(POSITION_SL);old_tp=PositionGetDouble(POSITION_TP);openPr=PositionGetDouble(POSITION_PRICE_OPEN);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY){
            new_sl=Tick.bid - trail ;//цена стопа от текущей цены
            if (new_sl > openPr+Trail_Start){//SL выше старта тралла
               if(new_sl > old_sl+Trail_Step){ExtTrade.PositionModify(ticket, n(new_sl),n(old_tp));}
            }
         }else{//sell
            new_sl=Tick.ask + trail;//цена стопа от текущей цены
            if (new_sl < openPr-Trail_Start){//SL выше старта тралла
               if(new_sl < old_sl-Trail_Step){ExtTrade.PositionModify(ticket, n(new_sl),n(old_tp));}
            }
         }
      }
}
double n(double v){if(v<0){return 0;} return NormalizeDouble(v,_Digits);}

void CancelOrders(){ 
   ulong ticket;
   for(int i=0;i<OrdersTotal();i++){//--- пройдем в цикле по всем ордерам
      if((ticket=OrderGetTicket(i))>0){ExtTrade.OrderDelete(ticket);}//--- получим тикет ордера по его позиции в списке
   }
}

int GetHour     (datetime &t){return((int)((t / 3600) % 24));}//current hour in day. 3600 sec in hour

string Flags(ulong f){
   string s="";if((f&TICK_FLAG_BID)>0){s+=" BID";}if((f&TICK_FLAG_ASK)>0){s+=" ASK";}if((f&TICK_FLAG_SELL)>0){s+=" SELL";}if((f&TICK_FLAG_BUY)>0){s+=" BUY";}if((f&TICK_FLAG_LAST)>0){s+=" LAST";}if((f&TICK_FLAG_VOLUME)>0){s+=" VOL";} 
   return s;
}

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

Вот скрин для сравнения реальной торговои и в тестере на кастомном символе:

Результат 3х сделок совпал, остальные немного отличаются (из за срабатывания на касании, и остальные видимо из за сетевых задержек, котороых нет в тестере). Суммарный итог случано совпал с реальным -3.4 рубля.
Для этого кастомного символа:
- удалил все Ask|Bid тики и создал новые актуальные при изменении цен Last. 
- Ask|Bid тики в одной миллисекунде (когда происходит выкуп нескольких уровней в стакане) не создавал, а только по цене последнего тика в этой мс.

Уверен, что вы тоже сможете достичь подобного сходства с реальной торговлей, а может даже большего.


Результат работы имеющегося тестера на оригинальном GAZP символе:

13 сделок вместо 8 в реальной торговле. Общий итог значительно завыше из за Ask|Bid взятых из тиков котороые не обновлялись своевременно.

Так же пригагаю распечатки тиков при реальной торговле за 30 октября (там есть записи о выставлении ордеров, пример: sell lim bid=116.69 open=116.74)
И прилагаю распечатку сделок за этот день.

Файлы:

Еще одно подтверждение, что имеющиеся в записанной истории Ask и Bid тики не правильны:

Ask и Bid тики выставлены на 30 пт выше идущих затем Last Buy и Last Sell.
Поэтому в моей версии кастомного символа результаты ближе к реальным.


Пример актуализированных тиков выглядит так: (я еще объединил тики по одной цене в один тик с объемом = сумме этих тиков, для экономии диска и увеличения скорости тестирования раза в 3)

Aleksei Kuznetsov #:

Проблема в том, что сначала торговые сессии начинались с 10:00, а с 2025 года с 07:00. И скоро снова изменят. 
Имеем 2 разных расписания в истории и одно расписание в спецификации. 

Если установить его правильно на текущий момент (с 7:00), то оно будет неправильным до 2025 когда было с 10:00 и тогда котировочные тики с 9:50 до 10:00 будут считаться как торговые.

Потому и предлагаю сделать флаг для обозначения тиков из торговой сессии.

Демонстрация проблемы с отсутствием разметки и автоматического отсеивания тиков по торговым сессиям.
Тот же эксперт, настройки приложены (просто увеличен размер тралла, чтобы сделки иногда проходили через вечерний и утренний ролловеры).
В начале не торговой сессии ставится Bid > Ask, причем цены на несколлько процентов выше и ниже текущей цены. 
Ранее кто-то предлагал торговые сессии контролировать самостоятельно. Но на срабатывание стоп-лоссов (и др стоп ордеров) мы никак не можем повлиять, что-то фильтруя в OnTick(). Поэтому они срабатывают в том числе по тикам из не торговой сессии.

Выглядит так на дневном графике: (торговля по символу Gazp, который терминал закачал с сервера дилера)

Видны срабатывания стопов об утренние Ask < Bid, с ценой до 3-5-10 размеров дневной свечи ниже. Огромное положительное проскальзывание, делает даже такую примитивную стратегию тестерным граалем даже на реальных тиках.

Если поменять направление на BuyLimit, то будет так:

Такие большие выбросы Bid|Ask были до 2025г., сейчас сделали поменьше, но тестировать то надо не только на последнем годе истории...

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