Bibliothèque de classes génériques - bogues, description, questions, caractéristiques d'utilisation et suggestions - page 12

 
Sergey Dzyublik:

Comprenez-vous ce que fait le code s'il n'y a pas d'implémentation explicite de la fonctionGetHashCode pour le type T ?
Réponse : c'est une déception car le problème de la mise en œuvre manquante est réduit au silence. Tous les objets de la même classe renverront la même valeur de hachage.

Qu'est-ce que la mise en œuvre (le corps) a à voir avec cela ! J'ai ajouté ceci.

int GetHashCode(T & value)

J'ai inséré le corps de la balle.

 
fxsaber:

Qu'est-ce que la mise en œuvre (le corps) a à voir avec cela ! J'ai ajouté ceci.

J'ai monté le corps en partant de zéro.


On vous parle de mouches (qu'il ne faut pas faire ça, le code peut vous rendre malade à l'avenir) et on vous parle de côtelettes.
OK, bon appétit.

 

J'ai décidé d'examiner les caractéristiques de vitesse de la solution proposée. Conseiller expert pour le testeur

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

Le conseiller expert ouvre 100 000 transactions et recherche ensuite les bénéfices totaux des transactions aléatoires en utilisant diverses méthodes (voir commentaires). Le résultat est

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

Nous comparons ici les deux valeurs mises en évidence. Il s'avère que l'accès au HashMap est 4 fois plus rapide que celui des développeurs. Mais chez les développeurs, il inclut déjà l'histoire...

Est-ce que 4 fois est assez rapide ou pas assez rapide pour cette situation ? Eh bien ici, c'est 24 millisecondes. Si vous accédez souvent à l'historique, vous pourriez probablement économiser beaucoup. Mais je ne suis pas sûr.


Pour un cas de test plus réaliste (2000 transactions et 1 000 000 d'accès à l'historique unique), le résultat est le suivant

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

Près de 100 msec économisés par passage ! Si, par exemple, nous optimisons pour 10 000 passages complets, la variante Hash sera plus rapide de 15 minutes.

Il est trop tôt pour donner aux développeurs un "A" pour l'histoire. Il est évident qu'ils peuvent l'accélérer, puisque même la solution MQL est de plus en plus rapide.

 
fxsaber:

J'ai décidé de voir les caractéristiques de vitesse de la solution proposée. Conseiller expert pour le testeur

Le conseiller expert ouvre 100 000 transactions et recherche ensuite les bénéfices totaux des transactions aléatoires en utilisant diverses méthodes (voir commentaires). Le résultat est

Nous comparons ici les deux valeurs mises en évidence. Il s'avère que l'accès au HashMap est 4 fois plus rapide que celui des développeurs. Mais chez les développeurs, il inclut déjà l'histoire...

Est-ce que 4 fois est assez rapide ou pas assez rapide pour cette situation ? Eh bien ici, c'est 24 millisecondes. Si vous accédez souvent à l'historique, vous pourriez probablement économiser beaucoup. Mais je ne suis pas sûr.

Dans les appels à travers la plate-forme, vous passez par des objets de synchronisation deux fois dans GetDealProfitFull et une fois dans GetDealProfitClear et beaucoup de contrôles obligatoires à chaque itération.

Ainsi, la vitesse est notoirement plus lente qu'en propre et optimisée avec un tas d'inlays travaillant sur une hashmap locale préparée à l'avance.

 
Renat Fatkhullin:

Lorsque l'on fait appel à la plate-forme, on passe par des objets de synchronisation deux fois dans GetDealProfitFull et une fois dans GetDealProfitClear et par de nombreuses vérifications obligatoires à chaque itération.

Par conséquent, la vitesse est intrinsèquement plus lente que dans un travail propre et optimisé basé sur un hashmap local préparé au préalable avec un tas d'inlays.

J'ai corrigé mon message précédent. C'est pourquoi le double contrôle est appelé Full.

Je ne comprends pas bien quels sont les objets de synchronisation coûteux et les nombreuses vérifications dont nous parlons dans le testeur de stratégie pour HistoryDealGetDouble?
 
fxsaber:

J'ai corrigé mon message précédent. La double vérification est la raison pour laquelle on l'appelle Full.

Je ne suis pas tout à fait sûr des objets de synchronisation coûteux et des masses de vérifications dont parle le testeur pour HistoryDealGetDouble ?

Quelle est la différence ?

  1. Pour un test hashmap, vous avez pré-rempli les données dans le stockage local avec un accès rapide et non synchronisé, alors que dans les appels de plate-forme, vous devez tomber dans le stockage
  2. Dans la plateforme, le stockage est d'un type différent, pas de hashmap.
  3. lorsque vous récupérez une valeur unique sur la plateforme, vous devez traiter la demande "comme une première fois", en vérifiant une nouvelle fois que toutes les données sont correctes et présentes. au cas où quelque chose aurait changé entre les appels


J'ai regardé notre code - il y a un moyen d'optimiser les appels à la base commerciale. Nous essaierons de l'implémenter d'ici la sortie de la semaine prochaine.

 

Renat Fatkhullin:

Dans la plate-forme, lors de l'extraction d'une seule valeur, nous devons traiter la requête "comme si c'était la première fois", en vérifiant à nouveau l'exactitude et la présence de toutes les données.

Mais TryGetValue n'est-il pas appelé sans vérification de l'exactitude ? Selon les journaux, vous pouvez voir que HistorySelect dans le testeur est gratuit.

Ce que je ne comprends pas, c'est pourquoi pour obtenir toutes les données d'une transaction, il faut appeler un tas de fonctions HistoryDealGet* coûteuses ? Il n'y a qu'un seul appel pour remplir la structure MqlDeal.

Évidemment, l'utilisateur, lorsqu'il veut travailler avec l'historique par le biais de HashMap, remplira le CHashMap<ulong, MqlDeal>.

Peut-être devrions-nous faire MqlDealInteger, MqlDealDouble, MqlDealString ou quelque chose de similaire, afin de ne pas multiplier les appels unitaires coûteux, puisque toute la table d'historique est dans le testeur de toute façon ? Et il suffit de vérifier l'exactitude de DealTicket une seule fois, pas à chaque fois.

 
fxsaber:

L'appel TryGetValue n'est-il pas effectué pour vérifier l'exactitude des données ? Selon les journaux, vous pouvez voir que HistorySelect est libre dans le testeur.

Comment cela peut-il être gratuit ? Pas du tout gratuit.


Ce que je ne comprends pas, c'est pourquoi pour obtenir toutes les données d'une transaction, il faut appeler un tas de fonctions HistoryDealGet* coûteuses ? Il n'y a qu'un seul appel pour remplir la structure MqlDeal.

Évidemment, l'utilisateur, lorsqu'il veut travailler avec l'historique via HashMap, remplit le CHashMap<ulong, MqlDeal>.

Nous ne disposons pas d'une structure MqlDeal car les formats d'enregistrement des échanges sont flottants et s'étendent périodiquement. Sans elle, il est impossible d'étendre les fonctionnalités de la plateforme.

Par conséquent, la seule option est d'y accéder par la fonction Get. Et l'accès à d'autres champs de l'enregistrement consulté précédemment est plusieurs fois plus rapide que le premier accès, car l'enregistrement est mis en cache.

Et il suffit de vérifier l'exactitude puis DealTicket une fois et non à chaque fois.

Dans le test ci-dessus, à chaque fois, le nombre d'offres est nouveau, ce qui perturbe constamment le cache de l'offre précédemment sélectionnée. En outre, il n'y a aucune garantie que quelque chose n'a pas changé entre les appels. Vous pouvez toujours échanger entre les demandes pour l'historique.

 
Renat Fatkhullin:
Comment ça, c'est gratuit ? Pas du tout gratuit.

Forum sur le trading, les systèmes de trading automatisé et les tests de stratégies de trading

Bibliothèque de classes génériques - bogues, description, problèmes, cas d'utilisation et suggestions

fxsaber, 2017.12.08 22:46

Le conseiller ouvre 100 000 transactions, puis recherche le bénéfice total de transactions aléatoires en utilisant différentes méthodes (voir commentaires). Résultat

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

Pour 100 000 transactions (et le même nombre d'ordres), une microseconde est gratuite. Tout tourne autour du testeur, tout le temps.

Dans le test ci-dessus, le nombre d'offres est nouveau à chaque fois, ce qui perturbe constamment le cache des offres sélectionnées précédemment. En outre, il n'y a aucune garantie que quelque chose n'a pas changé entre les appels. Vous pouvez échanger entre les demandes pour l'historique.

Ainsi, l'histoire (surtout chez le testeur) est seulement complétée, les anciens dossiers ne sont pas modifiés. Nous parlons de la variante Clear.


Sur le marché réel, il semble que même lorsqu'un ordre est partiellement exécuté et génère plusieurs transactions, il ne figure pas dans l'historique tant qu'il n'est pas complètement exécuté ou annulé. C'est-à-dire que la règle de l'histoire figée est maintenue.

 
fxsaber:

Pour 100 000 transactions (et le même nombre d'ordres), une microseconde est gratuite. Tout tourne autour du testeur, tout le temps.

HistorySelect dans le testeur est absolument virtuel/faux, encore plus avec les paramètres 0, INT_MAX. Cela a été optimisé il y a longtemps.

Vous ne pouvez pas comparer HistorySelect(met la plage d'accès dans le testeur) et HistoryDealSelect(ticket), qui recherche en fait un ticket spécifique et le met en cache.