チャート上でトレーディングの考え方を時間をかけずに検証する方法

Vladimir Kustikov | 19 11月, 2015

はじめに

第6回目の自動売買チャンピオンシップがついに開始されました。最初の盛り上がりは去り、少しリラックスし提出された売買ロボットを検証することができるようになりました。そこで最新売買ロボットのもっとも顕著な特徴を見つけ、そのトレード機能に何が期待できるのか見極めるべく少しばかり調査をすることにしました。

しかしそれは難しいことが判りました。そのため私の計算は完璧に正確だとか完成されているということはできません。私の手元にあるのは Expert Advisor の記述と開発者の稀なコメントだけだからです。ただそれでもある結論を導くことはできます。下記が私の結論です。:チャンピオンシップには 451 件の Expert Advisors が出場していますが、内、意義ある記述を持つのはたった 316 件です。それ以外はプログラム記述が友人や家族へのあいさつ、宇宙人へのメッセージ、自画自賛で埋められています。

ATC 2012 におけるもっともよく知られる戦略

もちろん指数戦略がこれまでもっとも知られるものの一つです。Expert Advisor の特定のインディケータの役割を明確にするのは困難ですが、その絶対的使用数を推定することは可能です。

データから得られるのは以下の結論です。ほとんどの出場者はインディケータを伴う戦略に従ったトレードを採用します。おそらく私はデータ収集段階で何か見落としをしたのでしょう。自動売買の分野における際立った特徴が現れるのを見ていくことになるのでしょうが、今のところそれは起こりそうにもありません。主な問題はマーケットに魅了された初心者はたいていの場合知識ではなくルールを受け入れることです。

たとえば、MACDを使うルールがある、シグナルがある、これでパラメータを最適化してお金を稼ぐのだ、という具合です。ちょっとは脳みそを使ったらどうですか?こんなことまったく無意味です!基準となるものはすでに開発済みです!なぜそれをまた一から始めるのですか?現在人気のインディケータは私やあなたと何も違わないトレーダーによって考え出されたのです。かれらも自身の基準と根拠を持っていたのです。もしかしたらあなたの名前がついた新しいインディケータが 10 年後標準的なインディケータになるかもしれません。

そんなわけで、これからトレーディングの考え方を検索する方法やその考え方を時間をかけずに検証する方法をみなさんと共有したいと思います。


メソッドの記述

テクニカル分析はすべて一つのシンプルな原理を基にしています。すなわち価格がすべてを考慮する、です。 しかし一つ問題があります。この意見はダイナミクスを欠いています。チャートを見て、静的イメージを確認します。価格は実際にはすべてを考慮してきているのです。ただし、収益を得るために、将来のある期間に価格は何を考えるのか、それはどこに向かうのか知りたいと思います。価格から派生するインディケータは将来に起こりうる変動を正確に予想するために作成されています。

物理学で知っているように大きさの一次微分係数は速度です。よってインディケータは現在価格変動のスピードを計算します。また有意な大きさは慣性を持ち、大きな外力の干渉なしに値の急激な変動で起こるスピードを避けることもわかっています。それが次第にトレンドのコンセプトに近づく方法-一次微分係数(スピード)が外力(ニュース、中央銀行の政策等)がマーケットに影響を与えない期間内、その値を保つ価格状態、です。

ですがここでスタート地点に戻りましょう。価格がすべてを考慮する、というところです。新しい考えを発展させるには、価格の動向および同じ時間間隔における微分係数を検証する必要があります。価格チャートを注意深く検証するだけが盲信からみなわんのトレーディングを純粋な理解のレベルに引き上げるのです。

これがトレーディングの結果をすぐに変えることにはなりませんが、数多くのなぜ?という質問に答える力は遅かれ早かれ役に立つものです。その上、チャートとインディケータの視覚的分析によりインディケータの開発者には見通せなかったまったく新しい相関関係を価格とインディケータの間に見つけることができるかもしれません。

思い通り役立ちそうな新しい相関関係を見つけたとします。では次にくるのは何でしょうか?もっとも簡単な方法はExpert Advisor を書き、履歴データに基づきそれを検証し、予想が正しいと確認することです。そうでなければ、パラメータを最適化する共通の方法を選択する必要があります。これに関する最悪のことがらは、なぜ?という質問に答えられないことです。なぜ Expert Advisor は損失を生む/収益性があるようになったのでしょうか?なぜそのような大きなドローダウンが生じたのでしょうか?答えなしには自分の考え方を効果的に取り入れることはできません。

取得した相関関係の結果をチャート上で適切に視覚化するために以下を行います。

  1. 必要なインディケータを作成または変更し、それがシグナルを生成するようにします。: -1 は売り、そして1 は買いです。
  2. チャートに参入点と終了点を表示する残高インディケーを連動します。インディケータはまたシグナル処理中の残高と資本(ポイント表示)の変動を表示します。
  3. そしてどのような場合また状況で予想が正しいか分析します。

方法には一定のメリットががあります。


実装

シンプルなシグナルインディケータを作成し、それがどのように動作するか理解し、この方法の利便性を評価します。ろうそく足パターンのことは以前から聞いています。実践でその動作を確認してはどうでしょうか?買いシグナル、売りシグナルにそれぞれ『ハンマー』と『流れ星』の逆パターンを選択しました。下図はそれらの視覚化されたスキームを示しています。

図1 ろうそく足パターン:"Hammer"と"Shooting Star"』

図1 ろうそく足パターン:"Hammer"と"Shooting Star"』

ここで"Hammer"パターンが現れるときのマーケット参入ルールを定義します。

  1. ろうそくの安値は前5本のろうそくの安値よりも低くなる必要があります。
  2. ろうそくの本体は高さ全体の 50% を越えません。
  3. ろうそくの上側のヒゲは高さ全体の 0% を越えません。
  4. ろうそくの高さはそれより以前の 5本 のろうそくの平均高さの 100% よりも低くなります。
  5. このパターンの終値は期間 10 の「移動平均」よりも低くなります。

これら条件が満たされれば、ロングポジションをオープンします。"Shooting Star"パターンに対してもルールは同じです。唯一の違いは"Shooting Star"パターンではショートポジションをオープンすることです。

  1. ろうそくの高値は前5本のろうそくの高値よりも高くなる必要があります。
  2. ろうそくの本体は高さ全体の 50% を越えません。
  3. ろうそくの下側のヒゲは高さ全体の 0% を越えません。
  4. ろうそくの高さはそれより以前の 5本 のろうそくの平均高さの 100% よりも低くなります。
  5. このパターンの終値は期間 10 の「移動平均」よりも高くなります。

将来最適化する可能性がある(パターンが受け入れ可能な結果を示せば)描画に基づき使用するパラメータのスタイルには太文字を使いました。希望の制限を取り入れ、不適切な外見を持つものからパターンを除外することができます(pp. 1-3)。シグナルとして受け入れられない弱いとわかっているものからのパターンも同様です。

その上、終了時も決定する必要があります。前述のパターンはトレンド逆シグナルとして現れるため、トレンドは適切なろうそくが出現するときに存在します。そのため価格を追う移動平均もまた存在することとなります。終了シグナルは価格と 10期間移動平均をクロスすることで形成されます。

では、プログラムにまいります。MQL5 「ウィザード」で新しいカスタムインディケータを作成し、それを PivotCandles と名づけ、そのふるまいを記述します。残高インディケータと連携するように戻り値を定義します。

ご存じのように本物のプログラマーは簡単な方法を探そうとしないものです。彼らはもっとも簡単な方法を探すのです。:) 私も例外ではありません。ヘッドフォンで音楽を聴き、アロマコーヒーを飲みながら、インディケータおよび Expert Advisor (インディケータを基にそれを作成しようと決めた場合)に実装するクラスを持つファイルを作成しました。たぶんそれは別のそうろく足パターンのために変更することも可能です。コードはなにも新しいことを含んでいません。コードに実装されるコメントは出る可能性のある課題に対応しているはずです。

//+------------------------------------------------------------------+
//|                                            PivotCandlesClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      iMaxBodySize            = 50;  // Maximum candle body, %
input int      iMaxShadowSize          = 0;   // Maximum allowed candle shadow, %
input int      iVolatilityCandlesCount = 5;   // Number of previous bars for calculation of an average volatility
input int      iPrevCandlesCount       = 5;   // Number of previous bars, for which the current bar should be an extremum
input int      iVolatilityPercent      = 100; // Correlation of a signal candle with a previous volatility, %
input int      iMAPeriod               = 10;  // Period of a simple signal moving average
//+------------------------------------------------------------------+
//| Class definition                                                 |
//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
private:
   MqlRates          m_candles[];              // Array for storing the history necessary for calculations
   int               m_history_depth;          // Array length for storing the history
   int               m_handled_candles_count;  // Number of the already processed candles
   
   double            m_ma_value;               // Current calculated moving average value
   double            m_prev_ma_value;          // Previous calculated moving average value
   bool              m_is_highest;             // Check if the current candle is the highest one
   bool              m_is_lowest;              // Check if the current candle is the lowest one
   double            m_volatility;             // Average volatility
   int               m_candle_pattern;         // Current recognized pattern
   
   void              PrepareArrayForNewCandle();        // Prepare the array for accepting the new candle
   int               CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns
   void              PrepareCalculation();
protected:
   int               DoAnalizeNewCandle();              // Calculation function
public:
   void              CPivotCandlesClass(); 
   
   void              CleanupHistory();                  // Clean up all calculation variables  
   double            MAValue() {return m_ma_value;}     // Current value of the moving average
   int               AnalizeNewCandle(MqlRates& candle);
   int               AnalizeNewCandle( const datetime time,
                                       const double open,
                                       const double high,
                                       const double low,
                                       const double close,
                                       const long tick_volume,
                                       const long volume,
                                       const int spread );
  };
//+------------------------------------------------------------------+
//| CPivotCandlesClass                                               |
//+------------------------------------------------------------------+
//| Class initialization                                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::CPivotCandlesClass()
  {
   // History depth should be enough for all calculations
   m_history_depth = (int)MathMax(MathMax(
      iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod);
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;
   
   ArrayResize(m_candles, m_history_depth);
  }  
//+------------------------------------------------------------------+
//| CleanupHistory                                                   |
//+------------------------------------------------------------------+
//| Clean up the candle buffer for recalculation                     |
//+------------------------------------------------------------------+
void CPivotCandlesClass::CleanupHistory()
  {
   // Clean up the array
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);
   
   // Null calculation variables
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;   
  }
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on candle's separate parameter values                       |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle( const datetime time,
                                          const double open,
                                          const double high,
                                          const double low,
                                          const double close,
                                          const long tick_volume,
                                          const long volume,
                                          const int spread )
  {
   // Prepare the array for the new candle
   PrepareArrayForNewCandle();

   // Fill out the current value of the candle
   m_candles[0].time          = time;
   m_candles[0].open          = open;
   m_candles[0].high          = high;
   m_candles[0].low           = low;
   m_candles[0].close         = close;
   m_candles[0].tick_volume   = tick_volume;
   m_candles[0].real_volume   = volume;
   m_candles[0].spread        = spread;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }  
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on the received candle                                      |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle)
  {
   // Prepare the array for the new candle   
   PrepareArrayForNewCandle();

   // Add the candle 
   m_candles[0] = candle;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }
//+------------------------------------------------------------------+
//| PrepareArrayForNewCandle                                         |
//+------------------------------------------------------------------+ 
//| Prepare the array for the new candle                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareArrayForNewCandle()
  {
   // Shift the array by one position to write the new value there
   ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1);
   
   // Increase the counter of added candles
   m_handled_candles_count++;
  }
//+------------------------------------------------------------------+
//| CalcMAValue                                                      |
//+------------------------------------------------------------------+ 
//| Calculate the current values of the Moving Average, volatility   |
//|   and the value extremality                                      |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareCalculation()
  {
   // Store the previous value
   m_prev_ma_value = m_ma_value;
   m_ma_value = 0;
   
   m_is_highest = true;  // check if the current candle is the highest one
   m_is_lowest = true;     // check if the current candle is the lowest one
   m_volatility = 0;    // average volatility
   
   double price_sum = 0; // Variable for storing the sum
   for (int i=0; i<m_history_depth; i++)
     {
      if (i<iMAPeriod)
         price_sum += m_candles[i].close;
      if (i>0 && i<=iVolatilityCandlesCount)
         m_volatility += m_candles[i].high - m_candles[i].low;
      if (i>0 && i<=iPrevCandlesCount)
        {
         m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high);
         m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low);
        }
     }
   m_ma_value = price_sum / iMAPeriod;
   m_volatility /= iVolatilityCandlesCount;
   
   m_candle_pattern = CheckCandleSize(m_candles[0]);
  }
//+------------------------------------------------------------------+
//| CheckCandleSize                                                  |
//+------------------------------------------------------------------+
//| Check if the candle sizes comply with the patterns               |
//| The function returns:                                            |
//|   0 - if the candle does not comply with the patterns            |
//|   1 - if "hammer" pattern is detected                            |
//|   -1 - if "shooting star" pattern is detected                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::CheckCandleSize(MqlRates &candle)
  {
   double candle_height=candle.high-candle.low;          // candle's full height
   double candle_body=MathAbs(candle.close-candle.open); // candle's body height

   // Check if the candle has a small body
   if(candle_body/candle_height*100.0>iMaxBodySize)
      return 0;

   double candle_top_shadow=candle.high-MathMax(candle.open,candle.close);   // candle upper shadow height
   double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height

   // If the upper shadow is very small, that indicates the "hammer" pattern
   if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize)
      return 1;
   // If the bottom shadow is very small, that indicates the "shooting star" pattern
   else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize)
      return -1;
   else
      return 0;
  }
//+------------------------------------------------------------------+
//| DoAnalizeNewCandle                                               |
//+------------------------------------------------------------------+
//| Real analysis of compliance with the patterns                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::DoAnalizeNewCandle()
  {
   // Prepare data for analyzing the current situation
   PrepareCalculation();
   
   // Process prepared data and set the exit signal
   int signal = 0;
   
   ///////////////////////////////////////////////////////////////////
   // EXIT SIGNALS                                                  //
   ///////////////////////////////////////////////////////////////////
   // If price crosses the moving average downwards, short position is closed
   if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value)
      signal = 2;
   // If price crosses the moving average upwards, long position is closed 
   else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value)
      signal = -2;
      
   ///////////////////////////////////////////////////////////////////
   // ENTRY SIGNALS                                                 //
   ///////////////////////////////////////////////////////////////////
   // Check if the minimum volatility condition is met
   if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility)
     {
      // Checks for "shooting star" pattern
      if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value)
         signal = -1;
      // Checks for "hammer" pattern
      else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value)
         signal = 1;
     }
     
   return signal;
  }
//+------------------------------------------------------------------+

計算部分はすべて CPivotCandlesClass クラスによって行われます。よいプログラムとみなされるのは、計算部分がビジュアルから分離されているもので、私はできるだけこの推奨に従おうとしています。そのメリットはすぐに得られます。以下はインディケータ自体のコードです。

//+------------------------------------------------------------------+
//|                                                 PivotCandles.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

// Use four buffers, while drawing two
#property indicator_buffers 4
#property indicator_plots   2
//--- plot SlowMA
#property indicator_label1  "SlowMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAliceBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot ChartSignal
#property indicator_label2  "ChartSignal"
#property indicator_type2   DRAW_COLOR_ARROW
#property indicator_color2  clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  3

#include <PivotCandlesClass.mqh>
//+------------------------------------------------------------------+
//| Common arrays and structures                                     |
//+------------------------------------------------------------------+
//--- Indicator buffers                                                
double   SMA[];            // Values of the Moving Average
double   Signal[];         // Signal values
double   ChartSignal[];    // Location of signals on the chart
double   SignalColor[];    // Signal color array
//--- Calculation class
CPivotCandlesClass PivotCandlesClass;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,SMA,INDICATOR_DATA);
   SetIndexBuffer(1,ChartSignal,INDICATOR_DATA);
   SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS);

//--- set 0 as an empty value
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   // If there have not been calculations yet or (!) the new history is uploaded, clean up the calculation object
   if (prev_calculated == 0)
      PivotCandlesClass.CleanupHistory();
   
   int end_calc_edge = rates_total-1;   
   if (prev_calculated >= end_calc_edge)
      return end_calc_edge;
   
   for(int i=prev_calculated; i<end_calc_edge; i++)
     {
      int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]);
      Signal[i] = signal;
      SMA[i] = PivotCandlesClass.MAValue();
      
      // Signals are processed, display them on the chart
      // Set the location of our signals...
      if (signal < 0)
         ChartSignal[i]=high[i];
      else if (signal > 0)
         ChartSignal[i]=low[i];
      else
         ChartSignal[i]=0;
      // .. as well as their color
      // Signals have a range of [-2..2], while color indices - [0..4]. Align them 
      SignalColor[i]=signal+2;
     }
   
   // Set the Moving Average value similar to the previous one to prevent it from sharp fall
   SMA[end_calc_edge] = SMA[end_calc_edge-1];

//--- return value of prev_calculated for next call
   return(end_calc_edge);
  }
//+------------------------------------------------------------------+

インディケータはできました。これからチャート上でそれを検証します。このために、コンパイルされたインディケータをチャート上にインストールします。その後、以下の画像に示されるようなものが表示されます。

図2 ろうそく足パターン『ハンマー』と『流れ星』のインディケータ

図2 ろうそく足パターン『ハンマー』と『流れ星』のインディケータ

色付でマークされている点は可能なマーケットへの参入と終了です。色は次のように選ばれています。

終了シグナルは価格が移動平均に達したとき毎回作成されます。そのときポジションがなければこのシグナルは無視されます。

それでは本稿のメインのトピックに移ります。いくつか特定シグナルのみを作成するシグナルバッファを持つインディケータを取得します。実際に追いかければこれらシグナルがどれほど収益性があるか/損失を生じるか同じチャートの個別ウィンドウに表示します。インディケータはそのような場合のために特別に作成されました。それはもう一つ別のインディケータに連携し、受け取られるシグナルに応じて実質上のポジションをオープン/クローズすることができます。

そしてここでも前のインディケータのように、コードをに分割する必要があります。すなわち計算するコードおよび表示するコードです。以下は徹夜の成果ですが、徹夜した価値があることを願っています。:)

//+------------------------------------------------------------------+
//|                                                 BalanceClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Common structures                                                |
//+------------------------------------------------------------------+
// Structure for returning calculation results 
// using only return command;
struct BalanceResults
  {
   double balance;
   double equity;
  };
//+------------------------------------------------------------------+
//| Common function                                                  |
//+------------------------------------------------------------------+
//  Function for searching for the indicator handle by its name
int FindIndicatorHandle(string _name)
  {
   // Receive the number of open charts
   int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
   
   // Search all of them
   for(int w=windowsCount-1; w>=0; w--)
     {
      // How many indicators are attached to the current chart
      int indicatorsCount = ChartIndicatorsTotal(0,w);

      // Search by all chart indicators
      for(int i=0;i<indicatorsCount;i++)
        {
         string name = ChartIndicatorName(0,w,i);
         // If such an indicator is found, return its handle
         if (name == _name)
            return ChartIndicatorGet(0,w,name);
        }
     }  
     
   // If there is no such an indicator, return the incorrect handle 
   return -1;
  }
//+------------------------------------------------------------------+
//| Base calculation class                                           |
//+------------------------------------------------------------------+
class CBaseBalanceCalculator
  {
private:
   double            m_position_volume; // Current open position volume
   double            m_position_price;  // Position opening price
   double            m_symbol_points;   // Value of one point for the current symbol
   BalanceResults    m_results;         // Calculation results
public:
   void              CBaseBalanceCalculator(string symbol_name = "");
   void              Cleanup();
   BalanceResults    Calculate( const double _prev_balance, 
                                const int    _signal,
                                const double _next_open,
                                const double _next_spread );
  };
//+------------------------------------------------------------------+
//| CBaseBalanceCalculator                                           |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "")
  {
   // Clean up state variables
   Cleanup();
   
   // Define point size (because we will calculate the profit in points)
   if (symbol_name == "")
      m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   else 
      m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT);
  }
//+------------------------------------------------------------------+
//| Cleanup                                                          |
//+------------------------------------------------------------------+
//| Clean up data on positions and prices                            |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::Cleanup()
  {
   m_position_volume = 0;
   m_position_price = 0;  
  }
//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
//| Main calculation block                                           |
//+------------------------------------------------------------------+
BalanceResults CBaseBalanceCalculator::Calculate(
                                       const double _prev_balance,
                                       const int _signal,
                                       const double _next_open,
                                       const double _next_spread )
  {
   // Clean up the output structure from the previous values
   ZeroMemory(m_results);
   
   // Initialize additional variables
   double current_price = 0; // current price (bid or ask depending on position direction)
   double profit = 0;        // profit calculated value
   
   // If there was no signal, the balance remains the same 
   if (_signal == 0)
      m_results.balance = _prev_balance;
   // the signal coincides with the direction or no positions are opened yet
   else if (_signal * m_position_volume >= 0)
     {
      // Position already exists, the signal is ignored
      if (m_position_volume != 0)
         // Balance is not changed
         m_results.balance = _prev_balance;
      // No positions yet, buy signal

      else if (_signal == 1)
        {
         // Calculate current ASK price, recalculate price, volume and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1);
         m_position_volume = m_position_volume + 1;
         m_results.balance = _prev_balance;
        }
      // No positions yet, sell signal
      else if (_signal == -1) 
        {
         // Calculate current BID price, recalculate price, volume and balance
         current_price = _next_open;
         m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1);
         m_position_volume = m_position_volume - 1; 
         m_results.balance = _prev_balance;      
        }
      else
         m_results.balance = _prev_balance;
     }
   // Position is set already, the opposite direction signal is received
   else 
     {
      // buy signal/close sell position
      if (_signal > 0)
        {
         // Close position by ASK price, recalculate profit and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
          
         // If there is a signal for opening a new position, open it at once
         if (_signal == 1)
           {
            m_position_price = current_price;
            m_position_volume = 1;
           }
         else
            m_position_volume = 0;
        }
      // sell signal/close buy position
      else 
        {
         // Close position by BID price, recalculate profit and balance
         current_price = _next_open;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
         
         // If there is a signal for opening a new position, open it at once
         if (_signal == -1)
           {
            m_position_price = current_price;
            m_position_volume = -1;
           }
         else 
           m_position_volume = 0;
        }
     }
    
   // Calculate the current equity
   if (m_position_volume > 0)
     {
      current_price = _next_open;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else if (m_position_volume < 0)
     {
      current_price = _next_open + _next_spread * m_symbol_points;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else
      m_results.equity = m_results.balance;    
   
   return m_results;
  }
//+------------------------------------------------------------------+

計算クラスはできました。それではどのように動作するか確認するためインディケータ表示を実装します。

//+------------------------------------------------------------------+
//|                                                      Balance.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots   3
#property indicator_level1  0.0 
#property indicator_levelcolor Silver 
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1  
//--- plot Balance
#property indicator_label1  "Balance"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrBlue,clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1
//--- plot Equity
#property indicator_label2  "Equity"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLime
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Zero
#property indicator_label3  "Zero"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#include <BalanceClass.mqh>
//+------------------------------------------------------------------+
//| Input and global variables                                       |
//+------------------------------------------------------------------+
input string   iParentName        = "";             // Indicator name for balance calculation
input int      iSignalBufferIndex = -1;            // Signal buffer's index number
input datetime iStartTime         = D'01.01.2012';  // Calculation start date
input datetime iEndTime           = 0;             // Calculation end date
//--- Indicator buffers 
double   Balance[];       // Balance values
double   BalanceColor[];  // Color index for drawing the balance
double   Equity[];        // Equity values
double   Zero[];          // Zero value for histogram's correct display
//--- Global variables
double   Signal[1];       // Array for receiving the current signal
int      parent_handle;   // Indicator handle, the signals of which are to be used 

CBaseBalanceCalculator calculator; // Object for calculating balance and equity
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {     
   // Binding indicator buffers
   SetIndexBuffer(0,Balance,INDICATOR_DATA);
   SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,Equity,INDICATOR_DATA);
   SetIndexBuffer(3,Zero,INDICATOR_DATA);
  
   // Search for indicator handle by its name
   parent_handle = FindIndicatorHandle(iParentName);
   if (parent_handle < 0)
     {
      Print("Error! Parent indicator not found");
      return -1;
     } 
   
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {   
   // Set the borders for calculating the indicator
   int start_index = prev_calculated;
   int end_index = rates_total-1;
   
   // Calculate balance and equity values
   for(int i=start_index; i<end_index; i++)
     {
      // Check if the balance calculation corresponds the interval
      if (time[i] < iStartTime)
        {
         Balance[i] = 0;
         Equity[i] = 0; 
         continue;
        }
      if (time[i] > iEndTime && iEndTime != 0)
        {
         Equity[i] = (i==0) ? 0 : Equity[i-1];
         Balance[i] = Equity[i]; 
         continue;
        }
      
      // Request a signal from the parent indicator
      if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Copy the indicator main line data
        {
         Print("Data copy error: " + IntegerToString(GetLastError()));
         return(0);  // Finish the function operation and send indicator for the full recalculation
        }
      
      // Initialize balance and equity calculation
      // Since the signal is formed when the candle is closing, we will be able 
      //   to perform any operation only at the next candle's opening price
      BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]);
      
      // Fill out all indicator buffers
      Balance[i] = results.balance;
      Equity[i] = results.equity; 
      Zero[i] = 0;
      if (Balance[i] >= 0)
         BalanceColor[i] = 0;
      else
         BalanceColor[i] = 1;
     }
     
   // Fill out buffers for the last candle 
   Balance[end_index] = Balance[end_index-1];
   Equity[end_index] = Equity[end_index-1]; 
   BalanceColor[end_index] = BalanceColor[end_index-1];
   Zero[end_index] = 0;
     
   return rates_total;
  }
//+------------------------------------------------------------------+

ついに完成です!コンパイルして結果を検証します。


使用説明

新たに作成したインディケータの処理を評価するために、最低シグナルインディケータを1個は持つチャートにアタッチする必要があります。全ステップに従うと、すでにそのようなインディケータ:PivotCandles は入手済みのはずです。そこで入力パラメータを構成する必要があります。指定が必要なものは何か見ます。

図3はPivotCandles インディケータに残高インディケータをアタッチするための最初の2個のパラメータのコンフィギュレーションを示しています。残りのパラメータ2個はお好きなように設定できます。

図3 残高インディケータのパラメータ

図3 残高インディケータのパラメータ

以前のステップをすべて正しく行えば、以下の表示にひじょうに近い画像を取得します。

図4 PivotCandles インディケータシグナルを用いて作成される残高および資金曲線

図4 PivotCandles インディケータシグナルを用いて作成される残高および資金曲線

ここで異なる時間枠とシンボルで試し、もっとも収益性のある、またはもっとも損失を生むマーケット参入を見つけます。この方法はトレーディング結果に影響を与えるマーケットの相関関係を見つけるのに役立つことを付け加えておきます。

元々、同じシグナルに基づいて Expert Advisor を検証するのに費やす時間を上述の方法を用いて費やす時間と比較したいと思っていました。しかしやめにしました。というのもインディケータの計算にかかる時間は約1秒だからです。履歴をアップロードしまだアルゴリズムを作成しているティックがあっては、確かに Expert Advisor ではそのような短い時間は達成できません。


おわりに

上で述べた方法はひじょうにスピードの速いものです。その上、ポジションのオープン/クローズのシグナルを作成するインディケータの検証を明確に行います。それによりトレーダーはシグナルの分析と、単一のチャートウィンドウ内でデポジットのシグナルに対する応答を分析することができます。ただ、それにはまだ以下のような知っておくべき制約があります。

ただし、こういったデメリットにもかかわらず、メリットの方が多いこと、この検証方法はマーケット変動を分析しマーケットにより作成されるシグナルを処理するために作成されるその他ツールの一旦となることを願っています。