Неправильный объем при валидации

 
//+------------------------------------------------------------------+
// Объём сделки
input double _Lots = 0.1;

// Stop Loss (в пунктах)
input int _SL = 0;

// Take Profit (в пунктах)
input int _TP = 0;

// Magic Number (метка "своих" сделок для робота)
input int _MagicNumber = 0305;

// Комментарий для сделок
input string _Comment = "RussiaBot";

// Допустимое проскальзывание
input int _Slippage = 5;

// Одновременно не более одной сделки
input bool _OnlyOneOpenedPos = true;

// Автоучёт числа знаков цены
input bool _AutoDigits = true;

// ---

double OP_LOTS = 0.0;
// ---
// MQL4 | класс автоопределния числа знаков после запятой у текущего инструмента | ENSED Team, http://ensed.org
class CKDig
{
   public:
      CKDig(const bool useAutoDigits)
      {
         Set(useAutoDigits);
      }
      
      ~CKDig(void)
      {
      }
      
      uint Get(void)
      {
         return m_value;
      }
      
   private:      
      uint m_value;      
      
      void Set(const bool useAutoDigits)
      {
         m_value = 1;
         if (!useAutoDigits)
         {
            return;
         }
         
         if (Digits() == 3 || Digits() == 5)
         {
            m_value = 10;
         }
      }
};

CKDig *KDig;
#define K_DIG (KDig.Get())

datetime LAST_BUY_BARTIME = 0;
datetime LAST_SELL_BARTIME = 0;

// ---
void OnInit()
{
        // ---
        get_lots_by_input();
        // ---
        KDig  = new CKDig(_AutoDigits);
}

// ---
void OnDeinit(const int reason)
{
        // ---
        // ---
        if(CheckPointer(KDig))
        {
           delete KDig;
        }
}

// ---
void OnTick()
{
        // ---
        // закрытие сделки
        if(find_orders(_MagicNumber))
        {
                if(cl_buy_sig())
                {
                        cbm(_MagicNumber, _Slippage, OP_BUY);
                }
                if(cl_sell_sig())
                {
                        cbm(_MagicNumber, _Slippage, OP_SELL);
                }
        }
        
        // открытие сделки
        // ---
        
        // ---
        if(!find_orders(_MagicNumber, (_OnlyOneOpenedPos ? -1 : OP_BUY)))
        {
                if(op_buy_sig() && LAST_BUY_BARTIME != iTime(Symbol(), Period(), 0))
                {
                        LAST_BUY_BARTIME = iTime(Symbol(), Period(), 0);
                        open_positions(OP_BUY, OP_LOTS);        
                }
        }
        // ---
        if(!find_orders(_MagicNumber, (_OnlyOneOpenedPos ? -1 : OP_SELL)))
        {
                if(op_sell_sig() && LAST_SELL_BARTIME != iTime(Symbol(), Period(), 0))
                {
                        LAST_SELL_BARTIME = iTime(Symbol(), Period(), 0);
                        open_positions(OP_SELL, OP_LOTS);       
                }
        }
}

// ---

// ---
// ---
void get_lots_by_input() 
{
//MQL4 | присвоение объёма по входному параметру | ENSED Team
  OP_LOTS = _Lots;
}

// ---
// ---
bool find_orders(int magic = -1, int type = -1, int time = -1, string symb = "NULL", double price = -1, double lot = -1)
{
        // MQL4 | функция поиска открытых ордеров | ENSED Team, http://ensed.org
        // возвращает истину, если найден хотя бы один ордер с подходящими параметрами
        for (int i = OrdersTotal() - 1; i >= 0; i--)
        {
                if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
                        break;
                if (((OrderType() == type) || (type == -1))
                        && ((OrderMagicNumber() == magic) || (magic == -1))
                        && ((OrderSymbol() == symb) || (symb == "NULL" && OrderSymbol() == Symbol()))
                        && ((OrderOpenTime() >= time) || (time == -1))
                        && ((OrderLots() == lot) || (lot == -1))
                        && ((NormalizeDouble(OrderOpenPrice(), Digits) == NormalizeDouble(price, Digits)) || (price == -1)))
                {
                        return (true);
                        break;
                }
        }
        return (false);


// ---
void open_positions(int signal, double lot, double price = 0.0, string symb = "NONE", int mode = 0)
{
        // MQL4 | функция открытия ордеров | ENSED Team, http://ensed.org
        RefreshRates();
        // ---
        int symbDigits = 0;
        string _symb = symb;
        // ---
        if (_symb == "NONE")
        {
                symbDigits = Digits;
                _symb = Symbol();
        }
        else
                symbDigits = int(MarketInfo(_symb, MODE_DIGITS));
        // ---
        if (signal == OP_BUY)
                price = NormalizeDouble(MarketInfo(_symb, MODE_ASK), symbDigits); // цена открытия для покупок
        if (signal == OP_SELL)
                price = NormalizeDouble(MarketInfo(_symb, MODE_BID), symbDigits); // цена открытия для продаж
        // ---
        int err = 0;
        for (int i = 0; i <= 5; i++)
        {
           RefreshRates();
           // ---
                int ticket = OrderSend(_symb, // символ
                        signal, // тип ордера
                        lot, // объем
                        NormalizeDouble(price, symbDigits), // цена открытия
                        _Slippage * KDig.Get(), // уровень допустимого реквота
                        0, // Stop Loss
                        0, // Take Profit
                        _Comment, // комментарий ордера
                        _MagicNumber, // магическое число
                        0, // срок истечения (используется в отложенных ордерах)
                        CLR_NONE); // цвет отображаемой стрелки на графике (CLR_NONE - стрелка не рисуется)
                // ---
                if (ticket != -1)
                {
                        err = 0;
                        // ---
                        if (!IsTesting())
                                Sleep(1000);
                        // ---
                        RefreshRates();
                        // ---
                        if(_SL != 0 || _TP != 0)
                        {
                                for (int tryModify = 0; tryModify <= 5; tryModify++)
                                {
                                        RefreshRates();
                                        // ---
                                        if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
                                        {
                                                double sl = NormalizeDouble(get_sl(_SL * KDig.Get(), signal, price, _symb), symbDigits);
                                                double tp = NormalizeDouble(get_tp(_TP * KDig.Get(), signal, price, _symb), symbDigits);
                                                // ---
                                                if (sl != 0 || tp != 0)
                                                if (OrderModify(OrderTicket(), OrderOpenPrice(), sl, tp, OrderExpiration()))
                                                        break;
                                                // ---
                                                err = GetLastError(); // получаем код ошибки модификации
                                        }
                                        // ---
                                        if (!IsTesting())
                                                Sleep(tryModify*1000);
                                }
                                // ---
                                if (err != 0)
                                        Alert("Ошибка выставления SL/TP: " + Market_Err_To_Str(err));
                        }
                        // ---
                        break;
                }
                else
                {
                        err = GetLastError(); // получаем код ошибки открытия
                        // ---
                        if (err == 0)
                                break;
                        // ---
                        i++;
                        // ---
                        if (!IsTesting())
                                Sleep(i*500); // в случае ошибки делаем паузу перед новой попыткой
                }
        }
        // ---
        if (err != 0)
        {
                if(signal == OP_BUY)
                        LAST_BUY_BARTIME = 0;
                if(signal == OP_SELL)
                        LAST_SELL_BARTIME = 0;
                Alert("Ошибка открытия: "  + Market_Err_To_Str(err)); // если есть ошибка - выводим сообщение
        }
}

// ---
double get_tp(int tp_value, int type, double price = 0.0, string symb = "NONE")
{
        // MQL4 | функция расчета величины Take Profit для ордеров | ENSED Team, http://ensed.org
        double _price = price;
        string _symb = symb;
        // ---
        if (_symb == "NONE")
                _symb = Symbol();
        int symbDigits = int(MarketInfo(_symb, MODE_DIGITS));
        // ---
        if (_price == 0)
        {
                if (type == OP_BUY)
                        _price = NormalizeDouble(MarketInfo(_symb, MODE_ASK), symbDigits);
                // ---
                if (type == OP_SELL)
                        _price = NormalizeDouble(MarketInfo(_symb, MODE_BID), symbDigits);
        }
        // ---
        if (tp_value > 0)
        {
                if (type == OP_BUY || type == OP_BUYLIMIT || type == OP_BUYSTOP)
                        return NormalizeDouble(_price + tp_value * MarketInfo(_symb, MODE_POINT), symbDigits);
                // ---
                if (type == OP_SELL || type == OP_SELLLIMIT || type == OP_SELLSTOP)
                        return NormalizeDouble(_price - tp_value *  MarketInfo(_symb, MODE_POINT), symbDigits);
        }
        // ---
        return 0.0;
}

// ---
double get_sl(int sl_value, int type, double price = 0.0, string _symb = "NONE")
{
        // MQL4 | функция расчета величины Stop Loss для ордеров по фиксированному значению SL | ENSED Team, http://ensed.org
        if (_symb == "NONE")
                _symb = Symbol();
        int symbDigits = int(MarketInfo(_symb, MODE_DIGITS));
        double symbPoint = MarketInfo(_symb, MODE_POINT);
        // ---
        if (price == 0.0)
        {
                if (type == OP_BUY)
                        price = NormalizeDouble(MarketInfo(_symb, MODE_ASK), symbDigits);
                if (type == OP_SELL)
                        price = NormalizeDouble(MarketInfo(_symb, MODE_BID), symbDigits);
        }
        // ---
        if (sl_value > 0)
        {
                if (type == OP_BUY || type == OP_BUYLIMIT || type == OP_BUYSTOP)
                        return NormalizeDouble(price - sl_value * symbPoint, symbDigits);
                if (type == OP_SELL || type == OP_SELLLIMIT || type == OP_SELLSTOP)
                        return NormalizeDouble(price + sl_value * symbPoint, symbDigits);
        }
        // ---
        return 0.0;
}

// ---
bool close_by_ticket(const int ticket, const int slippage)
{
        /*
         MQL4 | функция закрытия сделки по её номеру (тикету) | ENSED Team
         При закрытии рыночного ордера учитывается уровень максимально допустимого проскальзывания (slipage)
         */
        if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) // выбираем ордер по тикету
        {
           return false;
        }
        
        int err = 0;
        
        for (int i = 0; i < 5; i++)
        {
           ResetLastError();
           
                RefreshRates();
                
                double price = 0.0;
                
                if (OrderType() == OP_BUY)
                {
                        price = NormalizeDouble(SymbolInfoDouble(OrderSymbol(), SYMBOL_BID), (int)SymbolInfoInteger(OrderSymbol(), SYMBOL_DIGITS));
                }
                if (OrderType() == OP_SELL)
                {
                        price = NormalizeDouble(SymbolInfoDouble(OrderSymbol(), SYMBOL_ASK), (int)SymbolInfoInteger(OrderSymbol(), SYMBOL_DIGITS));
                }
                                   
           // если рыночный ордер - закрываем его, если отложенный - удаляем
           bool result = false;
           
                if (OrderType() <= OP_SELL) 
                {
                        result = OrderClose(OrderTicket(), OrderLots(), price, slippage * KDig.Get(), clrNONE);
           }
                else
                {
                        result = OrderDelete(OrderTicket());
                }
                
                if (result) // если закрытие или удаление прошло успешно - возвращаем true и выходим из цикла
                {
                        return (true);
                }
                
                err = GetLastError();
                
                if (err != 0)
                {
                        Print("Error of close_by_ticket() #" + (string)err + ": " + Market_Err_To_Str(err)); // если есть ошибка - даём расшифровку её кода в журнал
                }
                
                Sleep(300 * i);
        }
        return (false);
}

