Занялся оптимизацией своих функций, нужен совет.

 

Уже много лет делаю советники для МТ4 с использованием библиотек,в которых все нужные функции в обертке классов.

Р‌ешил заняться серьезно оптимизацией и вот с чем столкнулся, скорость в тестере без визуализации на загрузку данных о ордерах занимает в среднем 20-25 мс, хочу снизить, но похоже предел.

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

В‌от основная функция загрузки данных о ордерах, так как у меня реализована поддержка открытия ордеров если лот превышает максимальный, то надо грузить все ордера по инструменту без учета их меджика, и уже потом отбирать при необходимости нужные.

//функции управления ордерами
   int Update()
     {

      CSpeed s1;
      s1.Start(__DEBUG__);

      __COPYRIGHT__
      ArrayResize(m_orders,0);
      m_all=0;
      ArrayInitialize(orders,0);

      int isize=0;
      int itotal=OrdersTotal();
      
      m_total=itotal; //запомним дял проверок кол-ва
      
      for(int i=itotal-1;i>=0;i--)
        {
         if(!OrderSelect(i,SELECT_BY_POS)) continue; //если не выбирает, пропускаем

         if(CheckSymbol(OrderSymbol()))
           {

            if((!options_bSupportMaxLot && CheckMagic(OrderMagicNumber())) || options_bSupportMaxLot) //поддержка лотв больше чем максимальный
              {

               isize=ArraySize(m_orders); //кол-во оредров в структуре

               if(ArrayResize(m_orders,isize+1)!=-1)
                 {
                  if(m_orders[isize].getOrderByTicket(OrderTicket())) //загрузим информацию по ордеру (грузим все ордера)
                    {
                     if(CheckMagic(m_orders[isize].OrderMagicNumber))
                       {
                        orders[m_orders[isize].OrderType]++; //увеличим счетчик
                        m_all++;
                       }
                    }
                 }
              }

           }
        }
      s1.Stop(__FUNCTION__);
      return m_all; //вернем кол-во ордеров в структуре
     }


‌Как реализован m_orders?

Е‌сть класс COrderInfo в классе ордеров объявлен массив этого класса.

class COrderInfo
  {
public:
   string            OrderSymbol;
   string            OrderComment;
   int               OrderTicket;
   int               OrderType;
   int               OrderMagicNumber;
   datetime          OrderOpenTime;
   datetime          OrderCloseTime;
   datetime          OrderExpiration;
   double            OrderOpenPrice;
   double            OrderClosePrice;
   double            OrderLots;
   double            OrderProfit;
   double            Profit; //суммарный профит
   double            OrderProfitPoint; //профит убыток в пунктах, если оредер будет закрыт
   double            OrderStep; //считаем шаг между ордерами, без учета их закрытия (для бай между аск, для селл между бид)
   double            OrderSwap;
   double            OrderCommission;
   double            OrderStopLoss;
   double            OrderTakeProfit;

   double ProfitPoint() //ордер должен быть выбран!
     {
      string n=OrderSymbol;
      double dpp=point(n);
      if(dpp==0.0) return 0;

      if(OrderCloseTime==0.0 && OrderType<2)
        {
         if(OrderType==OP_BUY) return (NormalizeDouble((MarketInfo(n,MODE_BID)-OrderOpenPrice)/dpp,1));
         if(OrderType==OP_SELL) return (NormalizeDouble((OrderOpenPrice-MarketInfo(n,MODE_ASK))/dpp,1));
        }

      if(OrderCloseTime!=0.0 && OrderType<2)
        {
         if(OrderType==OP_BUY) return (NormalizeDouble((OrderClosePrice-OrderOpenPrice)/dpp,1));
         if(OrderType==OP_SELL) return (NormalizeDouble((OrderOpenPrice-OrderClosePrice)/dpp,1));
        }

      return 0;
     }

   double StepPoint() //ордер должен быть выбран!
     {
      string n=OrderSymbol;
      double dpp=point(n);
      if(dpp==0.0) return 0;

      if(OrderCloseTime==0.0 && OrderType<2)
        {
         if(OrderType==OP_BUY) return (NormalizeDouble((MarketInfo(n,MODE_ASK)-OrderOpenPrice)/dpp,1));
         if(OrderType==OP_SELL) return (NormalizeDouble((OrderOpenPrice-MarketInfo(n,MODE_BID))/dpp,1));
        }

      if(OrderCloseTime!=0.0 && OrderType<2)
        {
         if(OrderType==OP_BUY) return (NormalizeDouble((OrderClosePrice-OrderOpenPrice)/dpp,1));
         if(OrderType==OP_SELL) return (NormalizeDouble((OrderOpenPrice-OrderClosePrice)/dpp,1));
        }

      return 0;
     }

   void operator=(const int nil=NULL) //нулевая инициализация переменных
     {
      OrderSymbol="";
      OrderComment="";
      OrderTicket=0;
      OrderType=-1;
      OrderMagicNumber=0;
      OrderOpenTime=0;
      OrderCloseTime=0;
      OrderExpiration=0;
      OrderOpenPrice=0;
      OrderClosePrice=0;
      OrderLots=0;
      OrderProfit=0;
      Profit=0;
      OrderProfitPoint=0;
      OrderStep=0;
      OrderSwap=0;
      OrderCommission=0;
      OrderStopLoss=0;
      OrderTakeProfit=0;
     }

   void operator=(const COrderInfo &a1)//простое копирование
     {
      OrderSymbol=a1.OrderSymbol;
      OrderComment=a1.OrderComment;
      OrderTicket=a1.OrderTicket;
      OrderType=a1.OrderType;
      OrderMagicNumber=a1.OrderMagicNumber;
      OrderOpenTime=a1.OrderOpenTime;
      OrderCloseTime=a1.OrderCloseTime;
      OrderExpiration=a1.OrderExpiration;
      OrderOpenPrice=a1.OrderOpenPrice;
      OrderClosePrice=a1.OrderClosePrice;
      OrderLots=a1.OrderLots;
      OrderProfit=a1.OrderProfit;
      Profit=a1.Profit;
      OrderProfitPoint=a1.OrderProfitPoint;
      OrderStep=a1.OrderStep;
      OrderSwap=a1.OrderSwap;
      OrderCommission=a1.OrderCommission;
      OrderStopLoss=a1.OrderStopLoss;
      OrderTakeProfit=a1.OrderTakeProfit;
     }

   bool getOrderByTicket(int iticket,int imode=MODE_TRADES)
     {
      if(OrderTicket()!=iticket)
        {
         if(!OrderSelect(iticket,SELECT_BY_TICKET,imode)) return false;
        }

      if(iticket<=0) return false;

      OrderSymbol=OrderSymbol();
      OrderComment=OrderComment();
      OrderTicket=OrderTicket();
      OrderType=OrderType();
      OrderMagicNumber=OrderMagicNumber();
      OrderOpenTime=OrderOpenTime();
      OrderCloseTime=OrderCloseTime();
      OrderExpiration=OrderExpiration();
      OrderOpenPrice=OrderOpenPrice();
      OrderClosePrice=OrderClosePrice();
      OrderLots=OrderLots();
      OrderProfit=OrderProfit();
      OrderProfitPoint=ProfitPoint();
      OrderStep=StepPoint();
      OrderSwap=OrderSwap();
      OrderCommission=OrderCommission();
      OrderStopLoss=OrderStopLoss();
      OrderTakeProfit=OrderTakeProfit();
      Profit=OrderProfit+OrderSwap+OrderCommission;

      return true;
     }

  };

Для определения скорости обработки испольузется класс CSpeed

//класс для профилирования функций в тестере и во время работы
class CSpeed
  {
protected:
   ulong             utimer;
   bool              bPrint;
public:
   void Start(bool bprint)
     {
      bPrint=bprint;
      if(bPrint)utimer=GetMicrosecondCount();
     }

   void Stop(string sfunc,string sdop="")
     {
      if(!bPrint) return;  //выключено

      ulong msec=GetMicrosecondCount()-utimer;
      PrintFormat("CSpeed(%s): %s - %d ms",sdop,sfunc,msec);
     }

   void ReStart(string sfunc,string sdop="")
     {
      if(!bPrint) return;  //выключено

      ulong msec=GetMicrosecondCount()-utimer;
      PrintFormat("CSpeed(%s): %s - %d ms",sdop,sfunc,msec);
      utimer=GetMicrosecondCount();
     }
  };

Как считаете, можно ли добиться скорости загрузки данных о ордерах больше? Может както иначе код функции опроса реализовать?

Вообще по таким тестам больше всего у меня отжирают функции открытия ордеров от 36 до 300 мс

Вот вспомогательная функция которая отвечает за открытие сделок

//торговые функции, функция уммет открывать сделки объемом больше чем максимальный, при этом она дробит их на несколько частей
   int Trade(const int cmd,const double dlot,double dopen,const double dsl=0,const double dtp=0,const string _comment="",const datetime dt=0,const color cl=clrNONE,const bool tpslIsPoint=true,const bool bAddCount=true)
     {
      __COPYRIGHT__
      ierrors=0;
      if(cmd<0) return -1;

      if(!CheckOrderSend(cmd,dlot))
        {
         //Alert("Enough money to make the operation");
         ierrors=134;

         return (-134);
        }

      if(dlot==0.0) return 0; //лот не задан. не открываемся

      double ddlot=NLot(dlot,m_name); //округлим лот по правилам
      p=point(m_name);

      bool is_open=true;
      bool bret=false;
      string sinfo="";

      double dstop=0,dtake=0;

      double dnewopen=dopen;

      int digit=digits(m_name);
      int iticket=-1,j=0;

      ierrors=0; //обнулим данные о последней ошибке

      bool bOpenEmpty=false;

      if(stoplevel==0) stoplevel=GetStopLevel(m_name)*MarketInfo(m_name,MODE_POINT);

      double _Ask=Ask(m_name);
      double _Bid=Bid(m_name);

      while(is_open && j<options_iRestartCount && !IsStopped())
        {
         j++;

         if(dopen==-1)
           {
            RefreshRates();

            _Ask=Ask(m_name);
            _Bid=Bid(m_name);

            bOpenEmpty=true;
            if(cmd==OP_BUY) dnewopen=_Ask;
            if(cmd==OP_SELL) dnewopen=_Bid;
           }

         //проверим корректность тейка и стопа для постановки, иначе отклоним неверный запрос
         if(cmd==OP_BUYSTOP && _Ask>=dnewopen-stoplevel)
           {
            return 0;
           }

         if(cmd==OP_SELLSTOP && _Bid<=dnewopen+stoplevel)
           {
            return 0;
           }

         if(iticket==-1)
           {

            dtake=0;dstop=0;
            if(!tpslIsPoint)
              {
               if(cmd==OP_BUYSTOP || cmd==OP_BUYLIMIT)
                 {
                  if(dtp>0) dtake=dtp;
                  if(dsl>0) dstop=dsl;
                 }

               if(cmd==OP_SELLSTOP || cmd==OP_SELLLIMIT)
                 {
                  if(dtp>0) dtake=dtp;
                  if(dsl>0) dstop=dsl;
                 }
                 }else{
               if(cmd==OP_BUYSTOP || cmd==OP_BUYLIMIT)
                 {
                  if(dtp>0) dtake=dnewopen+dtp*p;
                  if(dsl>0) dstop=dnewopen-dsl*p;
                 }

               if(cmd==OP_SELLSTOP || cmd==OP_SELLLIMIT)
                 {
                  if(dtp>0) dtake=dnewopen-dtp*p;
                  if(dsl>0) dstop=dnewopen+dsl*p;
                 }
              }

            if(cmd>=2)
              {

               if(!options_bNoModify) iticket=::OrderSend(m_name,cmd,ddlot,NormalizeDouble(dnewopen,digit),m_islippage,dstop,dtake,_comment,(int)m_imagic,dt,cl);
               else iticket=::OrderSend(m_name,cmd,ddlot,NormalizeDouble(dnewopen,digit),m_islippage,0,0,_comment,(int)m_imagic,dt,cl);
               if(iticket>0 && options_bVirtual) virtual_set(iticket,NormalizeDouble(dstop,digit),NormalizeDouble(dtake,digit)); //дублируем виртуальным режимом , он будет по умолчанию
              }
            else
              {
               iticket=::OrderSend(m_name,cmd,ddlot,NormalizeDouble(dnewopen,digit),m_islippage,0,0,_comment,(int)m_imagic,dt,cl);
              }

           }

         //модифицируем ордер после открытия для точной постановки стопа и тейка от цены открытия
         if(iticket!=-1 && cmd<2)
           {

            if(OrderSelect(iticket))
              {

               dnewopen=OrderOpenPrice();
               dtake=0;dstop=0;

               if(!tpslIsPoint)
                 {
                  if(cmd==OP_BUY)
                    {
                     if(dtp>0) dtake=dtp;
                     if(dsl>0) dstop=dsl;
                    }

                  if(cmd==OP_SELL)
                    {
                     if(dtp>0) dtake=dtp;
                     if(dsl>0) dstop=dsl;
                    }
                 }
               else
                 {
                  if(cmd==OP_BUY)
                    {
                     if(dtp>0) dtake=dnewopen+dtp*p;
                     if(dsl>0) dstop=dnewopen-dsl*p;
                    }

                  if(cmd==OP_SELL)
                    {
                     if(dtp>0) dtake=dnewopen-dtp*p;
                     if(dsl>0) dstop=dnewopen+dsl*p;
                    }
                 }

               if((dtake>0.0 || dstop>0.0) && dnewopen>0)
                 {
                  //поддержка возможности полностью отключить выставление стопа и тейка, они ставятся только виртуально
                  if(!options_bNoModify) bret=::OrderModify(iticket,NormalizeDouble(dnewopen,digit),NormalizeDouble(dstop,digit),NormalizeDouble(dtake,digit),dt); else bret=true;

                  if(options_bVirtual) virtual_set(iticket,NormalizeDouble(dstop,digit),NormalizeDouble(dtake,digit));
                 }

              }
           }

         ierrors=GetLastError();

         if(iticket==-1 || ierrors>0)
           {
            sinfo=StringConcatenate(__FUNCTION__,"(",m_name,",",string_type(cmd),",",ddlot,",",dnewopen,",",dstop,",",dtake,",",_comment,") Ask=",_Ask," Bid=",_Bid);
            is_open=ErrorMessage(ierrors,j,sinfo,options_iTimeout);
           }
         else
           {
            is_open=false;
            break;
           }

         if(iticket>0)
           {
            if(OrderSelect(iticket))
              {

               int i=ArraySize(m_orders);
               ArrayResize(m_orders,i+1);
               if(m_orders[i].getOrderByTicket(iticket))
                 {
                  //обновим счетчики, обновлять не будем , если мы хотим дооткрыть ордера к текущему и это не учитывать в расчетах
                  if(bAddCount)
                    {
                     m_all++;
                     orders[m_orders[i].OrderType]++;
                    }
                 }
              }
            if(iticket>0) m_total++;
            return iticket;
           }

        }
      
      if(iticket>0) m_total++;
      
      return iticket;
     }


