English Русский 中文 Español Deutsch Português
パターンと例(第I部): マルチトップ

パターンと例(第I部): マルチトップ

MetaTrader 5トレーディングシステム | 29 9月 2021, 09:53
572 0
Evgeniy Ilin
Evgeniy Ilin

内容


概要

パターンは多くのトレーダーによって使用されているため、インターネットでよく議論されます。パターンは、後に続く価格変動の方向を特定するための視覚的分析基準と呼ぶことができます。アルゴリズム取引はそれとは異なります。アルゴリズム取引に視覚的な基準はありません。エキスパートアドバイザーとインジケーターには、価格シリーズを操作するための個別の方法があります。長所と短所は両者にあります。コードには、人間の思考の幅と分析の質が欠けていますが、比類のない速度と単位時間あたりに処理される数値データまたは論理データの比類のない量という、価値ある利点があります。マシンに何をするかを指示するのは簡単ではありません。これには練習が必要です。時間が経つにつれて、プログラマーはマシンを理解し始め、マシンはプログラマーを理解し始めます。この連載では、初心者は思考を構造化し、複雑なタスクをより単純な手順に分割する方法を学ぶことができます。


反転パターンについて

個人的に言って、反転パターンの定義は曖昧すぎます。さらに、反転パターンには基盤となる数学がありません。正直なところ、基盤となる数学はどのパターンにもないため、ここで考慮できる数学は統計だけです。唯一の真実の基準となる統計は、実際の取引に基づいて集められています。明らかに、いかなる情報源も非常に正確な統計を提供することはできません。1つの特定の研究問題についてそのようなデータを提供することには意味もありません。ここでの唯一の解決策は、ストラテジーテスターでのバックテストと視覚化です。このアプローチではデータの質は低くなりますが、データ量と速度は紛れもない利点です。 

もちろん、反転パターンはトレンド反転を特定するための十分なツールではありませんが、レベルやローソク足分析などの他の分析方法と組み合わせれば、望ましい結果を生み出すことができます。この連載では、パターンは特に興味深い分析方法とは見なされていませんが、アルゴリズム取引スキルの練習に使用できます。練習に加え、アルゴリズム取引のためでないとしてもトレーダーの目のためも、興味深くそして有用な補助ツールが手に入るでしょう。有用な指標の価値は高いものです。


マルチトップを選ぶ理由 — その特定の機能

このパターンは、そのシンプルさからインターネットで非常に人気になっています。複雑さを一切ともなわないという理由だけで、さまざまな取引商品やさまざまなチャートの時間枠で非常に一般的です。さらに、このパターンを詳しく見ると、アルゴリズム取引とMQL5言語機能を使用して手法の概念を拡張できることがわかります。ダブルトップだけに制限されない一般的なコードを作成してみることにしますす。賢明に作成されたプロトタイプは、すべてのパターンのハイブリッドと後継者を探索するのに使用できます。

マルチトップの古典的な後継者は、非常に人気のある「頭と肩」のパターンです。残念ながら、このパターンを取引する方法に関する構造化された情報はありません。この問題は、多くの一般的なストラテジーに共通しています。美しい単語はたくさんありますが、統計がないためです。この記事では、これらをアルゴリズム取引の枠組みで使用できるかどうかを理解しようと思います。デモ口座やリアル口座で取引せずに統計を収集する唯一の方法は、ストラテジーテスターの機能を使用することです。このツールがなければ、特定のストラテジーに関しての複雑な結論を出すことはできません。


ダブルトップ概念の拡張

記事のトピックに関して、ダブルトップから始まるパターンの木として図を描いてみます。これは、この概念の可能性がどれほど広いかを理解するのに役立つでしょう。

木

いくつかのパターンの概念がほぼ同じアイデアに基づいていると仮定して、組み合わせることにしました。このアイデアの始まりは単純です。あらゆる方向への適切な動きを見つけ、それが反転するはずの場所を正しく特定することです。提案されたパターンを目視したら、補助線を何本か正しく描く必要があります。これは、パターンが特定の基準を満たしているかどうかを評価し、目標および損切りレベルとともに市場エントリポイントを特定するのに役立ちます。ここでは、目標の代わりに利食いを使用できます。

パターンにはいくつかの一般的な構造原理があり、それに基づいてその概念を組み合わせることができます。このような明確な定義が、アルゴリズムトレーダーと手動トレーダーの分け目となります。不確実性と同じ原則の複数の解釈は、期待外れの結果につながる可能性があります。

基本的なパターンは次のとおりです。

  1. ダブルトップ
  2. トリプルトップ
  3. 頭と肩

これらのパターンの構造と使用原理は同様です。すべてが反転を特定することを目的としており、補助線に関してのロジックも同様です。ダブルトップの例を考えてみてください。

二重極値

上の図では、必要なすべての線に番号が付けられています。これらは、次のことを意味します。

  1. トレンドの抵抗
  2. 悲観的な山を定義するための補助線(「首」だと思う人もいる)
  3. ネックライン
  4. 楽観的な目標(=取引の利食いレベル)
  5. 最大許容損切レベル(一番上に設定)
  6. 楽観的な予測線(前のトレンドの動きに等しい)

悲観的な目標は、市場に最も近いエッジからのネックラインの交点を基準にして決定されます。「1」と「2」の間の距離を取り、「t」として示します。提案された反転の方向に同じ距離を測定します。楽観的な目標の最小値も同様の方法で決定されますが、距離は「5」と「3」の間で測定され、「s」として示されます。


マルチトップをレンダリングするコードを書く

これらのパターンを定義するための推論ロジックを定義することから始めましょう。パターンを見つけるには、バーごとのロジックに固執する必要があります。つまり、ティックではなくバーで作業します。この場合、不必要な計算を回避できるため、ターミナルの負荷が大幅に軽減されます。まず、パターンを探す独立したオブザーバーを象徴するクラスを見つけます。正しいパターン検出に必要な操作はすべてのインスタンスの一部になるため、検索はインスタンス内で実行されます。このソリューションを選択したのは、たとえば、機能を拡張したり既存の機能を変更したりする必要があるときなどに、さらにコードを変更できるようにするためです。

クラスマップ

クラスの内容を検討することから始めましょう。

class ExtremumsPatternFamilySearcher// class simulating an independent pattern search
   {
   private:
   int BarsM;// how many bars on chart to use
   int MinimumSeriesBarsM;// the minimum number of bars in a row to detect a top
   int TopsM;// number of tops in the pattern
   int PointsPessimistM;// minimum distance in points to the nearest target
   double RelativeUnstabilityM;// maximum excess of the head size relative to the minimum shoulder
   double RelativeUnstabilityMinM;// minimum excess of the head size relative to the minimum shoulder
   double RelativeUnstabilityTimeM;// maximum excess of head and shoulders sizes
   bool bAbsolutelyHeadM;// whether a pronounced head is required
   bool bRandomExtremumsM;// random selection of extrema
     


   struct Top// top data
      {
      datetime Datetime0;// time of the candlestick closest to the market
      datetime Datetime1;// time of the next candlestick
      int Index0;// index of the candlestick closest to the market
      int Index1;// index of the next candlestick
      datetime DatetimeExtremum;// time of the top
      int IndexExtremum;// index of the top
      double Price;// price of the top
      bool bActive;// if the top is active (if not, then it does not exist)
      };
   
   struct Line// line
      {
      double Price0;// price of the candlestick closest to the market, to which the line is bound
      datetime Time0;// time of the candlestick closest to the market, to which the line is bound
      double Price1;// price of the farthest candlestick to which the line is bound
      datetime Time1;// time of the farthest candlestick to which the line is bound
      datetime TimeX;// time of the X point
      int Index1;// index of the left edge
      bool DirectionOfFormation;// direction
      double C;// free coefficient in the equation
      double K;// aspect ratio
   
      void CalculateKC()// find unknowns in the equation
         {
         if ( Time0 != Time1 ) K=double(Price0-Price1)/double(Time0-Time1);
         else K=0.0;
         C=double(Price1)-K*double(Time1);
         }
      
      double Price(datetime T)// function of line depending on time
         {
         return K*T+C;
         }
      };
   
   public:   
   
   ExtremumsPatternFamilySearcher(int BarsI,int MinimumSeriesBarsI,int TopsI,int PointsPessimistI, double RelativeUnstabilityI,
   double RelativeUnstabilityMinI,double RelativeUnstabilityTimeI,bool bAbsolutelyHeadI,bool bRandomExtremumsI)// parametric constructor
      {
      BarsM=BarsI;
      MinimumSeriesBarsM=MinimumSeriesBarsI;
      TopsM=TopsI;
      PointsPessimistM=PointsPessimistI;
      RelativeUnstabilityM=RelativeUnstabilityI;
      RelativeUnstabilityMinM=RelativeUnstabilityMinI;
      RelativeUnstabilityTimeM=RelativeUnstabilityTimeI;
      bAbsolutelyHeadM=bAbsolutelyHeadI;
      bRandomExtremumsM=bRandomExtremumsI;
      bPatternFinded=bFindPattern();
      }
      
   int FormationDirection;// direction of the formation (multiple top or bottom, or none at all) ( -1,1,0 )      
   bool bPatternFinded;// if the pattern was found during formation
   Top TopsUp[];// required upper extrema
   Top TopsDown[];// required lower extrema
   Top TopsUpAll[];// all upper extrema
   Top TopsDownAll[];// all lower extrema
   int RandomIndexUp[];// array for the random selection of the tops index
   int RandomIndexDown[];// array for the random selection of the bottoms index
   Top StartTop;// where the formation starts (top farthest from the market)
   Top EndTop;// where the formation ends (top closest to the market)
   Line Neck;// neck
   Top FarestTop;// top farthest from the neck (will be used to determine the head or the formation size) or the same as the head
   Line OptimistLine;// line of optimistic forecast
   Line PessimistLine;// line of pessimistic forecast
   Line BorderLine;// line at the edge of the pattern
   Line ParallelLine;// line parallel to the trend resistance
   
      
   private:
   void SetTopsSize();// setting sizes for arrays with tops
   bool SearchFirstUps();// search for tops
   bool SearchFirstDowns();// search for bottoms
   void CalculateMaximum(Top &T,int Index0,int Index1);// calculate the maximum price between two bars
   void CalculateMinimum(Top &T,int Index0,int Index1);// calculate the minimum price between two bars
   bool PrepareExtremums();// prepare extrema
   bool IsExtremumsAbsolutely();// control the priority of tops
   void DirectionOfFormation();// determine the direction of the formation
   void FindNeckUp(Top &TStart,Top &TEnd);// find neck for the bullish pattern
   void FindNeckDown(Top &TStart,Top &TEnd);// find neck for the bearish pattern
   void SearchFarestTop();// find top farthest from the neck
   bool bBalancedExtremums();// initial balancing of extrema (so that they do not differ much)
   bool bBalancedExtremumsHead();// if a pattern has more than 2 tops, we can check for a pronounced head
   bool bBalancedExtremumsTime();// require that the extrema be not very far in time relative to the minimum distance
   bool bBalancedHead();// balance the head (in other words, require that it be neither the first nor the last one on the list of tops, if there are more than three of them)
   bool CorrectNeckUpLeft();// adjust the neck so as to find the intersection of price and neck (this creates prerequisites for the previous trend) 
   bool CorrectNeckDownLeft();// similarly for the bottom
   int CorrectNeckUpRight();// adjust the neck so as to find the intersection of price and neck on the right or at the current price position, which is the same (to determine the entry point)
   int CorrectNeckDownRight();// similarly for the bottom
   void SearchLineOptimist();// calculate the optimistic forecast line
   bool bWasTrend();// determine whether a trend preceded the pattern definition (in this case the optimistic target line is considered as the trend beginning)
   void SearchLineBorder();// determine trend resistance or support (usually a sloping line)
   void CalculateParallel();// determine a line parallel to support or resistance (crosses the neck at the pattern low or high)
   bool bCalculatePessimistic();// calculate the line of the pessimistic target
   bool bFindPattern();// perform all the above actions
   int iFindEnter();// find intersection with the neck
   public:
   void CleanAll();// clean up objects
   void DrawPoints();// draw points
   void DrawNeck();// draw the neck
   void DrawLineBorder();// line at the border
   void DrawParallel();// line parallel to the border
   void DrawOptimist();// line of optimistic forecast
   void DrawPessimist();// line of pessimistic forecast
   };

クラスは、人が機械の代わりに実行するだろう一連の操作を表します。フォーメーションの検出は、連続した一連の単純作業に分割できます。数学には、方程式を解く方法がわからないければ単純化するという規則があります。この規則は、数学だけでなく、あらゆるアルゴリズムにも適用されます。検出ロジックは最初は明確ではありません。ただし、検出をどこで始めるかがわかれば、タスクははるかに簡単になります。この場合、パターン全体を見つけるために、トップまたはボトムのいずれか、あるいは実際には両方を検索します。

トップとボトムの特定

トップとボトムがなければパターン全体が無意味です。これは、トップとボトムの存在がパターンの必須条件であるためですが、この条件だけでは十分ではありません。トップを特定する方法には色々あります。最も重要な条件は顕著な半波の存在です。半波は2つの顕著な反対の動きによって決定されます。この場合、一方向に数本のバーが並んでいる必要があります。この目的のために、動きの存在を示す一方向のバーの最小数を決定する必要があります。そのための入力変数を指定します。 

bool ExtremumsPatternFamilySearcher::SearchFirstUps()// find tops
   {
   int NumUp=0;// the number of found tops
   int NumDown=0;// the number of found bottoms
   bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
   bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
   bool bNextUp=true;// can we move on to searching for the next top
   bool bNextDown=true;// can we move on to searching for the next bottom
   
   for(int i=0;i<ArraySize(TopsUp);i++)// before search, set all necessary tops to an inactive state
      {
      TopsUp[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsUpAll);i++)// before search, set all tops to an inactive state
      {
      if (!TopsUpAll[i].bActive) break;
      TopsUpAll[i].bActive=false;
      }
               
   
   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top
         {
         if ( bNextUp )// if it is allowed to search for the next top
            {
            bDown=true;
            for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops
               {
               if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward
                  {
                  bDown=false;
                  break;
                  }
               }
            if ( bDown )
               {
               TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-1;
               bNextUp=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bDown )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
         {
         bUp=true;                  
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction
            {
            if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward
               {
               bUp=false;
               break;
               }
            }
         if ( bUp )
            {
            TopsUpAll[NumUp].Datetime1=Time[i];
            TopsUpAll[NumUp].Index1=i;
            TopsUpAll[NumUp].bActive=true;
            bNextUp=false;
            }   
         } 
      // after that, register the found formation as a top, if it is a top
      if ( bDown && bUp )
         {
         CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);// calculate extremum between two bars
         bNextUp=true;
         bDown=false;
         bUp=false;
         NumUp++;
         }
      }
   if ( NumUp >= TopsM ) return true;// if the required number of tops have been found
   else return false;
   }

ボトムは反対の方法で定義されます。

bool ExtremumsPatternFamilySearcher::SearchFirstDowns()// find bottoms
   {
   int NumUp=0;
   int NumDown=0;
   bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
   bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
   bool bNextUp=true;// can we move on to searching for the next top
   bool bNextDown=true;// can we move on to searching for the next bottom

   for(int i=0;i<ArraySize(TopsDown);i++)// before search, set all necessary bottoms to an inactive state
      {
      TopsDown[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsDownAll);i++)// before search, set all bottoms to an inactive state
      {
      if (!TopsDownAll[i].bActive) break;
      TopsDownAll[i].bActive=false;
      }

   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top
         {
         if ( bNextDown )// if it is allowed to search for the next bottom
            {
            bUp=true;               
            for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops
               {
               if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward
                  {
                  bUp=false;
                  break;
                  }
               }
            if ( bUp )
               {
               TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-1;
               bNextDown=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bUp )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
         {   
         bDown=true;                              
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction
            {
            if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward
               {
               bDown=false;
               break;
               }
            }
         if ( bDown )
            {
            TopsDownAll[NumDown].Datetime1=Time[i];
            TopsDownAll[NumDown].Index1=i;
            TopsDownAll[NumDown].bActive=true;
            bNextDown=false;              
            }
         } 
      // after that, register the found formation as a bottom, if it is a bottom
      if ( bDown && bUp )
         {
         CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);// calculate extremum between two bars
         bNextDown=true;
         bDown=false;
         bUp=false;            
         NumDown++;
         }
      }
      
   if ( NumDown == TopsM ) return true;//if the required number of bottoms have been found
   else return false;
   }

この場合、フラクタルのロジックは使用せず、代わりに、トップとボトムを特定するための独自のロジックを作成しました。フラクタルより良くも悪くもないと思いますが、少なくとも外部機能を使用する必要はありません。さらに、いるとは限らない、不要な組み込み言語関数を使用する必要がありません。これらの関数は良いかもしれませんが、この場合は冗長です。この関数はすべてのトップとボトムを特定します。これは、将来作業します。次の画像は、この関数で何が起こっているかを視覚的に表したものです。

トップとボトムの検索

まず動き1を検索してから動き2を検索します。最後に3でトップまたはボトムを特定します。3のロジックは、次のような2つの別々の関数で実装されます。

void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,int Index0,int Index1)// if 2 intermediate points are found, find High between them
   {
   double MaxValue=High[Index0];
   datetime MaxTime=Time[Index0];
   int MaxIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( High[i] >  MaxValue )
         {
         MaxValue=High[i];
         MaxTime=Time[i];
         MaxIndex=i;
         }
      }
   T.DatetimeExtremum=MaxTime;
   T.IndexExtremum=MaxIndex;
   T.Price=MaxValue;
   }
   
void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,int Index0,int Index1)//if 2 intermediate points are found, find Low between them
   {
   double MinValue=Low[Index0];
   datetime MinTime=Time[Index0];
   int MinIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( Low[i] <  MinValue ) 
         {
         MinValue=Low[i];
         MinTime=Time[i];
         MinIndex=i;
         }
      } 
   T.DatetimeExtremum=MinTime;
   T.IndexExtremum=MinIndex;
   T.Price=MinValue;      
   }

次に、これらすべてを事前に準備されたコンテナに納めます。ロジックは次のとおりです。クラス内で使用されるすべての構造体では、データを段階的に追加する必要があります。すべての手順と段階を通過した後、必要なデータが出力され、このデータを使用して、パターンをチャートにグラフィカルに表示できます。もちろん、トップとボトムを特定するロジックは異なる場合があります。私の目的は、複雑なものに対して単純な検出ロジックを示すことだけです。

使用するトップの選択

見つかったトップとボトムは中間的なものにすぎません。それらを見つけたら、肩として機能するのに最も適切と思われるトップを選択する必要があります。コードにはマシンビジョンがないため、これを確実に判断することはできません(一般に、このような複雑な手法を使用してもパフォーマンスが向上する可能性はほとんどありません)。とりあえず、市場に最も近いトップを選択します。

bool ExtremumsPatternFamilySearcher::PrepareExtremums()// assign the tops with which we will work
   {
   int Quantity;// an auxiliary counter for random tops
   int PrevIndex;// an auxiliary index for maintaining the order of indexes (increment only)
   
   for(int i=0;i<TopsM;i++)// simply select the tops that are closest to the market
      {
      TopsUp[i]=TopsUpAll[i];
      TopsDown[i]=TopsDownAll[i];
      }
   return true;   
   }

銘柄チャート上では、ロジックは視覚的に紫色のフレームのバリアントと同等になります。選択のためにさらにいくつかのバリアントを描画します。

トップとボトムの選択

この場合、選択ロジックは非常に単純です。選択されたバリアントは、市場に最も近いため、0と1です。ここではすべてがダブルトップに適用されます。ただし、トリプル以上のマルチトップでは同じロジックが使用されます。唯一の違いは、選択されたトップの数です。

この関数は将来拡張され、上の画像の青色で示されているように、トップをランダムに選択できるようになります。パターンファインダーの複数のインスタンスがシミュレートされ、自動モードではすべてのパターンをより効率的かつ頻繁に見つけることができます。

パターンの方向の特定

トップとボトムを特定したら、そのようなフォーメーションが市場の特定のポイントに存在する場合はその方向を特定する必要があります。この段階で、極値タイプが市場に最も近い方向を優先することを検討します。この論理に基づいて、図のバリアント0を使用します。市場に最も近いのはトップではなくボトムであるためです(市場の状況が図とまったく同じである場合)。この部分のコードは単純です。

void ExtremumsPatternFamilySearcher::DirectionOfFormation()// determine whether it is a double top (1) or double bottom (-1) (only if all tops and bottoms are found - if not found, then 0)
   {
   if ( TopsDown[0].DatetimeExtremum > TopsUp[0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-1].bActive )
      {
      StartTop=TopsDown[ArraySize(TopsDown)-1];
      EndTop=TopsDown[0];    
      FormationDirection=-1;
      }
   else if ( TopsDown[0].DatetimeExtremum < TopsUp[0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-1].bActive )
      {
      StartTop=TopsUp[ArraySize(TopsUp)-1];
      EndTop=TopsUp[0]; 
      FormationDirection=1;  
      }
   else FormationDirection=0;   
   }

それ以上の措置をとるには明確に特定された方向性が必要です。方向はパターンタイプと同じです。

  1. マルチトップ
  2. マルチボトム

これらのルールは、頭と肩のパターンおよび他のすべてのハイブリッドフォーメーションにも適用されます。このクラスは、このファミリーのすべてのパターンに共通するはずでした。この一般性はすでに部分的に機能しています。

誤ったパターンを破棄するためのフィルター:

それでは、さらに進みます。方向があり、トップとボトムを選択する方法の1つがあることがわかったので、マルチトップに対して情報を提供する必要があります。それは、選択したトップの間にあるトップは、選択したトップの最低値よりも低くする必要があるということです。マルチボトムの場合、そのようなボトムは、選択したボトムの最高値よりも高くする必要があります。この場合、トップがランダムに選択されると、選択されたすべてのトップが明確に区別されます。それ以外の場合、この確認は必要ありません。

bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()// require the selected extrema to be the most extreme ones
   {
   if ( bRandomExtremumsM )// check only if we have a random selection of tops (in other case the check should be considered completed)
      {
      if ( FormationDirection == 1 )
         {
         int StartIndex=RandomIndexUp[0];
         int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones
            {
            for(int j=0;j<ArraySize(TopsUp);j++)
               {
               if ( TopsUpAll[i].Price >= TopsUp[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexUp);k++)
                     {
                     if ( i != RandomIndexUp[k] ) return false;
                     }
                  }
               }
            }
         return true;
         }
      else if ( FormationDirection == -1 )
         {
         int StartIndex=RandomIndexDown[0];
         int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones
            {
            for(int j=0;j<ArraySize(TopsDown);j++)
               {
               if ( TopsDownAll[i].Price <= TopsDown[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexDown);k++)
                     {
                     if ( i != RandomIndexDown[k] ) return false;
                     }
                  }
               }
            }
         return true;      
         }
      else return false;      
      }
   else
      {
      return true;
      }
   }

最後の述語関数によって実行されるランダムトップ選択の正しいバリアントと誤ったバリアントを視覚的に表示すると、次のようになります。

未計上のトップの管理


これらの基準は強気と弱気のパターンに反映されています。この図は、例として強気のパターンを示しています。2番目の場合は簡単に想像できます。

すべての準備手順が完了したら、「首」の検索に進みます。さまざまなトレーダーがさまざまな方法で首をプロットします。私は条件付きでいくつかのタイプの構造を決定しました。

  1. 視覚的に傾いている(髭ではない)
  2. 視覚的に水平(髭ではない)
  3. 最高点または最低点、傾斜(髭による)
  4. 最高点または最低点、水平(髭による)

安全上の理由と利益の可能性を高めるために最適なバリアントは4であると私は信じています。選択理由は次です。

  • 反転運動の始まりがより明確にわかる
  • コードで実装するのがより簡単
  • 勾配が明確に特定される(水平)

おそらく、これは建設の観点からは完全に正しいわけではありませんが、明確なルールは見つかりませんでした。これは、アルゴリズム取引の観点からは重要ではありません。このパターンで合理的なものが見つかった場合、テスターまたは視覚化によって確実に何かがわかります。さらなるタスクは、取引結果の強化を意味しますが、これはまったく別の物です。

「首」に必要なすべてのパラメータを定義する強気と弱気のパターン用に2つのミラー関数を作成しました。

void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)// find the neck line based on the two extreme tops (for the classic multiple top)
   {
   double PriceMin=Low[TStart.IndexExtremum];
   datetime TimeMin=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point
      {
      if ( Low[i] < PriceMin )
         {
         PriceMin=Low[i];
         TimeMin=Time[i];
         }
      }
   // define the parameters of the anchor point and all parameters of the line equation
   Neck.Price0=PriceMin;
   Neck.TimeX=TimeMin;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMin;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=true;
   Neck.CalculateKC();
   }
   
void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)// find the neck line based on two extreme bottoms (for the classic multiple bottom)
   {
   double PriceMax=High[TStart.IndexExtremum];
   datetime TimeMax=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point
      {
      if ( High[i] > PriceMax )
         {
         PriceMax=High[i];
         TimeMax=Time[i];         
         }
      }
   // define the parameters of the anchor point and all parameters of the line equation
   Neck.Price0=PriceMax;
   Neck.TimeX=TimeMax;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMax;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=false;
   Neck.CalculateKC();
   }

「首」を正しく簡単にプロットするには、選択したファミリーのすべてのパターンで「首」の構造に同じルールを使用することをお勧めします。一方では、これにより不要な詳細が排除され、ここでの場合、何も得られません。複雑なマルチトップの「首」を構築するには、パターンの2つの極端なトップを使用することをお勧めします。これらの山のインデックスは、市場の選択されたセグメントで最低価格または最高価格を検索するためのインデックスになります。「首」は通常の横線になります。最初のアンカーポイントは正確にこのレベルにある必要がありますが、アンカー時間は極端なトップとボトムの時間と正確に等しくする必要があります(検討しているパターンによって異なります)。図で示します。

「首」

低/高値を検索するウィンドウは、最初と最後のトップの真ん中にあります。このルールは、このファミリーの任意のパターン、任意の数のトップとボトムに有効です。

楽観的な目標を決定するには、最初にパターンサイズを定義する必要があります。パターンサイズは、頭から首までの垂直距離をポイントで表したものです。距離を決定するには、最初に首から最も遠いトップを見つける必要があります。このトップがパターンの境界になります。

void ExtremumsPatternFamilySearcher::SearchFarestTop()// define the farthest top
   {
   double MaxTranslation;// temporary variable to determine the highest top
   if ( FormationDirection == 1 )// if we deal with a multiple top
      {
      MaxTranslation=TopsUp[0].Price-Neck.Price0;// temporary variable to determine the highest top
      FarestTop=TopsUp[0];
      for(int i=1;i<ArraySize(TopsUp);i++)
         {
         if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) 
            {
            MaxTranslation=TopsUp[i].Price-Neck.Price0;
            FarestTop=TopsUp[i];
            }
         }      
      }
   if ( FormationDirection == -1 )// if we deal with a multiple bottom
      {
      MaxTranslation=Neck.Price0-TopsDown[0].Price;// temporary variable to determine the lowest bottom
      FarestTop=TopsDown[0];      
      for(int i=1;i<ArraySize(TopsDown);i++)
         {
         if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) 
            {
            MaxTranslation=Neck.Price0-TopsDown[0].Price;
            FarestTop=TopsDown[i];
            }
         }      
      }
   }

トップの違いが大きすぎないように、追加の確認が必要です。次の手順に進むことができるのは、これが確認出来た場合のみです。より正確に言えば、極値の垂直サイズと水平(時間)の2つの確認が必要です。トップが時間的に離れすぎている場合、そのようなバリアントも適していません。垂直方向のサイズの確認は次のとおりです。

bool ExtremumsPatternFamilySearcher::bBalancedExtremums()// balance the tops
   {
   double Lowest;// the lowest top for the multiple top
   double Highest;// the highest bottom for the multiple bottom
   double AbsMin;// distance from the neck to the nearest top
   if ( FormationDirection == 1 )// for the multiple top
      {
      Lowest=TopsUp[0].Price;
      for(int i=1;i<ArraySize(TopsUp);i++)// find the lowest top
         {
         if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price;
         }
      AbsMin=Lowest-Neck.Price0;// determine distance from the lowest top to the neck
      if ( AbsMin == 0.0 ) return false;
      if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage
      }
   else if ( FormationDirection == -1 )// for the multiple bottom
      {
      Highest=TopsDown[0].Price;
      for(int i=1;i<ArraySize(TopsDown);i++)// find the highest top
         {
         if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price;
         }
      AbsMin=Neck.Price0-Highest;// determine distance from the highest top to the neck
      if ( AbsMin == 0.0 ) return false;
      if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage
      }
   else return false;
   return true;   
   }

トップの正しい垂直サイズを判定するには、2つのトップが必要です。1番目は首から最も遠いトップで、2番目は首に最も近いトップです。これらのサイズが大きく異なる場合、このフォーメーションは無効であることが判明する可能性があるため、リスクを冒さずに無効として指定することをお勧めします。前の述語と同様に、これにはすべて、何が正しく何が間違っているかを示す適切なグラフィックが付随している可能性があります。

垂直サイズの制御

視覚的には簡単に判断できますが、コードには定量的な指標が必要です。この場合、これは次のように簡単です。

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityM

この指標は、非常に多くの誤ったパターンを除外するのに非常に効率的です。コードはどんなに洗練されていても、人間の目ほど効率的ではありません。私たちにできる唯一のことは、ロジックをできるだけ現実に近づけることです。しかし、ここでは、どこでやめるかを知る必要があります。

水平方向の確認は同じように見えます。唯一の違いは、サイズとしてバーインデックスを使用することです(時間も使用できます。基本的な違いはありません)。

bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()// balance the sizes of shoulders and head along the horizontal axis
   {
   double Lowest;// minimum distance between the tops
   double Highest;// maximum distance between the tops
   if ( FormationDirection == 1 )// for the multiple top
      {
      Lowest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      Highest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsUp)-1;i++)// find the lowest top
         {
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much
      }
   else if ( FormationDirection == -1 )// for the multiple bottom
      {   
      Lowest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      Highest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsDown)-1;i++)// find the lowest top
         {
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much 
      }
   else return false;
   return true;
   }

この確認では、同様の指標を使用できます。視覚的には、次のように表現できます。

水平サイズの制御

この場合、定量的基準は同じになります。ただし、今回はポイントの代わりにインデックスまたは時間を使用します。柔軟な調整の余地が生まれるので、比較している数値を個別に実装する方がよい場合があります。

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityTimeM

ネックラインは左側の価格と交差する必要があります。これは、パターンの前にトレンドがあったことを意味します。

bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( Neck.DirectionOfFormation )// if the neck is found for a double top
      {
      for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( High[i] >= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake
            {
            return false;
            }         
         if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0   )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }
   
bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom
      {
      for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( Low[i] <= FarestTop.Price )//  if the movement goes beyond the formation, then the formation is fake
            {
            return false;
            }         
         if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }

繰り返しになりますが、強気と弱気のパターンには2つのミラー関数があります。以下は、この述語と次の述語の図解です。

左右の交差点制御

青いボックスは、交差点を制御する市場セグメントを示しています。両方のセグメントは、パターンの後ろ、極端なトップの左側と右側にあります。 

確認が残るのは2点だけです。

  1. 現時点で(ゼロローソク足で)ネックラインと交差するパターンがある
  2. パターンの前にパターン自体以上の動きがある

1点目はアルゴリズム取引に必要です。この関数もありますが、フォーメーションを表示するためだけに検出する価値はないと思います。両方の検出、そして取引できるポイント(つまりエントリポイントにいることを知ってすぐにポジションを開くことができるポイントで)を正確に知ることが必要です。2点目は必要条件の1つです。事前に適切な動きがなかった場合、パターン自体は役に立たないためです。

ゼロローソク足クロス(右側の交差点をチェック)は次のように特定されます。

int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()// next the neck line must be corrected so that it finds an intersection with the price on the right
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( Neck.DirectionOfFormation )// if the neck is found for a double top
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point
         {
         if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] <= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }      
   return 0;
   }

int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()// next the neck line must be corrected so that it finds an intersection with the price on the right
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point
         {
         if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0  )// if the movement goes beyond the formation, then the formation is fake
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] >= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }   
      
   return 0;
   }
<

ここでも、2つのミラー関数があります。価格がパターンを超えて戻った場合、右側の交差点は有効とは見なされないことに注意してください。この動作はここで説明されており、前の図に示されています。

次に、前のトレンドを見つける方法を特定しましょう。これまでのところ、私はこの目的のために楽観的な予測線を使用しています。首と楽観的な予測線の間に市場セグメントがある場合、これは望ましい動きです。この動きはあまり長期ではいけません、長期過ぎる場合、それは明らかに動きではありません。

bool ExtremumsPatternFamilySearcher::bWasTrend()// did we find the movement preceding the formation (also move here the anchor point to the intersection)
   {
   bool bCrossOptimist=false;// denotes if the neck is crossed
   if ( FormationDirection == 1 )// if the optimistic forecast is at the double top
      {
      for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( High[i] > Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake
            {
            return false;
            }         
         if ( Low[i] < OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }
      }
   else if ( FormationDirection == -1 )// if the optimistic forecast is at the double bottom
      {
      for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( Low[i] < Neck.Price0 )//  if the movement goes beyond the neck, then the formation is fake
            {
            return false;
            }         
         if ( High[i] > OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }      
      }
   return false;
   }

最後の述語は、次のようにグラフィカルに表すこともできます。

前の動き

ここでコードの確認を終了し、視覚的な評価に進みます。この方法の主なアイデアは、この記事で十分に説明されていると思います。次の連載記事では、さらなるアイデアについて検討します。

MetaTrader 5ビジュアルテスターで結果を確認します。

線による描画は高速でシンプルかつ明確なので、私は常に線画を使用します。MQL5ヘルプには、線を含むグラフィカルオブジェクトの使用例が記載されています。描画コードは提示せずに実行結果を確認します。もちろん、これは単にプロトタイプなので、改良は可能です。ここでは「必要性と十分性」の原則を使用できると思います。

MetaTrader 5ストラテジーテスタービジュアライザーのトリプルトップ

次にトリプルトップの例を示します。この例は私にはもっと興味深いものでした。ダブルトップも同様の方法で検出されるので、パラメータで必要なトップ数を設定するだけで済みます。コードがそのようなフォーメーション見つけることはあまりありませんが、これは単にデモンストレーションなので、さらに改善できます(これは後で行う予定です)。


さらなる開発アイデア

後ほど、この記事で述べられていないことを検討し、すべてのフォーメーションの検索品質を向上させます。また、クラスを磨いて、頭と肩のフォーメーションを検出できるようにします。また、これらのフォーメーションの可能なハイブリッド機能を見つけようとします。 それらの1つは「Nトップと複数の肩」かもしれません。この連載は、このパターンファミリーだけに焦点を当てているわけではなく、新しく興味深い有用な資料が含まれています。パターン検索にはさまざまなアプローチがあります。このシリーズのアイデアは、さまざまな例を使用してできるだけ多くのパターンを表示し、複雑なタスクをより単純なタスクのセットに分解するさまざまな方法をカバーすることです。連載には以下が含まれます。

  1. その他の興味深いパターン
  2. さまざまなフォーメーションタイプを検出するための他の方法
  3. 過去のデータを使用した取引と、さまざまな商品や時間枠での統計の収集
  4. パターンはたくさんありますが、すべてはわかりません(パターンを検討できる可能性があります)
  5. レベルも考慮します(レベルは反転を検出するためによく使用されるため)


    終わりに

    内容は誰もが理解できるようにシンプルにしてみました。ここでどなたかが、何か役に立つものを見つけられることを願っています。ビジュアルストラテジーテスターからわかるように、この特定の記事の結論は、単純なコードで複雑なフォーメーションを見つけることができるということです。ニューラルネットワークを使用したり、複雑なマシンビジョンアルゴリズムを作成/使用したりする必要は必ずしもありません。MQL5言語には、最も複雑なアルゴリズムでも実装できる豊富な機能があります。可能性を制限するのはあなたの想像力と勤勉さだけです。 

    MetaQuotes Ltdによってロシア語から翻訳されました。
    元の記事: https://www.mql5.com/ru/articles/9394

    添付されたファイル |
    Prototype.zip (309.42 KB)
    DoEasyライブラリのグラフィックス(第73部): グラフィック要素のフォームオブジェクト DoEasyライブラリのグラフィックス(第73部): グラフィック要素のフォームオブジェクト
    本稿からは、ライブラリでのグラフィックの使用に関する新しい大きなセクションを始めます。本稿では、マウスステータスオブジェクト、すべてのグラフィック要素の基本オブジェクト、およびライブラリのグラフィック要素のフォームオブジェクトのクラスを作成します。
    DoEasyライブラリでのその他のクラス(第72部): コレクション内のチャートオブジェクトパラメータの追跡と記録 DoEasyライブラリでのその他のクラス(第72部): コレクション内のチャートオブジェクトパラメータの追跡と記録
    本稿では、チャートオブジェクトクラスとそのコレクションの操作を完成します。また、チャートプロパティとそのウィンドウの変更の自動追跡を実装し、オブジェクトプロパティに新しいパラメータを保存します。このような変更により、を将来チャートコレクション全体のイベント機能実装できるようになります。
    取引のための組合せ論と確率論(第I部):基本 取引のための組合せ論と確率論(第I部):基本
    この連載では、確率論の実用的応用を見つけて、取引と価格設定のプロセスの説明を試みます。最初の記事では、組合せ論と確率の基礎を調べ、確率論の枠組みでフラクタルを適用する方法の最初の例を分析します。
    プロのプログラマーからのヒント(第2部): パラメータの保存とエキスパートアドバイザー、スクリプト、外部プログラム間での交換 プロのプログラマーからのヒント(第2部): パラメータの保存とエキスパートアドバイザー、スクリプト、外部プログラム間での交換
    プログラミングを容易にする方法、テクニック、および補助ツールに関するプロのプログラマーからのヒントです。ターミナルの再起動(シャットダウン)後に復元できるパラメータについて説明します。すべての例は、私のCaymanプロジェクトからの実際に機能するコードセグメントです。