Generische Klassenbibliothek - Bugs, Beschreibung, Fragen, Nutzungsmöglichkeiten und Vorschläge - Seite 12

 
Sergey Dzyublik:

Verstehen Sie, was der Code tut, wenn es keine explizite Implementierung der FunktionGetHashCode für den Typ T gibt?
Antwort: Das ist schade, denn das Problem der fehlenden Umsetzung wird dadurch zum Schweigen gebracht. Alle Objekte der gleichen Klasse geben den gleichen Hash-Wert zurück.

Was hat die Umsetzung (Körper) damit zu tun?! Ich habe dies hinzugefügt.

int GetHashCode(T & value)

Ich habe den Körper von der Kugel aus eingesetzt.

 
fxsaber:

Was hat die Umsetzung (Körper) damit zu tun?! Ich habe dies hinzugefügt.

Ich habe die Karosserie von Grund auf neu aufgebaut.


Man erzählt dir von Fliegen (dass du das nicht tun solltest, der Code kann dich in Zukunft krank machen) und du erzählst von Schnitzeln.
OK, guten Appetit.

 

Ich beschloss, mir die Geschwindigkeitseigenschaften der vorgeschlagenen Lösung anzusehen. Expert Advisor für den Prüfer

#include <MT4Orders.mqh>
#include <Generic\HashMap.mqh>

CHashMap<ulong, double> DealsProfit;

// Создаем историю из Amount сделок в тестере
void CreateHistory( const int Amount, const double Lots = 0.1 )
{
  MqlTick Tick;
  
  if (SymbolInfoTick(_Symbol, Tick) && Tick.ask && Tick.bid)
    for (int i = (Amount >> 1) - 1; i >= 0; i--)
      OrderClose(OrderSend(_Symbol, OP_BUY, Lots, Tick.ask, 0, 0, 0), Lots, Tick.bid, 0);
}

// Заполняем массив случайно выбранными сделками
void GetDeals( const int Amount, const int MaxDealTicket, ulong &Deals[] )
{
  for (int i = ArrayResize(Deals, Amount) - 1; i >= 0; i--)  
    Deals[i] = MathRand() * MaxDealTicket / SHORT_MAX;
}

// Заполнили HashMap
void SetHashMap()
{
  if (HistorySelect(0, INT_MAX))
    for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
    {
      const ulong DealTicket = HistoryDealGetTicket(i);
      
      DealsProfit.Add(DealTicket, HistoryDealGetDouble(DealTicket, DEAL_PROFIT));
    }
}

double GetDealProfitHashClear( const ulong Deal )
{
  static double Profit = 0;
  
  return(DealsProfit.TryGetValue(Deal, Profit) ? Profit : 0);
}

double GetDealProfitFull( const ulong Deal )
{
  return(HistoryDealSelect(Deal) ? HistoryDealGetDouble(Deal, DEAL_PROFIT) : 0);
}

double GetDealProfitClear( const ulong Deal )
{
  return(HistoryDealGetDouble(Deal, DEAL_PROFIT));
}

typedef double (*GetDealProfit)( const ulong );

// Находим суммарный профит сделок из массива
double SumProfit( const ulong &Deals[], GetDealProfit DealProfit )
{
  double Profit = 0;
  
  for (int i = ArraySize(Deals) - 1; i >= 0; i--)
    Profit += DealProfit(Deals[i]);
    
  return(Profit);
}

#define  BENCH(A)                                                              \
{                                                                             \
  const ulong StartTime = GetMicrosecondCount();                              \
  A;                                                                          \
  Print("Time[" + #A + "] = " + (string)(GetMicrosecondCount() - StartTime)); \
} 

int OnInit()
{
  const int Amount = 100000;  
  CreateHistory(Amount); // Создаем историю из Amount сделок в тестере
  
  ulong Deals[];
  GetDeals(Amount, Amount, Deals); // Заполняем массив случайно выбранными сделками

  // Находим суммарный профит сделок из массива
  
  BENCH(Print(SumProfit(Deals, GetDealProfitFull))); // Полноценная классическая реализация
  
  BENCH(SetHashMap()); // Заполнили HashMap
  BENCH(Print(SumProfit(Deals, GetDealProfitHashClear))); // Реализация через HashMap
  
  BENCH(HistorySelect(0, INT_MAX));
  BENCH(Print(SumProfit(Deals, GetDealProfitClear))); // Реализация с предварительно загруженной историей
  
  return(INIT_FAILED);
}

Der Expert Advisor öffnet 100.000 Trades und sucht dann nach Gesamtgewinnen von zufälligen Trades mit verschiedenen Methoden (siehe Kommentare). Das Ergebnis ist

2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 38082
2017.12.05 00:00:00   Time[SetHashMap()] = 57849
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 7437
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 31669

Hier vergleichen wir die beiden hervorgehobenen Werte. Es stellt sich heraus, dass der HashMap-Zugriff 4 Mal schneller ist als der der Entwickler. Aber bei den Entwicklern ist die Geschichte bereits enthalten...

Ist 4 mal schnell genug oder nicht schnell genug für diese Situation? Hier sind es 24 Millisekunden. Wenn Sie häufig auf den Verlauf zugreifen, können Sie wahrscheinlich eine Menge sparen. Aber ich bin mir nicht sicher.


Für einen realistischeren Testfall (2000 Abschlüsse und 1 000 000 einzelne Zugriffe auf die Historie) ergibt sich folgendes Bild

2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 122969
2017.12.05 00:00:00   Time[SetHashMap()] = 816
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 23852
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 114427

Fast 100 msec pro Durchgang eingespart! Wenn wir z. B. Optimize für 10.000 vollständige Durchläufe durchführen, ist die Hash-Variante am Ende 15 Minuten schneller.

Es ist noch zu früh, um den Entwicklern eine "Eins" für Geschichte zu geben. Es ist offensichtlich, dass sie es beschleunigen können, denn auch die MQL-Lösung wird immer schneller.

 
fxsaber:

Ich beschloss, mir die Geschwindigkeitseigenschaften der vorgeschlagenen Lösung anzusehen. Expert Advisor für den Prüfer

Der Expert Advisor öffnet 100.000 Trades und sucht dann nach Gesamtgewinnen von zufälligen Trades mit verschiedenen Methoden (siehe Kommentare). Das Ergebnis ist

Hier vergleichen wir die beiden hervorgehobenen Werte. Es stellt sich heraus, dass der HashMap-Zugriff 4 Mal schneller ist als der der Entwickler. Aber bei den Entwicklern ist die Geschichte bereits enthalten...

Ist 4 mal schnell genug oder nicht schnell genug für diese Situation? Hier sind es 24 Millisekunden. Wenn Sie häufig auf den Verlauf zugreifen, können Sie wahrscheinlich eine Menge sparen. Aber ich bin mir nicht sicher.

Bei Aufrufen über die Plattform durchlaufen Sie die Synchronisationsobjekte zweimal in GetDealProfitFull und einmal in GetDealProfitClear sowie eine Menge obligatorischer Prüfungen bei jeder Iteration.

Die Geschwindigkeit ist also notorisch langsamer als bei einer sauberen und optimierten Version mit einer Reihe von Inlays, die auf einer vorbereiteten lokalen Hashmap arbeiten.

 
Renat Fatkhullin:

Beim Aufruf über die Plattform durchlaufen Sie Synchronisationsobjekte zweimal in GetDealProfitFull und einmal in GetDealProfitClear sowie eine Menge obligatorischer Prüfungen bei jeder Iteration.

Daher ist die Geschwindigkeit inhärent langsamer als bei einer sauberen und optimierten Arbeit auf der Grundlage eines vorbereiteten lokalen Hashmaps mit einer Reihe von Inlays.

Ich habe meinen vorherigen Beitrag korrigiert. Deshalb heißt die doppelte Kontrolle auch Full.

Ich verstehe nicht ganz, von welchen teuren Synchronisationsobjekten und vielen Prüfungen wir im Strategy Tester für HistoryDealGetDouble sprechen?
 
fxsaber:

Ich habe meinen vorherigen Beitrag korrigiert. Die doppelte Kontrolle ist der Grund, warum es Full heißt.

Ich bin mir nicht ganz sicher, von welchen teuren Synchronisationsobjekten und massenhaften Prüfungen der Tester bei HistoryDealGetDouble spricht?

Worin besteht der Unterschied?

  1. Für einen Hashmap-Test haben Sie die Daten in den lokalen Speicher mit schnellem unsynchronisiertem Zugriff vorgefüllt, während Sie bei Plattformaufrufen in den Speicher fallen müssen
  2. In der Plattform ist die Speicherung von einer anderen Art, nicht hashmap
  3. beim Abrufen eines einzelnen Wertes in einem Plattformaufruf müssen Sie die Anfrage "wie beim ersten Mal" behandeln und doppelt überprüfen, ob alle Daten korrekt und vorhanden sind. Sie wissen nicht, ob sich zwischen den Aufrufen etwas geändert hat


Ich habe mir unseren Code angesehen - es gibt eine Möglichkeit, die Aufrufe an die Handelsbasis zu optimieren. Wir werden versuchen, diese bis zur nächsten Woche zu implementieren.

 

Renat Fatkhullin:

Wenn Sie in der Plattform einen einzelnen Wert extrahieren, müssen Sie die Abfrage "wie beim ersten Mal" behandeln und die Korrektheit und das Vorhandensein aller Daten doppelt überprüfen

Aber wird TryGetValue nicht ohne Überprüfung der Korrektheit aufgerufen? Aus den Protokollen geht hervor, dass HistorySelect im Testgerät kostenlos ist.

Was ich nicht verstehe, ist, warum man, um alle Daten zu einem Geschäft zu erhalten, eine Reihe von teuren HistoryDealGet*-Funktionen aufrufen muss? Es gibt nur einen Aufruf, um die MqlDeal-Struktur zu füllen.

Wenn der Benutzer mit der Historie über die HashMap arbeiten will, wird er natürlich die CHashMap<ulong, MqlDeal> ausfüllen.

Vielleicht sollten wir MqlDealInteger, MqlDealDouble, MqlDealString oder etwas ähnliches machen, um teure Unit-Aufrufe nicht zu vervielfachen, da die gesamte Historientabelle sowieso im Tester ist? Und es reicht aus, die Korrektheit von DealTicket einmal zu überprüfen, nicht jedes Mal.

 
fxsaber:

Wird der TryGetValue-Aufruf nicht zur Überprüfung der Korrektheit durchgeführt? Aus den Protokollen können Sie ersehen, dass HistorySelect im Testgerät frei ist.

Wie kann es kostenlos sein? Es ist überhaupt nicht kostenlos.


Was ich nicht verstehe, ist, warum man, um alle Daten zu einem Geschäft zu erhalten, eine Reihe von teuren HistoryDealGet*-Funktionen aufrufen muss? Es gibt nur einen Aufruf, um die MqlDeal-Struktur zu füllen.

Wenn der Benutzer mit der Historie über die HashMap arbeiten will, muss er natürlich die CHashMap<ulong, MqlDeal> ausfüllen.

Wir haben keine MqlDeal-Struktur, weil die Handelsdatensatzformate fließend sind und regelmäßig erweitert werden. Ohne sie ist es unmöglich, die Funktionalität der Plattform zu erweitern.

Daher besteht die einzige Möglichkeit darin, über die Funktion Get auf sie zuzugreifen. Und der Zugriff auf andere Felder des zuvor aufgerufenen Datensatzes ist um ein Vielfaches schneller als der erste Zugriff, da der Datensatz zwischengespeichert wird.

Und es reicht aus, die Korrektheit von DealTicket einmal zu überprüfen und nicht jedes Mal.

Im obigen Test wird jedes Mal eine neue Anzahl von Geschäften angezeigt, wodurch der Cache des zuvor ausgewählten Geschäfts ständig unterbrochen wird. Außerdem gibt es keine Garantie dafür, dass sich zwischen den Anrufen nicht etwas geändert hat. Sie können immer noch zwischen den Anfragen für die Geschichte handeln.

 
Renat Fatkhullin:
Wie kann es kostenlos sein? Überhaupt nicht kostenlos.

Forum zum Thema Handel, automatisierte Handelssysteme und Testen von Handelsstrategien

Generische Klassenbibliothek - Bugs, Beschreibung, Probleme, Anwendungsfälle und Vorschläge

fxsaber, 2017.12.08 22:46

Der Berater eröffnet 100.000 Trades und sucht dann nach dem Gesamtgewinn von zufälligen Trades mit verschiedenen Methoden (siehe Kommentare). Ergebnis

2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1

Pro 100.000 Abschlüsse (und die gleiche Anzahl von Aufträgen) ist 1 Mikrosekunde kostenlos. Es geht um den Tester.

Im obigen Test wird die Anzahl der Angebote jedes Mal neu festgelegt, wodurch der Cache der zuvor ausgewählten Angebote ständig unterbrochen wird. Außerdem gibt es keine Garantie dafür, dass sich zwischen den Anrufen nicht etwas geändert hat. Sie können zwischen den Anfragen für den Verlauf tauschen.

So wird die Historie (insbesondere im Tester) nur ergänzt, die alten Datensätze werden nicht verändert. Die Rede ist von der Clear-Variante.


Auf dem realen Markt scheint es so zu sein, dass ein Auftrag, selbst wenn er teilweise ausgeführt wird und mehrere Geschäfte generiert, nicht in die Historie aufgenommen wird, bis er vollständig ausgeführt oder storniert wurde. D.h. die Regel der eingefrorenen Geschichte wird beibehalten.

 
fxsaber:

Bei 100.000 Geschäften (und der gleichen Anzahl von Aufträgen) ist 1 Mikrosekunde kostenlos. Es geht immer nur um den Tester.

HistorySelect im Tester ist absolut virtuell/falsch, erst recht mit den Parametern 0, INT_MAX. Dies wurde schon vor langer Zeit optimiert.

Sie können HistorySelect(legt den Zugriffsbereich in den Tester) und HistoryDealSelect(Ticket), das tatsächlich nach einem bestimmten Ticket sucht und es zwischenspeichert, nicht vergleichen.