‌Замер скорости открытия

С чем связаны скачки скорости открытия в тестере? Это так задумано или нет?

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

Это тестер или демо?

Местами у Вас вместо "else if" стоит просто "if". По какой-то причине используете вложенность if-ов, вместо того, чтобы использовать один if и &&.

Дорогим видится этот кусок

ArrayResize(m_orders,i+1);

Посмотрите в сторону параметра reserve_size.


 

Сделал замер исполнения всей функции OnTick получил в среднем 100-200 мс, но почему в тестере без визуализации идет тестирование медленное мне непонятно. Такое ощуение, что где то идет утечка и в процессе тестирования когда ордеров становиться много (советеник с усреднениями в 2 стороны) и скорость падает до 400-500 мс !!!

‌Для желающих покопаться прикладываю файл советника с текущей  библиотекой.

Файлы:
expert_080.zip  519 kb
 
fxsaber:

Это тестер или демо?

Местами у Вас вместо "else if" стоит просто "if". По какой-то причине используете вложенность if-ов, вместо того, чтобы использовать один if и &&.

Дорогим видится этот кусок

ArrayResize(m_orders,i+1);

Посмотрите в сторону параметра reserve_size.



все замеры в тестере, мне важно ускорить тестирование советников.
 

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

в‌от сама функция она и отжирает в зависимости от кол-ва ордеров до 400 мс