// ---
bool cbm(int magic, int slippage, int type)
{
        /*
         close by magic (закрытие всех ордеров данного типа с данным MagicNumber)
         Учитывается максимально допустимое проскальзывание (slipage)
         Используется функция close_by_ticket.
         */
        int n = 0;
        RefreshRates();
        for (int i = OrdersTotal() - 1; i >= 0; i--)
        {
                if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
                        break;
                if ((OrderType() == type) && (OrderMagicNumber() == magic) && (Symbol() == OrderSymbol()))
                {
                        close_by_ticket(OrderTicket(), slippage); // закрываем сделку
                        n++; // наращиваем счётчик закрытых сделок (на самом деле - попыток закрытия)
                }
        }
        if (n > 0) // если попыток закрытия было больше 0, то функция возвращает true
                        return (true);
        return (false);
}
 
Шо не так с этим кодом? При валидации выдает неправильный объем.
 
Sprut112:
Шо не так с этим кодом? При валидации выдает неправильный объем.

Очень подробный ответ с примером есть в инструкции "Какие проверки должен пройти ...", ссылку на которую движок форума вставил в вашем сообщении.

 
Alexey Viktorov:

Очень подробный ответ с примером есть в инструкции "Какие проверки должен пройти ...", ссылку на которую движок форума вставил в вашем сообщении.

Да, только это для mql5
 
Sprut112:
Да, только это для mql5

MQL5 отличается от MQL4 только торговыми функциями. Всё остальное абсолютно одинаково. Ну, или почти всё... за исключением незначительной мелочи. Даже не вспомню конкретно чего-то.

 
Alexey Viktorov:

MQL5 отличается от MQL4 только торговыми функциями. Всё остальное абсолютно одинаково. Ну, или почти всё... за исключением незначительной мелочи. Даже не вспомню конкретно чего-то.

Понятно, спасибо. Посмотреть бы ещё код какого-нибудь простого советника, прошедшего валидацию, чтобы не ошибнуться
 
Sprut112:
Понятно, спасибо. Посмотреть бы ещё код какого-нибудь простого советника, прошедшего валидацию, чтобы не ошибнуться

А там куда ведёт ссылка читал? Там есть готовая функция. Не видел???

//+------------------------------------------------------------------+
//|  Проверяет объем ордера на корректность                          |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,string &description)
  {
//--- минимально допустимый объем для торговых операций
   double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   if(volume<min_volume)
     {
      description=StringFormat("Объем меньше минимально допустимого SYMBOL_VOLUME_MIN=%.2f",min_volume);
      return(false);
     }

//--- максимально допустимый объем для торговых операций
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
   if(volume>max_volume)
     {
      description=StringFormat("Объем больше максимально допустимого SYMBOL_VOLUME_MAX=%.2f",max_volume);
      return(false);
     }

//--- получим минимальную градацию объема
   double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);

   int ratio=(int)MathRound(volume/volume_step);
   if(MathAbs(ratio*volume_step-volume)>0.0000001)
     {
      description=StringFormat("Объем не является кратным минимальной градации SYMBOL_VOLUME_STEP=%.2f, ближайший корректный объем %.2f",
                               volume_step,ratio*volume_step);
      return(false);
     }
   description="Корректное значение объема";
   return(true);
  }
 
Alexey Viktorov:

А там куда ведёт ссылка читал? Там есть готовая функция. Не видел???

Да видел, ладно попробую сама. Это ж на мт5

 

Sprut112:

Да видел, ладно попробую сама. Это ж на мт5

Странный ответ. Видел м.р., а сама ж.р. Так с кем я веду беседу???

Ладно... видимо где-то очепятка.

А какая функция из этого кода не поддерживается в mql4???

 
Alexey Viktorov:

Странный ответ. Видел м.р., а сама ж.р. Так с кем я веду беседу???

Ладно... видимо где-то очепятка.

А какая функция из этого кода не поддерживается в mql4???

Уже не знаю чо делать. Как Вы сказали вставил туда код, целиком как на сайте. На удивление скомпилировался без ошибок. Отправил на валидацию и опять выдал 131 ошибка.

 
Sprut112:

Уже не знаю чо делать. Как Вы сказали вставил туда код, целиком как на сайте. На удивление скомпилировался без ошибок. Отправил на валидацию и опять выдал 131 ошибка.

Видимо тут другая проблема. Не корректность объёма, а нехватка средств для открытия позиции. Другими словами недостаточно маржи... В mql4 это проверяется проще чем в mql5. Есть функция, не помню как правильно, но возвращает остаток средств если открыть ордер указанным объёмом. Так вот разница текущего эквити и этого значения должно быть больше нуля. Соответственно, если меньше нуля, то ордер не открываем и все дела.
Причина обращения: