汎用クラスライブラリ - バグ、説明、質問、使用上の特徴、提案 - ページ 12

 
セルゲイ・デジュブリク

T型に対するGetHashCode 関数の明示的な実装がない場合、コードがどうなるか理解できていますか?
答え:実装が足りないという問題が黙殺されてしまうので、興ざめです。同じクラスのオブジェクトはすべて同じハッシュ値を返します。

実装(ボディ)は関係あるのか!これを追加しました。

int GetHashCode(T & value)

ボールから本体を挿入しました。

 
fxsaber

実装(ボディ)は関係あるのか!これを追加しました。

ボディを一から入れました。


ハエについて言われ(そんなことしたらダメだよ、コードで将来病気になるよと)、カツについて語られる。
よし、腹ごしらえだ。

 

提案するソリューションの速度特性を見ることにした。テスター用Expert Advisor

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

Expert Advisorは10万トレードを開き、様々な方法(コメント参照)でランダムなトレードの総利益を検索します。その結果

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

ここでは、2つの強調表示された値を比較しています。HashMapのアクセスは開発者の4倍速いことが判明した。でも、開発者ではすでに歴史も含めて...。

この状況で4倍速は十分なのか、それとも不十分なのか?ここでは24ミリ秒です。履歴によくアクセスする人は、かなり節約できるのではないでしょうか。でも、どうなんだろう。


より現実的なテストケース(2000トレード、1,000 000シングルヒストリーアクセス)では、以下のような結果になります。

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

1パスあたり約100msecの短縮を実現例えば、10,000回のフルパスでOptimizeを実行した場合、Hashバリアントは15分早く終わります。

開発者に「History」の「A」をつけるのは時期尚早です。MQLソリューションですら高速化しているのだから、高速化できるのは明らかだ。

 
fxsaber

提案するソリューションの速度特性を見ることにした。テスター用Expert Advisor

Expert Advisorは10万トレードを開き、様々な方法(コメント参照)でランダムなトレードの総利益を検索します。その結果

ここでは、2つの強調表示された値を比較しています。HashMapのアクセスは開発者の4倍速いことが判明した。でも、開発者ではすでに歴史も含めて...。

この状況で4倍速は十分なのか、それとも不十分なのか?ここでは24ミリ秒です。履歴にアクセスする回数が非常に多ければ、かなりの節約になるのではないでしょうか。でも、どうなんだろう。

プラットフォームを介した呼び出しでは、GetDealProfitFullで2回、GetDealProfitClearで1回の同期オブジェクトを通過し、各反復で多くの必須チェックが行われます。

そのため、あらかじめ用意されたローカルハッシュマップ上で動作するインレイの束で最適化されたクリーンな状態よりも、速度が遅くなるのは有名な話です。

 
Renat Fatkhullin:

プラットフォーム経由で呼び出す場合、GetDealProfitFullで2回、GetDealProfitClearで1回、同期オブジェクトを通過させ、各反復で多くの必須チェックが行われます。

そのため、インレイの束をあらかじめ用意したローカルハッシュマップに基づくクリーンで最適化された作業と比べると、本質的に速度が遅くなる。

前回の投稿を修正しました。そのため、ダブルチェックはFullと呼ばれています。

HistoryDealGetDoubleの Strategy Testerの高価な同期オブジェクトと多くのチェックの話がよくわからないのですが?
 
fxsaber

前回の投稿を修正しました。ダブルチェックがあるからこそ、Fullと呼ばれるのです。

HistoryDealGetDoubleについて、高価な同期オブジェクトや大量のチェックについて、Testerがどのように話しているのか、よくわかりません。

何が違うのか。

  1. ハッシュマップテストでは、高速な非同期アクセスでローカルストレージにデータを事前投入していますが、プラットフォームコールでは、ストレージの内部に落ちる必要があります
  2. プラットフォームでは、ストレージはハッシュマップではなく、別の種類のものです。
  3. プラットフォーム上で単一の値を取得する場合、「初めて」のようにリクエストを処理する必要があり、すべてのデータが正しく、存在していることを再確認する必要があります。


コードを見てみると、トレード・ ベースの呼び出しを最適化する方法がありました。来週のリリースまでに実装するようにします。

 

レナト・ファットフーリン

プラットフォームでは、単一の値を抽出する場合、クエリーを「初めて扱うように」扱い、すべてのデータの正しさと存在を再確認する必要があります。

しかし、TryGetValueは正しさをチェックすることなく呼び出されるのでは?ログによると、テスターのHistorySelectは 無料であることがわかります。

理解できないのは、取引に関するすべてのデータを取得するために、なぜ高価なHistoryDealGet*関数を何度も呼び出す必要があるのか、ということです。MqlDeal-structureを埋めるための呼び出しは1回だけです。

当然ながら、HashMapを通して履歴を扱う場合、ユーザーはCHashMap<ulong, MqlDeal>を記入することになります。

MqlDealInteger, MqlDealDouble, MqlDealString または同様のものを作成し、高価なユニットコールを増やさないようにする必要があるかもしれません。また、DealTicketの正誤チェックは、毎回ではなく、一回で十分です。

 
fxsaber

TryGetValueの呼び出しは、正しさを確認するために行うのではないでしょうか?ログによると、テスターではHistorySelectは無料であることがわかります。

どうして無料なの?全然タダじゃないんです。


理解できないのは、取引に関するすべてのデータを取得するために、なぜ高価なHistoryDealGet*関数を何度も呼び出す必要があるのか、ということです。MqlDeal-structureを埋めるための呼び出しは1回だけです。

もちろん、HashMapで履歴を扱う場合は、CHashMap<ulong, MqlDeal>と記入します。

取引記録形式がフローティングで定期的に拡張されるため、MqlDeal構造体は持っていないのです。これなくして、プラットフォームの機能を拡張することはできません。

そのため、Get機能でアクセスするしかない。また、以前にアクセスしたレコードの他のフィールドへのアクセスは、レコードがキャッシュされているため、最初のアクセスよりも何倍も速くなります。

また、DealTicketの正誤チェックは、毎回でなく、1回で十分です。

上記のテストでは、毎回案件の数が新しくなるため、以前に選択した案件の キャッシュが常に乱される。また、通話と通話の間に何かが変わっていない保証はありません。履歴のリクエスト間交換は今まで通り可能です。

 
レナト・ファットフーリン
どうして無料なの?全く無料ではありません。

トレーディング、自動売買システム、トレーディング戦略のテストに関するフォーラム

汎用クラスライブラリ - バグ、説明、問題、使用例、提案

fxsaber さん 2017.12.08 22:46

アドバイザーが10万件の取引を開始し、さまざまな方法(コメント参照)を使ってランダムな取引の総利益を調べます。結果

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

10万トレード(注文数も同じ)あたり、1マイクロ秒が無料になります。テスターが大事なんです。

上記のテストでは、案件の数が毎回新しくなるため、以前に選択された案件の キャッシュが常に途切れることになる。また、通話と通話の間に何かが変わっていない 保証はありません。履歴のリクエスト間の交換が可能です。

だから、(特にテスターでの)履歴は補足されるだけで、古い記録は変更されない。クリアバリエーションについてです。


実際の市場では、注文が部分的に成立して複数の取引が発生しても、完全に埋まるかキャンセルされるまで履歴に残らないようです。つまり、凍結された歴史のルールが維持されているのです。

 
fxsaber

10万トレード(注文数も同じ)の場合、1マイクロ秒は無料です。ずっとテスターが中心なんです。

テスターのHistorySelectは、パラメータ0,INT_MAXで、絶対的に仮想・機能的です。これはずいぶん前に最適化されたものです。

HistorySelect(puts access range in tester) と、実際に特定のチケットを探しキャッシュする HistoryDealSelect(ticket) を比較することはできません。