Libreria di classi generiche - bug, descrizione, domande, caratteristiche d'uso e suggerimenti - pagina 12

 
Sergey Dzyublik:

Capite cosa fa il codice se non c'è un'implementazione esplicita della funzioneGetHashCode per il tipo T?
Risposta: è un peccato perché il problema dell'implementazione mancante è messo a tacere. Tutti gli oggetti della stessa classe restituiranno lo stesso valore di hash.

Cosa c'entra l'implementazione (del corpo)? Ho aggiunto questo.

int GetHashCode(T & value)

Ho inserito il corpo dalla palla.

 
fxsaber:

Cosa c'entra l'implementazione (del corpo)? Ho aggiunto questo.

Ho messo il corpo da zero.


Ti viene detto delle mosche (che non dovresti farlo, il codice può farti ammalare in futuro) e tu vai avanti con le cotolette.
Ok, buon appetito.

 

Ha deciso di guardare le caratteristiche di velocità della soluzione proposta. Consulente esperto per il tester

#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);
}

L'Expert Advisor apre 100.000 trade e poi cerca i profitti totali dei trade casuali usando vari metodi (vedi commenti). Il risultato è

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

Qui confrontiamo i due valori evidenziati. Si scopre che l'accesso alla HashMap è 4 volte più veloce di quello degli sviluppatori. Ma agli sviluppatori include già la storia...

4 volte è abbastanza o non abbastanza veloce per questa situazione? Beh, qui sono 24 millisecondi. Se accedete spesso alla cronologia, potreste probabilmente risparmiare molto. Ma non sono sicuro.


Per un caso di test più realistico (2000 transazioni e 1 000 000 di accessi alla storia singola), il risultato è il seguente

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

Quasi 100 msec risparmiati per ogni passaggio! Se, diciamo, facciamo Optimize per 10.000 passaggi completi, allora la variante Hash finirà per essere 15 minuti più veloce.

È troppo presto per dare agli sviluppatori una "A" per la storia. È ovvio che possono accelerare, visto che anche la soluzione MQL sta diventando più veloce.

 
fxsaber:

Ha deciso di guardare le caratteristiche di velocità della soluzione proposta. Consulente esperto per il tester

L'Expert Advisor apre 100.000 trade e poi cerca i profitti totali dei trade casuali usando vari metodi (vedi commenti). Il risultato è

Qui confrontiamo i due valori evidenziati. Si scopre che l'accesso alla HashMap è 4 volte più veloce di quello degli sviluppatori. Ma agli sviluppatori include già la storia...

4 volte è abbastanza o non abbastanza veloce per questa situazione? Beh, qui sono 24 millisecondi. Se accedete spesso alla cronologia, potreste probabilmente risparmiare molto. Ma non sono sicuro.

Nelle chiamate attraverso la piattaforma, si passa attraverso gli oggetti di sincronizzazione due volte in GetDealProfitFull e una volta in GetDealProfitClear e un sacco di controlli obbligatori ad ogni iterazione.

Quindi la velocità è notoriamente più lenta che in modo pulito e ottimizzato con un mucchio di intarsi che lavorano su una hashmap locale pre-preparata.

 
Renat Fatkhullin:

Quando si chiama attraverso la piattaforma, si passa attraverso gli oggetti di sincronizzazione due volte in GetDealProfitFull e una volta in GetDealProfitClear e un sacco di controlli obbligatori ad ogni iterazione.

Pertanto, la velocità è intrinsecamente più lenta che in un lavoro pulito e ottimizzato basato su una hashmap locale preparata preliminarmente con un mucchio di inlay.

Corretto il mio post precedente. Ecco perché il doppio controllo si chiama Full.

Non capisco bene di quali costosi oggetti di sincronizzazione e di quali controlli stiamo parlando nello Strategy Tester per HistoryDealGetDouble?
 
fxsaber:

Corretto il mio post precedente. Il doppio controllo è il motivo per cui si chiama Full.

Non sono sicuro di quali costosi oggetti di sincronizzazione e masse di controlli stia parlando il Tester per HistoryDealGetDouble?

Qual è la differenza:

  1. Per un test hashmap, avete pre-popolato i dati nella memoria locale con un accesso veloce e non sincronizzato, mentre nelle chiamate alla piattaforma avete bisogno di cadere dentro la memoria
  2. In piattaforma la memorizzazione è di un tipo diverso, non hashmap
  3. quando si recupera un singolo valore sulla piattaforma, è necessario trattare la richiesta "come una prima volta", ricontrollando che tutti i dati siano corretti e presenti. nel caso in cui qualcosa sia cambiato tra le chiamate


Ho guardato il nostro codice - c'è un modo per ottimizzare le chiamate alla base commerciale. Cercheremo di implementare per il rilascio della prossima settimana.

 

Renat Fatkhullin:

Nella piattaforma, quando si estrae un singolo valore, dobbiamo trattare la query "come se fosse la prima volta", ricontrollando la correttezza e la presenza di tutti i dati

Ma TryGetValue non viene chiamato senza controllarne la correttezza? Secondo i log, si può vedere che HistorySelect nel tester è libero.

Quello che non capisco è perché per ottenere tutti i dati di un affare, è necessario chiamare un mucchio di costose funzioni HistoryDealGet*? C'è solo una chiamata per riempire la struttura MqlDeal.

Ovviamente, l'utente, quando vuole lavorare con la storia tramite HashMap, compilerà la CHashMap<ulong, MqlDeal>.

Forse dovremmo fare MqlDealInteger, MqlDealDouble, MqlDealString o qualcosa di simile, per non moltiplicare costose chiamate di unità, dato che tutta la tabella della storia è comunque nel tester? Ed è sufficiente controllare la correttezza di DealTicket una volta, non ogni volta.

 
fxsaber:

La chiamata TryGetValue non è fatta per controllare la correttezza? Secondo i log, si può vedere che HistorySelect è libero nel tester.

Come può essere gratis? Non è affatto gratis.


Quello che non capisco è perché per ottenere tutti i dati di un affare, è necessario chiamare un mucchio di costose funzioni HistoryDealGet*? C'è solo una chiamata per riempire la struttura MqlDeal.

Ovviamente, l'utente, quando vuole lavorare con la storia tramite HashMap, compila la CHashMap<ulong, MqlDeal>.

Non abbiamo una struttura MqlDeal perché i formati dei record commerciali sono fluttuanti e si espandono periodicamente. Senza di essa, è impossibile estendere la funzionalità della piattaforma.

Pertanto, l'unica opzione è quella di accedervi attraverso la funzione Get. E l'accesso ad altri campi del record precedentemente consultato è molto più veloce del primo accesso, perché il record è nella cache.

Ed è sufficiente controllare la correttezza di DealTicket una volta e non ogni volta.

Nel test di cui sopra, ogni volta i numeri delle offerte sono nuovi, il che interrompe costantemente la cache dell'offerta precedentemente selezionata. Inoltre, non c'è garanzia che qualcosa non sia cambiato tra una chiamata e l'altra. È ancora possibile scambiare tra le richieste per la storia.

 
Renat Fatkhullin:
In che modo è gratis? Non è affatto gratis.

Forum sul trading, sistemi di trading automatico e test di strategie di trading

Libreria di classi generiche - bug, descrizione, problemi, casi d'uso e suggerimenti

fxsaber, 2017.12.08 22:46

Il consulente apre 100.000 operazioni, poi cerca il profitto totale di operazioni casuali usando diversi metodi (vedi commenti). Risultato

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

Per 100.000 scambi (e lo stesso numero di ordini) 1 microsecondo è gratis. Dipende tutto dal tester.

Nel test di cui sopra, i numeri delle offerte sono nuovi ogni volta, il che interrompe costantemente la cache delle offerte precedentemente selezionate. Inoltre, non c'è garanzia che qualcosa non sia cambiato tra una chiamata e l'altra. È possibile scambiare tra le richieste per la storia.

Così la storia (soprattutto nel tester) viene solo integrata, i vecchi record non vengono cambiati. Stiamo parlando della variante Clear.


Sul mercato reale, sembra che anche quando un ordine è parzialmente eseguito e genera diverse transazioni, non arriva nella storia fino a quando non è completamente riempito o cancellato. Cioè si mantiene la regola della storia congelata.

 
fxsaber:

Per 100.000 scambi (e lo stesso numero di ordini) 1 microsecondo è gratis. Tutto ruota intorno al tester per tutto il tempo.

HistorySelect nel tester è assolutamente virtuale/falso, ancora di più con i parametri 0, INT_MAX. Questo è stato ottimizzato molto tempo fa.

Non puoi paragonare HistorySelect(mette l'intervallo di accesso nel tester) e HistoryDealSelect(ticket), che effettivamente cerca un ticket specifico e lo memorizza.