Глюк Терминала? Вопрос к разработчикам. Build 225, 229.

 

Столкнулся сегодня с прелюбопытнейшим явлением и так и не понял что это - ошибка у меня в коде, или глюк тестера в терминале.

Суть в следующем. Приходит сигнал, открываем ордер. Цена поехала в профит и прошла расстояние = некоему шагу. Сбрасываем небольшой лот. Далее цена проходит ещё в профит и ещё расстояние = шагу. Чтобы не вычислять сколько раз был сброшен маленький лот и действительно ли цена снова прошла нужный нам шаг, я поступил следующим образом.

1. Пишем функцию, которая возвращает тикет последнего закрытого ордера истории.

2. Найденный только что тикет передаём в другую функцию, которая возвращает нужный нам параметр ордера - нам нужна цена закрытия, поэтому помимо тикета передаём в эту функцию спецпараметр.

3. Цена закрытия последнего ордера + шаг = уровень, на котором нужно сбросить очередной лот.

Такое построение кода куда более проще, чем запоминать время старта ордера, вычислять сколько же раз уже были сброшены лоты и просчитывать расстояние, которое цена прошла для сброса очередного лота.

Ну так вот, что любопытно. Перед первым сбросом лота функция возврата тикета ордера истории возвращает (-1) - ну оно и понятно - ведь тест только что стартонул и пока что ещё не закрыто не одного ордера. Советник понимает, что нужно отследить растояние = шагу сброса. Далее происходит сброс (цена прошла нужный шаг), и на следующем тике функция возврататикета возвращает нам 1 - это тоже понятно - в тестере первый закрытый ордер будет иметь тикет = 1. Функция возврата цены закрытия прекрасно видит в истории ордер №1 и прекрасно возвращает цену его закрытия. До этих пор всё прекрасно работает. Длее цена снова едет в профит и достигает уровня = цена закрытия ордера + шаг сроса лотов. Как только это случилось, у позиции сбрасывается второй маленький лот. Скажу сразу, ордер открывается с лотом = 1, а сбрасывается каждый раз лот = 0,1. Ну так вот. Как только произошёл сброс второго лота, функция поиска тикета последнего закрытого ордера возвращает нам тикет = 2. Всё верно - после второго сброса ордер 2 в истории и функция его видит. Передача этого тикета в функцию, которая может вернуть нам цену его закрытия приводит к тому, что функция возвращает ответ: "Ордера № 2 в истории не существует"!

Вначале я грешил на себя - искал ошибку в коде. Найти так и не удалось. Тогда я решил обновить терминал - у меня стоял Билд 225, я обновился до 229. Результат тот же самый - всех последующих ордеров в истори не существует - ордера невидимки :). Вот код, который я задействовал.


// перед блоком инициализации:
extern int      MAGIC=6523456;//У ордеров открытых вручную MAGIC=0
int             TicketSellHist;
double          LevelCloseHistOrdera;
string          SMB=Symbol();// инициализация этой переменной происходит в блоке инициализации. Объявлена она перед этим блоком
// внутри функции start:

TicketSellHist=TicketLastHistOrdera("HISTSELL");
LevelCloseHistOrdera=ParamOrdera(TicketSellHist,"CLOSEPRICE","HISTSELL");

// После функции start - пользовательские функции:

// =================== TicketLastHistOrdera() ==============================================================
// Функция возвращает тикет ордера

// -----------------------------------------
int TicketLastHistOrdera(string Type){
        string NameFunction="TicketLastHistOrdera()",;
        int Ticket=-1;
        datetime CloseTime=0;
        // ----------------- Обработка ошибок ------------------------
        if(Type!="HISTBUY" && Type!="HISTSELL" && Type!="HISTBUYSTOP" && Type!="HISTSELLSTOP" && Type!="HISTBUYLIMIT" && Type!="HISTSELLLIMIT"){
                Print("Некорректное значение переменной Type = ",Type);
                Print("Ошибка возникла в функции ",NameFunction);
                return(-1);
        }
        for (int i=OrdersHistoryTotal()-1;i>=0;i--) {
          if (!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) {
            WriteError(i);
            Print("Ошибка возникла в функции ",NameFunction);
        }
        else {
          if(OrderSymbol()!= SMB || OrderMagicNumber()!= MAGIC){ continue;}
          if(Type=="HISTBUY"){
            if(OrderType()!=OP_BUY){
                  continue;
                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
                        if(Type=="HISTSELL"){
                                if(OrderType()!=OP_SELL){
                                        continue;
                                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
                        if(Type=="HISTBUYSTOP"){
                                if(OrderType()!=OP_BUYSTOP){
                                        continue;
                                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
                        if(Type=="HISTSELLSTOP"){
                                if(OrderType()!=OP_SELLSTOP){
                                        continue;
                                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
                        if(Type=="HISTBUYLIMIT"){
                                if(OrderType()!=OP_BUYLIMIT){
                                        continue;
                                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
                        if(Type=="HISTSELLLIMIT"){
                                if(OrderType()!=OP_SELLLIMIT){
                                        continue;
                                }
                                else{
                                        if(CloseTime<OrderCloseTime()){
                                                Ticket=OrderTicket();
                                                CloseTime=OrderCloseTime();
                                        }
                                }
                        }
    }
  }
        return(Ticket);
}
 
// =================== ParamOrdera() ===============================================================
// Функция возвращает цену стопа, тейка, открытия и закрытия указанного ордера
// Параметры переменных:
// - Ticket - тикет ордера. Его нужно предварительно узнать с помощью функции TicketOrdera()
// - Param может принимать значения: "OPENPRICE", "CLOSEPRICE", "STOPLOSS", "TAKEPROFIT", "LOT"
//   значение "CLOSEPRICE" может указываться только для закрытых ордеров
// - Type  может принимать значения: "BUY", "SELL", "BUYSTOP", "SELLSTOP", "BUYLIMIT", "SELLLIMIT",
//   "HISTBUY", "HISTSELL", "HISTBUYSTOP", "HISTSELLSTOP", "HISTBUYLIMIT", "HISTSELLLIMIT"
// ---------
//  Если на вход функции будет подано значение Param=="CLOSEPRICE" и при этом указан тикет ордера,ещё
//  не закрытого, то функция выдаст сообщение об ошибке и вернёт значение = (-1), так как ордера,
//  которые ещё не находятся в истории, не имеют цены закрытия
// -------------------------------------------------------------------------------------------------
double ParamOrdera(int Ticket,string Param,string Type){
        string  NameFunction="ParamOrdera()";
        int                     DGS=MarketInfo(SMB,MODE_DIGITS);
        double  PRC=-1;
        // ------------- обработка ошибок ------------------
        if(Param=="CLOSEPRICE"){
                if(Type=="BUY" || Type=="SELL" || Type=="BUYSTOP" || Type=="SELLSTOP" || Type=="BUYLIMIT" || Type=="SELLLIMIT"){
                        Print("На вход функции подано значение CLOSEPRICE, а тип ордера - не ордер истории - Type = ",Type);
                        Print("Ошибка возникла в функции ",NameFunction);
                        return(-1);
                }
        }
        if(Param!="OPENPRICE" && Param!="CLOSEPRICE" && Param!="STOPLOSS" && Param!="TAKEPROFIT" && Param!="LOT"){
                Print("некорректное значение переменной Param = ",Param);
                Print("Ошибка возникла в функции ",NameFunction);
                return(-1);
        }
        if(Type!="BUY" && Type!="SELL" && Type!="BUYSTOP" && Type!="SELLSTOP" && Type!="BUYLIMIT" && Type!="SELLLIMIT" &&
                        Type!="HISTBUY" && Type!="HISTSELL" && Type!="HISTBUYSTOP" && Type!="HISTSELLSTOP" && Type!="HISTBUYLIMIT" && Type!="HISTSELLLIMIT"){
                Print("Некорректное значение переменной Type = ",Type);
                Print("Ошибка возникла в функции ",NameFunction);
                return(-1);
        }
        // ----------------------------------------------------------------------
        // --------- Для ордеров из рынка ---------------------------
        if(Type=="BUY" || Type=="SELL" || Type=="BUYSTOP" || Type=="SELLSTOP" || Type=="BUYLIMIT" || Type=="SELLLIMIT"){
                for (int i=OrdersTotal()-1;i>=0;i--) {
                        if (!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) {
                                WriteError(i);
                                Print("Ошибка возникла в функции ",NameFunction);
                        }
                        else {
                                if(OrderSymbol()!= SMB || OrderMagicNumber()!= MAGIC){continue;}
                                if(OrderTicket()==Ticket){
                                        if(Param=="LOT"){
                                                PRC=NormalizeDouble(OrderLots(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="OPENPRICE"){
                                                PRC=NormalizeDouble(OrderOpenPrice(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="STOPLOSS"){
                                                PRC=NormalizeDouble(OrderStopLoss(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="TAKEPROFIT"){
                                                PRC=NormalizeDouble(OrderTakeProfit(),DGS);
                                                return(PRC);
                                        }
                                }
                        }
                }
        }
        // --------- Для ордеров из истории торгов ---------------------------
        if(Type=="HISTBUY" || Type=="HISTSELL" || Type=="HISTBUYSTOP" || Type=="HISTSELLSTOP" || Type=="HISTBUYLIMIT" || Type=="HISTSELLLIMIT"){
                for (int ii=OrdersHistoryTotal()-1;ii>=0;ii--) {
                        if (!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) {
                                WriteError(ii);
                                Print("Ошибка возникла в функции ",NameFunction);
                        }
                        else {
                                if(OrderSymbol()!= SMB || OrderMagicNumber()!= MAGIC){continue;}
                                if(OrderTicket()==Ticket){
                                        if(Param=="LOT"){
                                                PRC=NormalizeDouble(OrderLots(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="OPENPRICE"){
                                                PRC=NormalizeDouble(OrderOpenPrice(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="CLOSEPRICE"){
                                                PRC=NormalizeDouble(OrderClosePrice(),DGS);
                                                Print("Параметр CLOSEPRICE ордера № ",Ticket," = ",PRC);
                                                return(PRC);
                                        }
                                        if(Param=="STOPLOSS"){
                                                PRC=NormalizeDouble(OrderStopLoss(),DGS);
                                                return(PRC);
                                        }
                                        if(Param=="TAKEPROFIT"){
                                                PRC=NormalizeDouble(OrderTakeProfit(),DGS);
                                                return(PRC);
                                        }
                                }
                        }
                }
        }
        if(Param=="CLOSEPRICE"){
                Print("Ордера № ",Ticket," в истории не существует");
        }
        return(PRC);
}
Понимаю, что можно и как обойти эту пролбему. Но всё же интересен ответ на вопрос, почему одна подпрограмма возвращает нам тикет последнего ордера истории, а другая не видит его в той же самой истории?
 

Хочу ещё пояснить, почему я выбрал вычисление последнего уровня через точку закрытия последнего ордера истории. Дело в том, что можно было бы поступить следующим образом: сбрасываем лот, запоминаем цену сброса. НО! При отключении электопитания, когда советник снова включится в работу, он эту предыдущую, запоменную цену сброса уже не увидит - переменная будет обнулена. Поэтому можно просто пройтись по истории, найти последний закрытый ордер и посмотреть цену его закрытия - это будет лучше, чем запоминать эту цену в переменную и дальше опираться исключительно на значение, хранящееся в этой переменной.

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

P.S.

Да, и вот ещё что. Я отказался выбирать ордер через OrderSelect(Ticket,SELECT_BY_TICKET) так как это почему-то не всегда работает. Неоднократно уже приходилось из-за этого переделывать собственные подпрограммы.

 
drknn: Ну так вот. Как только произошёл сброс второго лота, функция поиска тикета последнего закрытого ордера возвращает нам тикет = 2. Всё верно - после второго сброса ордер 2 в истории и функция его видит. Передача этого тикета в функцию, которая может вернуть нам цену его закрытия приводит к тому, что функция возвращает ответ: "Ордера № 2 в истории не существует"!

Попробуйте поискать этот тикет на следующем тике. Обновление базы истории не происходит мгновенно, видимо дело в этом.
 
Rosh:

Попробуйте поискать этот тикет на следующем тике. Обновление базы истории не происходит мгновенно, видимо дело в этом.



Спасибо за оперативность!

Поначалу так и было - сбрасывался лот и на текущем тике отыскивался тикет ордера по-новой, так как при сбросе лотов тикет меняется. Отыскивался для следующего участка кода - для трала стопа. Далее, поскольку я столкнулся с проблемой, я закомментировал полностью блок трала стопа и оставил в работе только блок сброса лотов. Итак, что получилось в результате - советник увидел, что в рынке появился ордер, он проверяет, ни пора ли сбросить лот. Если пора, то лот сбрасывается и ни каких запросов больше не происходит - советник ждёт следующего тика чтоб снова проверить, а ни пора ли снова сбросить лот. На следующем тике всё происходит по-новой. Проблема в том, что после первого сброса всё нормально отыскивается. После второго сброса тикета ордера нет уже на следующем и других тиках.

Я нашёл способ обойти глюк через проверку времени закрытия ордера - но проблема-то остаётся.

 
Без полного кода трудно сказать. Попробуйте написать в Сервисдеск - https://www.mql5.com/ru/users/drknn
 

1. Функция TicketLastHistOrdera возвращает не последний ордер, соответствующий запросу, а первый! После того, как ордер найден, надо бы выйти из цикла (поставить break)

2. А почему Вы не представили функцию, которая возвращает ответ "Ордера № 2 в истории не существует"?

 
stringo:

1. Функция TicketLastHistOrdera возвращает не последний ордер, соответствующий запросу, а первый! После того, как ордер найден, надо бы выйти из цикла (поставить break)

2. А почему Вы не представили функцию, которая возвращает ответ "Ордера № 2 в истории не существует"?



Функция TicketLastHistOrdera() проходит по списку ордеров. Поскольку изначально переменная CloseTime=0, то встретив первый попавшийся, скажем,Бай-ордер, она в эту переменную запомнит время его закрытия. Далее, проходя по всем прочим бай-ордерам, в этой перемнной кажется время закрытия, которое самое большое. Понятно, что у последнего ордера время закрытия будет самым большим. Попутно в другую переменную будет запомнен тикет ордера. Так что функция написана верно и возвращать она должна тикет ПОСЛЕДНЕГО ордера. Вникните. Что касается брека, то он тут ни к селу ни к городу (простите за прямоту). Если поставить брек, то мы рискуем пройти не по всему списку ордеров и прерваться раньше времени. Я вообще не представляю, из каких соображений Вы посоветовали сунуть сюда брек.

>> 2. А почему Вы не представили функцию, которая возвращает ответ "Ордера № 2 в истории не существует"?

Отвечаю, в функции ParamOrdera() предпоследняя строка: " Print("Ордера № ",Ticket," в истории не существует");" Она будет выполнена только если ордера с заданным тикетом не существует. Если такой существует, то функция прервётся раньше и это сообщение попросту не выстрелит в журнал.

 
Rosh:
Без полного кода трудно сказать. Попробуйте написать в Сервисдеск - https://www.mql5.com/ru/users/drknn


Код смоделировать не сложно - хотите я это сделаю? Там ведь нужно всего-навсего поставить условие, мол, если ордеров в рынке нет, то открываем бай и селл в обоих направлениях. Не важно, что не будет профита, важно что мы сможем отследить глюк тестера. Далее блок сброса лотов. Тож не сложно сделать.