void virtual_init()
     {
      //id_tp, id_sl
      if(!options_bVirtual) return; //если виртуализация выключена, то не проверяем

      string del[],sglobal="",param[],n;
      int icount=0,i,itype=-1,isizeParam;
      ArrayResize(del,0);

      int isize=GlobalVariablesTotal();

      for(i=isize-1;i>=0;i--)
        {
         sglobal=GlobalVariableName(i);

         if(sglobal=="" || sglobal==NULL) continue;
         if(StringFind(sglobal,"_sl")<0 && StringFind(sglobal,"_tp")<0) continue; //пропустим не наши

         if(GlobalVariableGet(sglobal)==0) //все нулевые удалим
           {
            ArrayResize(del,icount+1);
            del[icount]=sglobal;
            icount++;
            continue;
           }

         string_explode("_",sglobal,param);

         isizeParam=ArraySize(param);
         if(isizeParam!=2 && isizeParam!=3) continue; //пропустим сразу неподходящие под наши правила

         if((isizeParam==2 && StringToInteger(param[0])>0) || (isizeParam==3 && param[0]=="virtual" && StringToInteger(param[1])>0))
           {
            int iticket=(int)StringToInteger(param[0]);//тикет
            if(isizeParam==3)
              {
               iticket=(int)StringToInteger(param[1]);//тикет
              }

            //check dtp,sl
            if(!OrderSelect(iticket)) continue;

            itype=OrderType();
            if(itype>=2 || OrderCloseTime()>0) //отложки тоже подчистим и уже закрытых ордеров тоже
              {
               ArrayResize(del,icount+1);
               del[icount]=sglobal;
               icount++;
               continue; //next order
              }

            n=OrderSymbol();

            if(CheckMagic(OrderMagicNumber()) && CheckSymbol(n))
              {
               double pt=0;
               double dtp=0;
               double dsl=0;

               //получим сразу 2 и стоп и тейк, на следующей итерации подчистим, если ордер был закрыт на этом шаге
               dtp=virtual_get_tp(iticket);
               dsl=virtual_get_sl(iticket);


               if(dtp!=0 || dsl!=0)
                 {
                  RefreshRates();
                 }

               if(dtp==0 && dsl==0) continue;

               double _Ask=Ask(n);
               double _Bid=Bid(n);

               if(dtp!=0 && ((_Bid>=dtp && itype==OP_BUY) || (_Ask<=dtp && itype==OP_SELL)))
                 {
                  if(OrderClose(iticket,-1))
                    {
                     Print("Close ",iticket);
                     ArrayResize(del,icount+1);
                     del[icount]=sglobal;
                     icount++;
                     m_total--;
                    }
                 }//close by dtp

               if(dsl!=0 && ((_Bid<=dsl && itype==OP_BUY) || (_Ask>=dsl && itype==OP_SELL)))
                 {
                  if(OrderClose(iticket,-1))
                    {
                     Print("Close ",iticket);
                     ArrayResize(del,icount+1);
                     del[icount]=sglobal;
                     icount++;
                     m_total--;
                    }
                 }//close by dstop
              }

           }

        }

      //delete global
      if(icount>0)
        {
         for(i=0;i<icount;i++)
           {

            if(del[i]!="" && GlobalVariableCheck(del[i]))
               GlobalVariableDel(del[i]);
           }
        }

     }
 
Alexandr Gavrilin:

Е‌сть класс COrderInfo в классе ордеров объявлен массив этого класса.

COrderInfo должен быть структурой.
 
fxsaber:
COrderInfo должен быть структурой.

сделал. Но не думаю что структура сильно отличается от класса в работе. Хотя да логично бы ее использовать как структуру.
 

Начал копать функцию virtual_init . Все остальное исполняется очень быстро, а вот она дает ощутимые задержки, пока повторяющиеся проверки в  ее функциях сделал запоминание на весь класс.

Н‌о особой скорости это не дало. может 50 мс выиграл.

 
Местами делаются повторные проверки, но это не должно так тормозить.
 

При отключении поддержки виртуальных стопов и тейков, скорость выполнения в тестере всего тела OnTick укладывается в среднем 70 мс из них обновление данных о оредерах в среднем около 30 мс

Причина обращения: