Alternative Implementierungen von Standardfunktionen/-ansätzen

 

NormalisierenDouble

#define  EPSILON (1.0 e-7 + 1.0 e-13)
#define  HALF_PLUS  (0.5 + EPSILON)

double MyNormalizeDouble( const double Value, const int digits )
{
  // Добавление static ускоряет код в три раза (Optimize=0)!
  static const double Points[] = {1.0 e-0, 1.0 e-1, 1.0 e-2, 1.0 e-3, 1.0 e-4, 1.0 e-5, 1.0 e-6, 1.0 e-7, 1.0 e-8};

  return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
}

ulong Bench( const int Amount = 1.0 e8 )
{
  double Price = 1.23456;
  const double point = 0.00001;
  
  const ulong StartTime = GetMicrosecondCount();
  
  int Tmp = 0;
  
  for (int i = 0; i < Amount; i++)
  {
    Price = NormalizeDouble(Price + point, 5); // замените на MyNormalizeDouble и почувствуйте разницу
    
    // Если убрать, то общее время выполнения будет нулевым при любом Amount (Optimize=1) - круто! В варианте NormalizeDouble оптимизации такой не будет.  
    if (i + i > Amount + Amount)
      return(0);
  }
  
  return(GetMicrosecondCount() - StartTime);
}

void OnStart( void )
{
  Print(Bench());
    
  return;
};

Das Ergebnis ist 1123275 und 1666643 zu Gunsten von MyNormalizeDouble (Optimize=1). Ohne Optimierung ist er viermal schneller (pro Speicher).


 

Wenn Sie die

static const double Points[] = {1.0 e-0, 1.0 e-1, 1.0 e-2, 1.0 e-3, 1.0 e-4, 1.0 e-5, 1.0 e-6, 1.0 e-7, 1.0 e-8};

auf die Switch-Variante, können Sie die Qualität der Switch-Implementierung in Zahlen sehen.

 

Betrachten Sie die bereinigte Version des Skripts mit NormalizeDouble:

#define  EPSILON (1.0 e-7 + 1.0 e-13)
#define  HALF_PLUS  (0.5 + EPSILON)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double MyNormalizeDouble(const double Value,const int digits)
  {
   static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8};

   return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong BenchStandard(const int Amount=1.0 e8)
  {
   double       Price=1.23456;
   const double point=0.00001;
   const ulong  StartTime=GetMicrosecondCount();
//---
   for(int i=0; i<Amount;i++)
     {
      Price=NormalizeDouble(Price+point,5);
     }
   
   Print("Result: ",Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
//---
   return(GetMicrosecondCount() - StartTime);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong BenchCustom(const int Amount=1.0 e8)
  {
   double       Price=1.23456;
   const double point=0.00001;
   const ulong  StartTime=GetMicrosecondCount();
//---
   for(int i=0; i<Amount;i++)
     {
      Price=MyNormalizeDouble(Price+point,5);
     }
   
   Print("Result: ",Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
//---
   return(GetMicrosecondCount() - StartTime);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart(void)
  {
   Print("Standard: ",BenchStandard()," msc");
   Print("Custom:   ",BenchCustom(),  " msc");
  }

Ergebnisse:

Custom:   1110255 msc
Result:   1001.23456

Standard: 1684165 msc
Result:   1001.23456

Unmittelbare Bemerkungen und Erklärungen:

  1. static ist hier notwendig, damit der Compiler dieses Array außerhalb der Funktion nimmt und es nicht bei jedem Funktionsaufruf auf dem Stack aufbaut. Der C++-Compiler tut dasselbe.
    static const double Points
  2. Um zu verhindern, dass der Compiler die Schleife verwirft, weil sie nutzlos ist, sollten wir die Ergebnisse der Berechnungen verwenden. Drucken Sie zum Beispiel die Variable Preis.

  3. Es gibt einen Fehler in Ihrer Funktion - die Grenzen der Ziffern werden nicht überprüft, was leicht zu Array-Überläufen führen kann.

    Rufen Sie es zum Beispiel als MyNormalizeDouble(Price+point,10) auf und fangen Sie den Fehler ab:
    array out of range in 'BenchNormalizeDouble.mq5' (19,45)
    
    Die Methode der Beschleunigung durch Nichtkontrolle ist akzeptabel, aber nicht in unserem Fall. Wir müssen jede fehlerhafte Dateneingabe behandeln.

  4. Fügen wir eine einfache Bedingung für einen Index größer als 8 hinzu. Um den Code zu vereinfachen, ersetzen wir den Typ der Variablen digits durch uint, um einen Vergleich für >8 anstelle einer zusätzlichen Bedingung <0 durchzuführen
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    double MyNormalizeDouble(const double Value,uint digits)
      {
       static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8};
    //---
       if(digits>8)
          digits=8;
    //---
       return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
      }
    

  5. Führen wir den Code aus und... Wir sind überrascht!
    Custom:   1099705 msc
    Result:   1001.23456
    
    Standard: 1695662 msc
    Result:   1001.23456
    
    Ihr Code hat die Standardfunktion NormalizeDouble noch mehr überholt!

    Außerdem verkürzt sich durch die Hinzufügung der Bedingung sogar die Zeit (sie liegt sogar innerhalb der Fehlermarge). Warum gibt es einen solchen Geschwindigkeitsunterschied?

  6. All dies hat mit einem Standardfehler von Leistungstestern zu tun.

    Beim Schreiben von Tests sollten Sie die vollständige Liste der Optimierungen im Auge behalten, die vom Compiler angewendet werden können. Sie müssen sich darüber im Klaren sein, welche Eingabedaten Sie verwenden und wie diese zerstört werden , wenn Sie einen vereinfachten Mustertest schreiben.

    Lassen Sie uns die gesamte Palette der Optimierungen, die unser Compiler vornimmt, Schritt für Schritt bewerten und anwenden.

  7. Beginnen wir mit der konstanten Ausbreitung - dies ist einer der wichtigsten Fehler, die Sie in diesem Test gemacht haben.

    Sie haben die Hälfte Ihrer Eingabedaten als Konstanten. Lassen Sie uns das Beispiel mit Blick auf ihre Verbreitung umschreiben.

    ulong BenchStandard(void)
      {
       double      Price=1.23456;
       const ulong StartTime=GetMicrosecondCount();
    //---
       for(int i=0; i<1.0 e8;i++)
         {
          Price=NormalizeDouble(Price + 0.00001,5);
         }
    
       Print("Result: ",Price);
    //---
       return(GetMicrosecondCount() - StartTime);
      }
    
    ulong BenchCustom(void)
      {
       double      Price=1.23456;
       const ulong StartTime=GetMicrosecondCount();
    //---
       for(int i=0; i<1.0 e8;i++)
         {
          Price=MyNormalizeDouble(Price + 0.00001,5);
         }
    
       Print("Result: ",Price," ",1.0 e8);
    //---
       return(GetMicrosecondCount() - StartTime);
      }
    
    Nach dem Start hat sich nichts geändert - so muss es sein.

  8. Machen Sie weiter - inline Ihren Code (unser NormalizeDouble kann nicht inlined werden)

    So sieht Ihre Funktion in der Realität aus, nachdem sie zwangsläufig inline ist. Einsparungen bei Aufrufen, Einsparungen bei Array-Abrufen, Prüfungen werden aufgrund der ständigen Analyse entfernt:
    ulong BenchCustom(void)
      {
       double              Price=1.23456;
       const ulong         StartTime=GetMicrosecondCount();
    //---
       for(int i=0; i<1.0 e8;i++)
         {
          //--- этот код полностью вырезается, так как у нас заведомо константа 5
          //if(digits>8)
          //   digits=8;
          //--- распространяем переменные и активно заменяем константы
          if((Price+0.00001)>0)
             Price=int((Price+0.00001)/1.0 e-5+(0.5+1.0 e-7+1.0 e-13))*1.0 e-5;
          else
             Price=int((Price+0.00001)/1.0 e-5-(0.5+1.0 e-7+1.0 e-13))*1.0 e-5;
         }
    
       Print("Result: ",Price);
    //---
       return(GetMicrosecondCount() - StartTime);
      }
    
    Ich habe keine reinen Konstanten zusammengefasst, um Zeit zu sparen. Sie sind alle garantiert zur Kompilierzeit kollabiert.

    Führen Sie den Code aus und Sie erhalten die gleiche Zeit wie in der Originalversion:
    Custom:   1149536 msc
    Standard: 1767592 msc
    
    stören Sie sich nicht am Rattern der Zahlen - auf der Ebene der Mikrosekunden, des Timerfehlers und der schwebenden Belastung des Computers liegt dies im normalen Bereich. das Verhältnis bleibt voll erhalten.

  9. Schauen Sie sich den Code an, mit dessen Prüfung Sie aufgrund der festen Quelldaten begonnen haben.

    Da der Compiler über eine sehr leistungsfähige Optimierung verfügt, wurde Ihre Aufgabe effektiv vereinfacht.


  10. Wie sollten Sie also die Leistung testen?

    Wenn Sie verstehen, wie der Compiler arbeitet, müssen Sie ihn daran hindern, Voroptimierungen und Vereinfachungen vorzunehmen.

    Machen wir zum Beispiel den Parameter Ziffern variabel:

    #define  EPSILON (1.0 e-7 + 1.0 e-13)
    #define  HALF_PLUS  (0.5 + EPSILON)
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    double MyNormalizeDouble(const double Value,uint digits)
      {
       static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8};
    //---
       if(digits>8)
          digits=8;
    //---   
       return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    ulong BenchStandard(const int Amount=1.0 e8)
      {
       double       Price=1.23456;
       const double point=0.00001;
       const ulong  StartTime=GetMicrosecondCount();
    //---
       for(int i=0; i<Amount;i++)
         {
          Price=NormalizeDouble(Price+point,2+(i&15));
         }
    
       Print("Result: ",Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
    //---
       return(GetMicrosecondCount() - StartTime);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    ulong BenchCustom(const int Amount=1.0 e8)
      {
       double       Price=1.23456;
       const double point=0.00001;
       const ulong  StartTime=GetMicrosecondCount();
    //---
       for(int i=0; i<Amount;i++)
         {
          Price=MyNormalizeDouble(Price+point,2+(i&15));
         }
    
       Print("Result: ",Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
    //---
       return(GetMicrosecondCount() - StartTime);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnStart(void)
      {
       Print("Standard: ",BenchStandard()," msc");
       Print("Custom:   ",BenchCustom()," msc");
      }
    
    Starten Sie es und... erhalten wir das gleiche Geschwindigkeitsergebnis wie zuvor.

    Ihr Code wird um etwa 35 % besser als zuvor.

  11. Und warum ist das so?

    Wir können uns immer noch nicht vor der Optimierung durch Inlining retten. Die Einsparung von 100 000 000 Aufrufen durch die Weitergabe von Daten über den Stack an unsere Funktion NormalizeDouble, die ähnlich implementiert ist, könnte die gleiche Geschwindigkeitssteigerung bewirken.

    Es gibt einen weiteren Verdacht, dass unser NormalizeDouble nicht in den direct_call-Mechanismus implementiert wurde, wenn die Funktionsverschiebungstabelle im MQL5-Programm geladen wird.

    Wir werden es morgen früh überprüfen, und wenn ja, werden wir es in direct_call verschieben und die Geschwindigkeit erneut überprüfen.

Hier ist eine Studie von NormalizeDouble.

Unser MQL5-Compiler hat unsere Systemfunktion geschlagen, was seine Angemessenheit im Vergleich zur Geschwindigkeit von C++-Code zeigt.

 
fxsaber:

Wenn Sie die

auf die Switch-Variante, können Sie die Qualität der Switch-Implementierung in Zahlen sehen.

Sie verwechseln den direkten indizierten Zugriff auf ein statisches Array durch einen konstanten Index (der zu einer Konstanten aus einem Feld degeneriert) mit einem Schalter.

Switch kann mit einem solchen Gehäuse nicht wirklich konkurrieren. Switch verfügt über mehrere häufig verwendete Optimierungen der Form:

  • "notorisch geordnete und kurze Werte werden in ein statisches Array gestellt und indiziert" - die einfachste und schnellste, kann mit dem statischen Array konkurrieren, aber nicht immer
  • "mehrere Arrays durch geordnete und geschlossene Werteblöcke mit Zonengrenzprüfungen" - dies hat bereits eine Bremse
  • "wir prüfen zu wenige Werte durch if" - keine Geschwindigkeit, aber der Programmierer ist selbst schuld, er benutzt den Schalter unangemessen
  • "sehr spärliche geordnete Tabelle mit binärer Suche" - sehr langsam für die schlimmsten Fälle

In der Tat ist die beste Strategie für den Wechsel, wenn der Entwickler bewusst versucht, eine kompakte Menge von Werten in der unteren Reihe von Zahlen zu machen.

 
Renat Fatkhullin:

Betrachten Sie die bereinigte Version des Skripts mit NormalizeDouble:

Ergebnisse:


Unmittelbare Bemerkungen und Erklärungen:

  1. static wird hier benötigt, damit der Compiler dieses Array außerhalb der Funktion ablegt und nicht bei jedem Funktionsaufruf auf dem Stack aufbaut. Der C++-Compiler tut dasselbe.
Wenn "Optimieren=0" wahr ist. Mit "Optimize=1" kann man es sogar wegwerfen - der Optimizer-Compiler ist schlau, wie sich herausstellt.
  1. Um zu verhindern, dass der Compiler die Schleife wegen ihrer Nutzlosigkeit verwirft, müssen wir die Ergebnisse von Berechnungen verwenden. Drucken Sie zum Beispiel die Variable Preis.
Was für ein cooler Trick!
  1. Es gibt einen Fehler in Ihrer Funktion, der die Grenzen der Ziffern nicht überprüft, was leicht zu Array-Überläufen führen kann.

    Rufen Sie es zum Beispiel als MyNormalizeDouble(Price+point,10) auf und fangen Sie den Fehler ab:
    Die Methode der Beschleunigung durch Nichtkontrolle ist akzeptabel, aber nicht in unserem Fall. Wir müssen jede fehlerhafte Dateneingabe behandeln.

  2. Fügen wir eine einfache Bedingung für den Index größer als 8 hinzu. Um den Code zu vereinfachen, ersetzen wir den Typ der Variablen digits durch uint, um einen Vergleich für >8 anstelle der zusätzlichen Bedingung <0 durchzuführen
Das scheint optimaler zu sein!
double MyNormalizeDouble( const double Value, const uint digits )
{
  static const double Points[] = {1.0 e-0, 1.0 e-1, 1.0 e-2, 1.0 e-3, 1.0 e-4, 1.0 e-5, 1.0 e-6, 1.0 e-7, 1.0 e-8};
  const double point = digits > 8 ? 1.0 e-8 : Points[digits];

  return((int)((Value > 0) ? Value / point + HALF_PLUS : Value / point - HALF_PLUS) * point);
}
  1. Dies ist ein Standardfehler von Leistungstestern.

    Beim Schreiben von Tests sollten wir die gesamte Liste der Optimierungen im Auge behalten, die vom Compiler angewendet werden können. Sie müssen sich darüber im Klaren sein, welche Eingabedaten Sie verwenden und wie diese zerstört werden , wenn Sie einen vereinfachten Beispieltest schreiben.
  2. Wie sollten Sie also die Leistung testen?

    Wenn Sie verstehen, wie der Compiler arbeitet, müssen Sie ihn daran hindern, Voroptimierungen und Vereinfachungen vorzunehmen.

    Machen wir zum Beispiel den Parameter Ziffern variabel:
Vielen Dank für die ausführlichen Erläuterungen zur korrekten Vorbereitung der Leistungsmessungen des Compilers! Die Möglichkeit, die Konstante zu optimieren, wurde nicht wirklich berücksichtigt.

Dies ist die Studie NormalizeDouble.

Unser MQL5-Compiler hat unsere Systemfunktion geschlagen, was seine Eignung im Vergleich zur Geschwindigkeit von C++-Code zeigt.

Ja, auf dieses Ergebnis kann man stolz sein.
 
Renat Fatkhullin:

Sie verwechseln den direkten indizierten Zugriff auf ein statisches Array durch einen konstanten Index (der zu einer Konstanten aus einem Feld degeneriert) mit einem Schalter.

Switch kann mit einem solchen Gehäuse nicht wirklich konkurrieren. Switch verfügt über einige häufig verwendete Optimierungen dieser Art:

  • Die Variante "absichtlich geordnete und kurze Werte werden in ein statisches Array gestellt und per Schalter indiziert" ist die einfachste und schnellste und kann mit einem statischen Array konkurrieren, aber nicht immer.

Dies ist ein solcher Fall einer Bestellung.

In der Tat ist die beste Strategie für den Wechsel, wenn der Entwickler bewusst versucht hat, eine kompakte Menge von Werten in der unteren Gruppe von Zahlen zu machen.

Ich habe es auf einem 32-Bit-System ausprobiert. Dort führte der Austausch des Schalters im obigen Beispiel zu erheblichen Verzögerungen. Ich habe es nicht auf einem neuen Rechner getestet.
 
fxsaber:

Hier ist ein solcher Fall von Ordnungsmäßigkeit.

Wir müssen das separat prüfen, aber später.


Ich habe es auf einem 32-Bit-System ausprobiert. Der Wechsel zum Schalter im obigen Beispiel führte zu einer starken Bremsung. Ich habe es auf dem neuen Rechner nicht überprüft.

Es gibt eigentlich zwei kompilierte Programme in jedem MQL5: ein vereinfachtes für 32 Bit und ein maximal optimiertes für 64 Bit. In 32-Bit MT5 gilt der neue Optimierer überhaupt nicht und der Code für 32-Bit-Operationen ist so einfach wie MQL4 in MT4.

Die ganze Effizienz des Compilers, der den Code zehnmal schneller erzeugen kann, wenn er in der 64-Bit-Version von MT5 ausgeführt wird: https://www.mql5.com/ru/forum/58241

Wir konzentrieren uns voll und ganz auf 64-Bit-Versionen der Plattform.

 

Zum Thema NormalizeDouble gibt es diesen Unsinn

Forum zum Thema Handel, automatisierte Handelssysteme und Strategietests

Wie gehe ich eine Aufzählung konsequent durch?

fxsaber, 2016.08.26 16:08

In der Funktionsbeschreibung findet sich folgender Hinweis

Dies gilt nur für Symbole, die einen Mindestpreisschritt von 10^N haben, wobei N eine ganze Zahl und nicht positiv ist. Wenn der Mindestpreisschritt einen anderen Wert hat, dann ist die Normalisierung der Preisniveaus vor OrderSend eine sinnlose Operation, die in den meisten Fällen zu einem falschen OrderSend führt.


Es ist eine gute Idee, veraltete Darstellungen in der Hilfe zu korrigieren.

NormalizeDouble ist völlig diskreditiert. Nicht nur langsame Umsetzung, sondern auch sinnlos bei mehreren Börsensymbolen (z.B. RTS, MIX, etc.).

NormalizeDouble wurde ursprünglich von Ihnen für Order*-Operationen erstellt. Hauptsächlich für Preise und Lose. Aber nicht standardmäßige TickSize und VolumeStep erschienen. Und die Funktion ist einfach überflüssig. Aus diesem Grund schreiben sie langsamen Code. Ein Beispiel aus der Standardbibliothek
double CTrade::CheckVolume(const string symbol,double volume,double price,ENUM_ORDER_TYPE order_type)
  {
//--- check
   if(order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL)
      return(0.0);
   double free_margin=AccountInfoDouble(ACCOUNT_FREEMARGIN);
   if(free_margin<=0.0)
      return(0.0);
//--- clean
   ClearStructures();
//--- setting request
   m_request.action=TRADE_ACTION_DEAL;
   m_request.symbol=symbol;
   m_request.volume=volume;
   m_request.type  =order_type;
   m_request.price =price;
//--- action and return the result
   if(!::OrderCheck(m_request,m_check_result) && m_check_result.margin_free<0.0)
     {
      double coeff=free_margin/(free_margin-m_check_result.margin_free);
      double lots=NormalizeDouble(volume*coeff,2);
      if(lots<volume)
        {
         //--- normalize and check limits
         double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
         if(stepvol>0.0)
            volume=stepvol*(MathFloor(lots/stepvol)-1);
         //---
         double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
         if(volume<minvol)
            volume=0.0;
        }
     }
   return(volume);
  }

Nun, so ungeschickt können Sie nicht vorgehen! Es könnte um ein Vielfaches schneller sein, wenn man NormalizeDouble vergisst.

double NormalizePrice( const double dPrice, double dPoint = 0 )
{
  if (dPoint == 0) 
    dPoint = ::SymbolInfoDouble(::Symbol(), SYMBOL_TRADE_TICK_SIZE);

  return((int)((dPrice > 0) ? dPrice / dPoint + HALF_PLUS : dPrice / dPoint - HALF_PLUS) * dPoint);
}

Und für das gleiche Volumen dann tun

volume = NormalizePrice(volume, stepvol);

Für Preise tun

NormalizePrice(Price, TickSize)

Es scheint richtig, etwas Ähnliches hinzuzufügen, um den NormalizeDouble-Standard zu überladen. Der zweite Parameter "digits" ist ein double anstelle von int.

 

Im Jahr 2016 haben die meisten C++-Compiler den gleichen Optimierungsgrad erreicht.

MSVC lässt einen bei jedem Update über die Verbesserungen staunen, und Intel C++ als Compiler ist zusammengewachsen - es hat sich immer noch nicht von seinem "internen Fehler" bei großen Projekten erholt.

Eine weitere Verbesserung des Compilers in der 1400er Version besteht darin, dass er komplexe Projekte schneller kompilieren kann.

 

Zum Thema. Sie müssen Alternativen zu den Standardfunktionen erstellen, da diese manchmal die falsche Ausgabe liefern. Hier ein Beispiel für eine Alternative zu SymbolInfoTick

// Получение тика, который на самом деле вызвал крайнее событие NewTick
bool MySymbolInfoTick( const string Symb, MqlTick &Tick, const uint Type = COPY_TICKS_ALL )
{
  MqlTick Ticks[];
  const int Amount = ::CopyTicks(Symb, Ticks, Type, 0, 1);
  const bool Res = (Amount > 0);
  
  if (Res)
    Tick = Ticks[Amount - 1];
  
  return(Res);
}

// Возвращает в точности то, что SymbolInfoTick
bool CloneSymbolInfoTick( const string Symb, MqlTick &Tick )
{
  MqlTick TickAll, TickTrade, TickInfo;
  const bool Res = (MySymbolInfoTick(Symb, TickAll) &&
                    MySymbolInfoTick(Symb, TickTrade, COPY_TICKS_TRADE) &&
                    MySymbolInfoTick(Symb, TickInfo, COPY_TICKS_INFO));
  
  if (Res)
  {
    Tick = TickInfo;

    Tick.time = TickAll.time;
    Tick.time_msc = TickAll.time_msc;
    Tick.flags = TickAll.flags;
    
    Tick.last = TickTrade.last;
    Tick.volume = TickTrade.volume;    
  }
  
  return(Res);
}

Sie können SymbolInfoTick bei jedem Ereignis NewTick im Tester aufrufen und das Volumen-Feld summieren, um den Aktienumsatz zu ermitteln. Aber nein, das können Sie nicht! Wir müssen ein viel logischeres MySymbolInfoDouble erstellen.

 
fxsaber:

Zum Thema NormalizeDouble gibt es diesen Unsinn

NormalizeDouble wurde ursprünglich von Ihnen für Order*-Operationen erstellt. Hauptsächlich für Preise und Lose. Aber nicht standardmäßige TickSize und VolumeStep erschienen. Und die Funktion ist einfach überflüssig. Aus diesem Grund schreiben sie langsamen Code. Hier ist ein Beispiel aus der Standardbibliothek

Nun, so ungeschickt kann es nicht sein! Man kann es um ein Vielfaches schneller machen, wenn man NormalizeDouble vergisst.

Und für das gleiche Volumen tun

Für Preise tun

Es scheint richtig, so etwas als Überladung zum NormalizeDouble-Standard hinzuzufügen. Der zweite Parameter "digits" ist ein double anstelle von int.

Sie können alles drum herum optimieren.

Dies ist ein endloser Prozess. Aber in 99 % der Fälle ist es wirtschaftlich unrentabel.

Grund der Beschwerde: