English Русский 中文 Español Deutsch Português
スピンドルチャートインジケーター

スピンドルチャートインジケーター

MetaTrader 5 | 23 12月 2015, 12:15
1 023 0
Dmitriy Zabudskiy
Dmitriy Zabudskiy

イントロダクション

スピンドルチャートは、いわゆるボリュームバイプライスチャートの一種です。ボリュームバイプライスとは、シンボルの活動データをボリューム(一般的にティックボリューム)の形の中にプロットしたチャートです。マーケットプロファイルのようなものです。インターネット上では、有益な情報はあまりありませんでした。多くの場合、チャートの構成に関することについてばかりです。しかし、近年チャートを比較的目にするようになったということは、注目度が上がってきていると言えます。

読者の方から、そのようなインジケーターを作成して欲しいとのことで、チャートに関する情報をいただきました。自由に使える時間が足りないことや、開発の複雑さを考えると、インジケーターの開発はためらわれます。

スピンドルチャートは、日本におけるロウソク足チャートのように見えます。始値・終値・安値・高値が見て取れます。反面、ボリューム加重移動平均(VWMA)とボリュームレシオ (VR)が使われていることに加えられ、スピンドル(回転する軸)(図1)のように見えます。

図1. 日本式のロウソク足とスピンドルの比較

図1. 日本式のロウソク足とスピンドルの比較

図1から分かるように、2つの追加されたパラメータ(VWMA — ボリューム加重移動平均 と VR — ボリュームレシオ) が単純にチャート上に補足されています。この様子は、だれもが幼少時代に知っている、回転するおもちゃ(コマ)などのように見えます。このような理由で"スピンドル"(回転体)と呼ばれています。

VR と VWMA がどのように形成しているのかを考察します。ボリューム加重移動平均 (VWMA) は、移動平均の一種です。公式 (1)より算出されます。

VWMAの計算

ただし、P — 価格, V — ボリューム. 次のように言っても良いです。: "ボリューム加重移動平均は、価格とボリュームの積の特定期間の和を特定期間のボリュームの和で割ったものの累積合計に等しい。".

ボリュームレシオ (VR) は、一種の移動平均ですが、チャートには異なるように表示されます。第1の理由として、それ自体が価格範囲の値を持たないこと、第2の理由として、前の期間の相場の活動に影響を受けることが挙げられます。そのため、ティックボリュームとして、もしくは、スピンドルの幅として、別チャートに表示するのがベストです。公式(2)で計算できます。:

VRの計算

ただし、 V — ボリューム"ボリュームレシオは、現在のボリュームを、特定の期間のボリュームの平均で割った値に等しい。"と言えます。

よって、これらにより、"スピンドル"チャート (図2)を得ます。」

図2. スピンドルチャート

図2. スピンドルチャート

"なぜ図2のスピンドルは図1のように色付けされていないのか" 疑問に思うかもしれません。この疑問は次のチャプター — プロットの基礎で明らかになります。


プロットの基礎

MQL5には、いくつかのプロットのスタイルがあります。(線、セクション、ヒストグラム、矢印、エリアバー、ロウソク足) しかし、どれもスピンドルチャートを描写するには不足があります。つまりカスタムスタイルが必要になります。残念ながら、標準装備されたものはありません。しかし、ご自身のスタイルを作成するのに使える関数が山ほどあり、スピードと機能性と品質の違いを出せます。

"Bitmap" オブジェクトの助けを借りて、スタイルを作成します。上記のデメリットは、メモリ領域の使い過ぎとプロットの複雑さが、スピードと安定性の欠如につながってしまうことです。他のオブジェクトと比較したときの"Bitmap" オブジェクトのメリットは、透明化できることとオブジェクトの一部を使えることとは当然として、特定のスペースをプロットを操作できることにあります。スピンドルチャートを描写する際には、上記のメリットが必要になります。

"スピンドル"の見た目は、図1のようになります。これで、"回転体"の実体が満たされます。このプロットは、きわめて複雑で、"ビットマップ"オブジェクトを使っての開発が要求されます。このプロセスは図3のようになります。:

図3. "ビットマップ"を使って、"スピンドル"を描写する上での技術的な表現

図3. "ビットマップ"を使って、"スピンドル"を描写する上での技術的な表現

図3は、"回転体の塗りつぶし"における3種類の方法についての様子です。ただし:

  • p — "Bitmap" アンカーポイント
  • x — プロットの角度
  • ローマ数字は"スピンドル"の部品を表します。

よって、1つ目の回転体(図3, a)を描写するには、"大きなひし形のような形"として、4つの"ビットマップ"オブジェクトが必要になります。(図3, a; 部品: I, II, III, IV). ひし形のような図形の形状によって、幅と高さ(つまり、 Open, Close, VWMA, VR) に、異なる角度, a; 角: x1, x2, x3, x4)が必要になります。BMPのフォーマットで図形は四角形になります。その四角形の1つの頂点から側面に直線を引き、四角形を透明と塗りつぶしの2つの領域に分けます。

以下は計算方法です。すでに明らかですが、幅と高さ(つまり、Open, Close, VWMA, VR) の値でこのモデルを描写するには、単色には360 bitmap (角度を精確に表現) が、2色には720bitmapが必要です。

2番目の回転体(図3, b)は、領域が2つだけですが、より複雑です。("矢じり"と呼ぶ)   始値と終値の間の距離を考える必要があるので、角度の組み合わせが多くなります。代替手法 (図3, c)に表現される方法で、面倒な描写を避けることができます。

3番目の場合(図3, c)、4つの部品に分解されます。最初の2つ(I と II) は"おもちゃのコマ"と同様で、後ろ2つ(III と IV) は最初から余分な部品を覆ったものです。この方法では、隣接した回転体を重ねる可能性と背景が透明になる可能性があります。部品は合計で、"おもちゃのコマ"が180、その周りが180になります。

一般的に、スピンドルチャートの表現には900bitmapが必要になります。単一のチャートだとしてもリソースをかなり割きます。

それでは、"回転体"を埋める、比較的簡易で早いバージョンを考えてみましょう。(図2). チャートのバックグラウンドに関わらず、bitmapの数は360 です。(単色の180、他に180)

図4. "Bitmap"オブジェクトを使った、"塗りつぶさない回転体"のテクニカルな表現

図4. "Bitmap"オブジェクトを使った、"塗りつぶさない回転体"のテクニカルな表現

前のものと全く同様に、"塗りつぶさない回転体"は4つの部品から構成されます。つまり、色付き線と角度です。(0 から 180). オブジェクトのアンカーポイントに応じて角度が変化するので、360 bitmapを使う必要はありません。2つのアンカーポイントしかありません。 (図4, p1 及び p2): 1つのポイントで2つのオブジェクト、その他に対し2つのオブジェクト

何故bitmapが少なくて済むのでしょうか。図4 (a)のように対称なひし形系図形があるとします。すると、IはIVで置き換えられます。このためには、アンカーポイントを右上から左下に変える必要があります。結果、同色180あればよいことになり、側面に応じてアンカーポイントを変えます。

これで、数学、幾何学の面が明らかになりました。"塗りつぶさない回転体"を描写するために、計算プロセスと図形セクションについて考えてみます。(図5 、図6).

図5. "おもちゃのコマ"の計算

図5. "おもちゃのコマ"の計算

図5は、一般的な"おもちゃのコマ" (a)とその右手側(b)を表しています。すべての距離 (a, b, c, d)は、始値、終値、VWMA,VRが分かっているとき、簡単な計算で済みます。

  • a = Close - VWMA,
  • b = VWMA - Open,
  • c = Close - Open,
  • d = VR / 2.

a, b, d,が分かったので、公式3.1と3.3より、右側の三角形の斜辺を計算してef が求まります。したがって、右側の三角形で隣なり合った辺を斜辺で割ることにより、反対側の角のサインと等しくなります。公式3.2と3.4で、角x1x2のサインが計算できます。続いて、計算をして、角x1x2が分かります。そして、x2からx3を計算します。 同じ方法を"矢じり"にも使います。

図6. "矢じり"の計算

図6. "矢じり"の計算

プロットの基本が明らかになったので、インジケーターのコードを考えます。


インジケーターのコード

コーディングの前に、インジケーターのグラフィックリソースの準備が必要です。 - 透明な背景に540 х 540ピクセルのBMPフォーマットこのビットマップには、角からの線が含まれています。最初の89ビットマップでは、左上の角から線を引きます。角は1 から89まで変化します。次のビットマップでは、左下の角から直線を引きます。この角度は91から179度です。(水平線に対して1から89度)0, 90, 180の角のビットマップは、1 x 540 ピクセルと 540 х 1 ピクセルがそれぞれ必要になります。透明な背景は必要ありません。

合計で、362 bitmapです。 — 1つの角に181bitmap、その他に181 bitmap(bitmaps 1 と 181 は同じです). ファイルネームは線の色と角度によって決められます。(Red - 頭文字 "r", Blue - 頭文字"b")


Part1

OnInit関数。 すべての段階を考慮します

  • 特定のパラメータ (#property)は、今回の場合 — 11バッファ と 4種類のグラフィックタイプ(1ヒストグラムと3つの線)です。
  • リソースを実行可能なファイルとしてインクルードします。(#resource) 先述の通り362もあります。それぞれのファイルを別行で追加しなければなりません。そうしないと、アタッチされませんので、ご注意ください。上記の理由から、大半の行を省略しています。
  • 続いて、変数とバッファ用のインプット型パラメータです。
//+------------------------------------------------------------------+
//|                                                          SPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/ru/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/ru/users/aktiniy"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 11
#property indicator_plots 4
//---
#property indicator_label1  "Shadow"
#property indicator_type1   DRAW_COLOR_HISTOGRAM2
#property indicator_color1  clrRed,clrBlue,clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//---
#property indicator_label2  "Open"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//---
#property indicator_label3  "Close"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrBlue
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//---
#property indicator_label4  "VWMA"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrMagenta
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- リソースファイルのロード
#resource "\\Images\\for_SPC\\b0.bmp";
#resource "\\Images\\for_SPC\\b1.bmp";
#resource "\\Images\\for_SPC\\b2.bmp";
#resource "\\Images\\for_SPC\\b3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\b176.bmp";
#resource "\\Images\\for_SPC\\b177.bmp";
#resource "\\Images\\for_SPC\\b178.bmp";
#resource "\\Images\\for_SPC\\b179.bmp";
#resource "\\Images\\for_SPC\\b180.bmp";
#resource "\\Images\\for_SPC\\r0.bmp";
#resource "\\Images\\for_SPC\\r1.bmp";
#resource "\\Images\\for_SPC\\r2.bmp";
#resource "\\Images\\for_SPC\\r3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\r176.bmp";
#resource "\\Images\\for_SPC\\r177.bmp";
#resource "\\Images\\for_SPC\\r178.bmp";
#resource "\\Images\\for_SPC\\r179.bmp";
#resource "\\Images\\for_SPC\\r180.bmp";
//+------------------------------------------------------------------+
//| Type Drawing                                                     |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // スピンドル
   line_histogram=1, // ラインとヒストグラム
  };
//+------------------------------------------------------------------+
//| Type Price                                                       |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // 始値
   high=1,   // 終値
   low=2,    // 安値
   close=3,  // 終値
   middle=4, // 中間
  };
//--- インプット型パラメーター
input long         magic_numb=65758473787389; // マジックナンバー
input type_drawing type_draw=0;               // インジケーター描写タイプ
input int          period_VR=10;              // ボリュームレシオの期間
input int          correct_VR=4;              // ボリュームレシオの数
input int          period_VWMA=10;            // ボリューム加重移動平均の期間
input int          spindles_num=1000;         // スピンドルの数
input type_price   type_price_VWMA=0;         //ボリューム加重移動平均の価格タイプ
                                              // 始値=0; 高値=1; 安値=2; 終値=3; 中間=4
//--- アウトプット変数
int ext_period_VR=0;
int ext_correct_VR;
int ext_period_VWMA=0;
int ext_spin_num=0;
int long_period=0;
//--- チャートパラメーターの変数
double win_price_max_ext=0; //チャートの最大値
double win_price_min_ext=0; //チャートの最小値
double win_height_pixels_ext=0; // ピクセルの高さ
double win_width_pixels_ext=0;  // ピクセルの幅
double win_bars_ext=0; // バーの幅
//--- 補助変数
int end_bar;
//--- インジケーターバッファ
double         Buff_up[];   // ヒストグラムの上のバッファ
double         Buff_down[]; // ヒストグラムの下点のバッファ
double         Buff_color_up_down[]; // ヒストグラムの色のバッファ
double         Buff_open_ext[];  // 始値アウトプットバッファ
double         Buff_close_ext[]; // 終値アウトプットバッファ
double         Buff_VWMA_ext[];  // ボリューム加重移動平均アウトプットバッファ
double         Buff_open[];  // 始値バッファ
double         Buff_close[]; // 終値バッファ
double         Buff_VWMA[];  // ボリューム加重移動平均バッファ
double         Buff_VR[];   // ボリュームレシオバッファ
double         Buff_time[]; // バー始値時間バッファ

インプット型パラメータの数が少ないことが確認できるかと思います。

  • Magic number — インジケーターを区別;
  • Type of indicator drawing — クラシックビュー(スピンドル)か線の形状を同じにするか;
  • Volume Ratio formation period — VRの期間;
  • Volume Ratio correction number — VRが幅に影響するので、このパラメータで調整
  • Volume Weighted Moving Average formation period — VWMAの期間;
  • Number of spindles — システムの負荷を減らすため、スピンドルの表示数を減らせます;
  • Price type for plotting Volume Weighted Moving Average — VWMAの価格タイプ.

Output変数は、インプット型パラメータの有効化と調整に必要になります。チャートパラメーターの変数はインジケーターのウィンドウ画面で変更します。詳細は次のセクションを参照。

インジケーターバッファの宣言はPart1で終わっています。11のバッファがあります。


OnInit 関数

//+------------------------------------------------------------------+
//| カスタムインジケーター初期関数                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- インプット型変数の確認
   if(period_VR<=0)
     {
      ext_period_VR=10; // 変数の値の変更
      DAlert("Volume Ratio formation period was input incorrectly and has been changed.");
     }
   else ext_period_VR=period_VR;
   if(correct_VR<=0)
     {
      ext_correct_VR=10; // 変数の値を変更
      Alert("Volume Ratio correction number was input incorrectly and has been changed.");
     }
   else ext_correct_VR=correct_VR;
   if(period_VWMA<=0)
     {
      ext_period_VWMA=10; //変数の値を変更
      Alert("Volume Weighted Moving Average formation period was input incorrectly and has been changed.");
     }
   else ext_period_VWMA=period_VWMA;
   if(spindles_num<=0)
     {
      ext_spin_num=10; // 変数の値を変更
      Alert("Number of spindles was input incorrectly and has been changed.");
     }
   else ext_spin_num=spindles_num;
//--- チャートにプロットする最長期間を検索
   if(ext_period_VR>ext_period_VWMA)long_period=ext_period_VR;
   else long_period=ext_period_VWMA;
//--- インジケーターバッファマッピング
   SetIndexBuffer(0,Buff_up,INDICATOR_DATA);
   SetIndexBuffer(1,Buff_down,INDICATOR_DATA);
   SetIndexBuffer(2,Buff_color_up_down,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Buff_open_ext,INDICATOR_DATA);
   SetIndexBuffer(4,Buff_close_ext,INDICATOR_DATA);
   SetIndexBuffer(5,Buff_VWMA_ext,INDICATOR_DATA);
   SetIndexBuffer(6,Buff_open,INDICATOR_CALCULATIONS);
   SetIndexBuffer(7,Buff_close,INDICATOR_CALCULATIONS);
   SetIndexBuffer(8,Buff_VWMA,INDICATOR_CALCULATIONS);
   SetIndexBuffer(9,Buff_VR,INDICATOR_CALCULATIONS);
   SetIndexBuffer(10,Buff_time,INDICATOR_CALCULATIONS);
//--- インジケーターの名称をセット
   IndicatorSetString(INDICATOR_SHORTNAME,"SPC "+IntegerToString(magic_numb));
   PlotIndexSetString(0,PLOT_LABEL,"SPC");
//--- 精密さを定義
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- インジケーターを描写する最初のバーをセット
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,long_period+1);
//--- インジケーターに現在の値で描写をさせないようにする
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(2,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(3,PLOT_SHOW_DATA,false);
//--- 表示されない値をセット
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0);
//--- 使うオブジェクトを生成
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
        }
     }
//---
   return(INIT_SUCCEEDED);
  }

ここで入力されたパラメーターが正しいかどうか確認します。必要ならば、前で宣言した変数(output型変数 )を使います。前に利用された期間よりも大きいと判明した場合には、バッファを初期化し、インジケーターの表示を構成します。小さな配列で稼働させるため、グラフィックオブジェクトを生成します。("スピンドルの数"のパラメーターの制限を受けます).


OnChartEvent 関数

//+------------------------------------------------------------------+
//| チャートイベント関数                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- キープレスイベント
   if(id==CHARTEVENT_KEYDOWN)
     {
      if(lparam==82)
        {
         if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0)// check the presence of data on the chart
           {
            if(func_check_chart()==true)
              {
               if(type_draw==0)func_drawing(true,ext_spin_num,end_bar);
              }
           }
        }
     }
  }

この関数は、チャートを更新するとき(もしくは再描写するたとき)、キー "R" (code 82)制限します。インジケーターウィンドウのサイズが変化したとき、チャートを調整・再描写します。これは、ウィンドウのサイズが変わったとき、画像が引き伸ばされてしまうために行います。価格が変化したときにはチャートは自然に再描写されますが、ときとして、すぐに更新する必要があるときがあります。この関数はそのケースを意図したものです。

関数自体は、全体的にif-elseの条件文で構成されています。これにはインジケーターウィンドウの変化を確認する関数(func_check_chart)と、チャート描写をする関数(func_drawing)が含まれています。


インジケーターのウィンドウを確認する関数

//+------------------------------------------------------------------+
//| 関数チェック                                                 |
//+------------------------------------------------------------------+
bool func_check_chart()
  {
//--- 変数を返す
   bool x=false;
//---チャートのサイズを調べる
   int win=ChartWindowFind(); // サブウィンドウをインジケーターウィンドウとして定義
   double win_price_max=ChartGetDouble(0,CHART_PRICE_MAX,win); // チャートの最大値
   double win_price_min=ChartGetDouble(0,CHART_PRICE_MIN,win); // チャートの最小値
   double win_height_pixels=(double)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,win); //ピクセルの高さ
   double win_width_pixels=(double)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,win); //ピクセルの幅
   double win_bars=(double)ChartGetInteger(0,CHART_WIDTH_IN_BARS,win); // バーの幅

//--- 値が変わったかどうか確認
   int factor=(int)MathPow(10,_Digits);// double型をint型に変換
   if(int(win_price_max*factor)!=int(win_price_max_ext*factor))
     {
      win_price_max_ext=win_price_max;
      x=true;
     }
   if(int(win_price_min*factor)!=int(win_price_min_ext*factor))
     {
      win_price_min_ext=win_price_min;
      x=true;
     }
   if(int(win_height_pixels*factor)!=int(win_height_pixels_ext*factor))
     {
      win_height_pixels_ext=win_height_pixels;
      x=true;
     }
   if(int(win_width_pixels*factor)!=int(win_width_pixels_ext*factor))
     {
      win_width_pixels_ext=win_width_pixels;
      x=true;
     }
   if(int(win_bars*factor)!=int(win_bars_ext*factor))
     {
      win_bars_ext=win_bars;
      x=true;
     }
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      x=true;
     }
   return(x);
  }

この関数は、インジケーターのウィンドウサイズが変化したかどうかを確認するものとして使います。まず、チャート関数(ChartGetIntegerChartGetDouble)を用いて、ウィンドウの現在のパラメーター(価格の高さ、幅とピクセル)を探します。次に、その値を前に宣言したグローバル変数と比較します。(チャートパラメータの変数)


チャートプロットコントロール関数

//+------------------------------------------------------------------+
//| Func Drawing                                                     |
//+------------------------------------------------------------------+
void func_drawing(bool type_action,//修正タイプ: 0-最後の二つ, 1-全部
                  int num,         // 変更するスピンドルの数
                  int end_bar_now) // 現在の最新の足
  {
   int begin;
   if(end_bar_now>num)begin=end_bar_now-num;
   else begin=long_period+1;
//--- VRの最大値を探す   
   double VR_max=0;
   for(int x=begin; x<end_bar_now-1; x++)
     {
      if(Buff_VR[x]<Buff_VR[x+1])VR_max=Buff_VR[x+1];
      else VR_max=Buff_VR[x];
     }
//--- スケールの計算
   double scale_height=win_height_pixels_ext/(win_price_max_ext-win_price_min_ext);
   double scale_width=win_width_pixels_ext/win_bars_ext;
//--- プロット(x - オブジェクト名の一部, y - 配列インデックス)
   if(type_action==false)// false - 最後の二つのスピンドルを更新
     {
      for(int x=num-2,y=end_bar_now-2; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
//---
   if(type_action==true)// true - すべてのスピンドルを更新
     {
      for(int x=0,y=begin; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
   ChartRedraw();
  }

この関数は、チャートプロットの構造をコントールします。インプット型パラメーター: 修正パラメーター(最後の2つかすべて同時) スピンドルの総数、最後のスピンドル修正パラメーターは、価格が変化した場合、最後のスピンドルだけを修正します。また、インジケーターのパフォーマンスを向上するため、その他のスピンドルはそのままにされます。

次に、初めのロウソク足を計算します。バーに関する情報がスピンドルの数よりも少ない場合、チャート上の情報の最大数をもって描写を行います。

そして、最も大きいボリュームレシオ (func_picture 関数を通すためのパラメーターとして要求される)を見つけ、チャート描写のスケールを計算します。修正パラメーターによって、スピンドルの修正サイクルを呼び出します。(func_pictureで以前作成したグラフィカルオブジェクト ).


グラフィカルプロット関数

//+------------------------------------------------------------------+
//| Func Picture                                                     |
//+------------------------------------------------------------------+
void func_picture(string name,        // オブジェクトの名前
                  double open,        // 足の始値
                  double close,       // 足の終値
                  datetime time,      // バータイム
                  double VR,          //ボリュームレシオの値
                  double VR_maximum,  // ボリュームレシオの最大値
                  double VWMA,        // ボリューム加重移動平均の値
                  int correct,        // 表示した際のボリュームレシオのパラメーター
                  double scale_height,// 高さスケール (pixels/price)
                  double scale_width) // 幅スケール (pixels/bars)
  {
   string first_name;// プロットに使ったファイル名の頭文字
   string second_name_right;// 右側のプロットにつかたファイル名の残りの文字
   string second_name_left; // 左側のプロットに使ったファイル名の残り
   double cathetus_a;// 隣辺a
   double cathetus_b;// 隣辺b
   double hypotenuse;// 斜辺
   int corner;// コーナー
//--- 足の始値と終値を見つける
   cathetus_b=int(VR/VR_maximum/correct*scale_width);// ピクセル幅
                                                     //picture 540
   if(open<=close) first_name="r";// 上向きかドージ
   if(open>close) first_name="b"; // 下向き
//---
   if(open<VWMA)// VWMAが価格より上
     {
      cathetus_a=int((VWMA-open)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,open);
     }
   if(open>VWMA)// VWMA が始値より下
     {
      cathetus_a=int((open-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,open);
     }
   if(open==VWMA)// VWMAが始値の水準
     {
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,open);
     }
   if(close<VWMA)// VWMAが終値よりも上
     {
      cathetus_a=int((VWMA-close)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,close);
     }
   if(close>VWMA)// VWMAが終値より下
     {
      cathetus_a=int((close-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,close);
     }
   if(close==VWMA)// VWMA が終値と同じ
     {
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,close);
     }
  }

チャートプロットの"心臓部" — グラフィカルオブジェクトのビットマップの計算と関数の変更  バーの中で使われるビットマップ(4つのビットマップ)は、スピンドルを描写するために計算されます。次に、func_obj_mod関数を使い、グラフィカルオブジェクトのビットマップを変えます。(すべてのグラフィカルオブジェクトは コードの最初の方にあるOnInit関数の終わりに生成されています。).

そのとき修正されたバーのパラメーターは関数を通されます。前述の最も大きいボリュームレシオを、いわゆる隣辺bの計算の相対的なパラメーターとなります。(図5, b; dのサイズとして記述).

続いて、予備の変数を追加します。(1つ目は色、残りと右側はファイル名 — ファイル名の角度と隣辺 a と、bと斜辺と角) スピンドルの色は条件子ifで決定されます。そして、ボリューム加重移動平均(WVMA)に対する始値と終値の値に応じて、また、図5・図6の計算方法に基づいて、ビットマップの計算を行います。 もちろん、func_obj_mod関数を用いてグラフィカルオブジェクトの修正を行います。


オブジェクト修正関数

//+------------------------------------------------------------------+
//| Func Obj Mod                                                     |
//+------------------------------------------------------------------+
void func_obj_mod(string name,             // オブジェクト名
                  string file,             // ファイルソースのパス
                  int pix_x_b,             // 位置X
                  int pix_y_a,             //位置Y
                  int shift_y,             // シフトY
                  ENUM_ANCHOR_POINT anchor,// アンカーポイント
                  datetime time,           // タイムコーディネート
                  double price)            // 価格コーディネート
  {
   ObjectSetString(0,name,OBJPROP_BMPFILE,file);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,pix_x_b);// 位置X
   ObjectSetInteger(0,name,OBJPROP_YSIZE,pix_y_a);// 位置Y
   ObjectSetInteger(0,name,OBJPROP_XOFFSET,0);// X軸シフトしない
   ObjectSetInteger(0,name,OBJPROP_YOFFSET,shift_y);// Y軸のシフト
   ObjectSetInteger(0,name,OBJPROP_BACK,false);// 前面に表示
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);// ドラッグを禁止する
   ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);// グラフィックオブジェクトの名前を隠す
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,anchor);// アンカーポイントのセット
   ObjectSetInteger(0,name,OBJPROP_TIME,time);// タイムコーディネートのセット
   ObjectSetDouble(0,name,OBJPROP_PRICE,price);//価格コーディネイートのセット
  }

この関数は非常にシンプルで、値を入れ替えます。関数を変えるオブジェクトプロパティになります。主となる変更可能なプロパティは、オブジェクトビットマップ、見え方、アンカーポイントです。


OnCalculate 関数

//+------------------------------------------------------------------+
//| カスタムインジケーターの繰り返し関数                             |
//+------------------------------------------------------------------+
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(rates_total<long_period)
     {
      Alert("VR or VWMA period is greater than history data or history data is not loaded.");
      return(0);
     }
//--- ポジションを検索
   int position=prev_calculated-1;
   if(position<long_period)position=long_period; //ポジションの変更
//--- バッファ計算のメインサイクル 
   for(int i=position; i<rates_total; i++)
     {
      //--- ヒストグラムバッファを満たす
      Buff_up[i]=high[i];
      Buff_down[i]=low[i];
      if(open[i]<close[i])Buff_color_up_down[i]=0;// 上向き
      if(open[i]>close[i])Buff_color_up_down[i]=1;//下向き
      if(open[i]==close[i])Buff_color_up_down[i]=2;// ドージ
      //--- 補助バッファを満たす
      Buff_open[i]=open[i];
      Buff_close[i]=close[i];
      Buff_time[i]=double(time[i]);
      //--- ボリュームレシオの計算
      double mid_vol=0;
      int x=0;
      for(x=i-ext_period_VR; x<=i; x++)
        {
         mid_vol+=double(tick_volume[x]);
        }
      mid_vol/=x;
      Buff_VR[i]=tick_volume[i]/mid_vol; // VRの計算
      //--- ボリューム加重移動平均を計算
      long vol=0;
      double price_vol=0;
      x=0;
      switch(type_price_VWMA)
        {
         case 0:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(open[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 1:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(high[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 2:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(low[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 3:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(close[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 4:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               double price=(open[x]+high[x]+low[x]+close[x])/4;
               price_vol+=double(price*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
        }
      Buff_VWMA[i]=price_vol/vol; // calculate VWMA
      //---
      if(type_draw==1)
        {
         Buff_open_ext[i]=Buff_open[i];
         Buff_close_ext[i]=Buff_close[i];
         Buff_VWMA_ext[i]=Buff_VWMA[i];
        }
      else
        {
         //--- 使っていない配列のサイズを減少させる
         ArrayResize(Buff_open_ext,1);
         ArrayResize(Buff_close_ext,1);
         ArrayResize(Buff_VWMA_ext,1);
         //--- 使っていない配列をゼロアウト
         ZeroMemory(Buff_open_ext);
         ZeroMemory(Buff_close_ext);
         ZeroMemory(Buff_VWMA_ext);
        }
     }
   end_bar=rates_total;// 最後の足の数を定義
//---
   if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0 && type_draw==0)// インジケーターウィンドウのデータの利用可能性をチェック
     {
      func_drawing(func_check_chart(),ext_spin_num,end_bar);
     }
//--- 次のコール用にprev_calculated の値を返す
   return(rates_total);
  }

このインジケーターの標準関数は計算した後、バッファを満たします。ミスマッチのメッセージが表示されたとき、最初のVR と VWMA の期間とバーのデータが有効化されます。そして、バッファを満たす条件が整います。高値と安値を表すヒストグラムのバッファが満たされます。その後公式1・2に応じて、ボリュームレシオ(VR)とボリューム加重移動平均(VWMA)バッファが計算され、格納されます。(イントロダクション チャプターで定義).


その他の関数

インジケーターを新しいバーで適切に稼働させるための関数と(func_new_bar)  インジケーターの終了関数(OnDeinit)があります。

func_new_bar関数は、チャート上の新しい足の出現を決定し、func_check_chart 関数で補助として機能します。

//+------------------------------------------------------------------+
//| Func New Bar                                                     |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
   static datetime old_times; // 古い値の変数
   bool res=false;            // 分析結果変数
   datetime new_time[1];      // 新しい足の時間
   int copied=CopyTime(_Symbol,period_time,0,1,new_time); // 最後の足の時間を新しいnew_timeにコピー
   if(copied>0) // こピーされたデータ
     {
      if(old_times!=new_time[0]) // 新しい時間と古い時間が同じ場合
        {
         if(old_times!=0) res=true; // 最初の軌道でない場合、true = new bar
         old_times=new_time[0];     // 足の時間を保存
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+

この関数は、以前のarticlesですでに紹介されいるので、詳細は省きます。

OnDeinit関数は、先のOnInit関数で生成されたグラフィカルオブジェクトを削除します。この関数はインジケーターとしては一般的で、インジケーターがチャートから取り除かれたときに呼び出されます。

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 使ったオブジェクトの削除
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4");
        }
     }
  }

これでインジケーターのコードは終了です。トピックや定義に関して疑問点がある場合、記事のコメントかプライベートメッセージを通してお気軽にご連絡ください。


エキスパートアドバイザとトレード手法

トレードお手法を考える前に、エキスパートアドバイザーがこのインジケータでどのように稼働するのかテストしましょう。このテストは、1つのスピンドルだけを分析に使うEA上で実行します。VR (ボリュームレシオ)は使いません。単一のスピンドルのパターンをもとに分析を実行します。合計で30種類あります。詳細は図7の通りです。:

図7. スピンドルのパターン

図7. スピンドルのパターン

スピンドルの種類は、3つのグループと1つのサブグループに分けられます。(図7). 上記は、スピンドルの動きの違い、スピンドルの始値、終値とボリューム加重移動平均の違いを考慮することで可能です。

第一のスピンドルの違いを色だとします。つまり、その期間の上向きか下向きかを見ます。(図7, コラム1). 図7,最初のコラム (0) — 上向き (赤) と (1) — 下向き (青). 次のコラムでは、影S (高値と安値)に応じた実体Bの差を見ます (始値と終値) この例での差では、3種類に分けられます。(図7, コラム2). 3つ目のコラムでは、VWMA(ボリューム加重移動平均)を高値・安値と比較します。高値と安値の上(1)、下(2)、中間(3)のどれかに分けられます。3つ目のコラムでは、スピンドル(3)がVWMAの期間の始値と高値から異なります。そのため、他のコラム3-3(コラム3(3)から派生)が図7にあります。

上記の差による組み合わせにより、30種類のスピンドルのパターンを得ます。

図7上のすべてのパターンは、下記のコードによりエキスパート上で関数の結果に応じて割り当てられます。


エキスパートアドバイザーのパラメーター

すべてのコードは関数に分割でき、コード全体の総量を減らすため、関数はサブ関数から呼び出します。よって、関数の階層ツリーを作成します。コードの初期で宣言されたインプット型変数は、ロットサイズ、ストップロス、スピンドルの30パターンによって捕捉されたインジケーターのパラメーターと同じです。インジケーターハンドルの変数と、パターンを決定するために使われる保存データのためのバッファは、最後に宣言されます。 

//+------------------------------------------------------------------+
//|                                                        EASPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/ru/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/ru/users/aktiniy"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Type Drawing                                                     |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // スピンドル
   line_histogram=1, // ラインとヒストグラム
  };
//+------------------------------------------------------------------+
//| Type Price                                                       |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // 始値
   high=1,   // 終値
   low=2,    // 安値
   close=3,  // 終値
   middle=4, // 中間
  };
//--- インプット型パラメーター
input long         magic_numb=65758473787389; // マジックナンバー
input type_drawing type_draw=1;               // インジケーター描写タイプ
input int          period_VR=10;              // ボリュームレシオの期間
input int          correct_VR=4;              // ボリュームレシオの数
input int          period_VWMA=10;            // ボリューム加重移動平均の期間
input int          spindles_num=10;           //スピンドルの数
input type_price   type_price_VWMA=0;         //ボリューム加重移動平均の価格タイプ
                                              // 始値=0; 高値=1; 安値=2; 終値=3; 中間=4
input double lot=0.01;                        // ロットサイズ
input int    stop=1000;                       //ストップロス
//---
input char   p1=1;                            // パターンごとのアクション 1-買い, 2-売り, 3-決済, 4-何もしない
input char   p2=1;
input char   p3=1;
input char   p4=1;
input char   p5=1;
input char   p6=1;
input char   p7=1;
input char   p8=1;
input char   p9=1;
input char   p10=1;
input char   p11=1;
input char   p12=1;
input char   p13=1;
input char   p14=1;
input char   p15=1;
input char   p16=1;
input char   p17=1;
input char   p18=1;
input char   p19=1;
input char   p20=1;
input char   p21=1;
input char   p22=1;
input char   p23=1;
input char   p24=1;
input char   p25=1;
input char   p26=1;
input char   p27=1;
input char   p28=1;
input char   p29=1;
input char   p30=1;
//---
int handle_SPC; // インジケーターハンドル
long position_type; // ポジションタイプ
//--- インジケーターのコピーした値のバッファ
double         Buff_up[3]; // ヒストグラムの上部のバッファ
double         Buff_down[3]; // ヒストグラムの下部のバッファ
double         Buff_color_up_down[3]; // ヒストグラムの色のバッファ
double         Buff_open_ext[3]; // 始値のバッファ
double         Buff_close_ext[3]; // 終値のバッファ
double         Buff_VWMA_ext[3]; // ボリューム加重移動平均のバッファ

OnInit関数でインジケーターの初期化を行います。
//+------------------------------------------------------------------+
//| エキスパート初期関数                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   handle_SPC=iCustom(_Symbol,PERIOD_CURRENT,"SPC.ex5",magic_numb,type_draw,period_VR,correct_VR,period_VWMA,spindles_num,type_price_VWMA);
//---
   return(INIT_SUCCEEDED);
  }


サーバーへオーダーを送信する関数

2種類の関数があります: 1つはオープンオーダー、もう一つは決済オーダーです。どちらの関数もMQL5 ドキュメントに例があり、トレードリクエスト構造結果のさらなる分析のOrderSend関数の組み合わせを含みます。

//+------------------------------------------------------------------+
//| オーダー関数                                                  |
//+------------------------------------------------------------------+
bool func_send_order(ENUM_ORDER_TYPE type_order,// type of the placed order
                     double volume)             // ロット数
  {
   bool x=false; // アンサー変数
//--- オーダー送信のために変数を宣言
   MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- オーダー送信のために変数をセット
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=volume;
   if(type_order==ORDER_TYPE_BUY)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      order_request.sl=order_request.price-(_Point*stop);
     }
   if(type_order==ORDER_TYPE_SELL)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      order_request.sl=order_request.price+(_Point*stop);
     }
//--- オーダー送信
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
//--- 結果の確認
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }
//+------------------------------------------------------------------+
//| ポジション削除関数                                             |
//+------------------------------------------------------------------+
bool func_delete_position()
  {
   bool x=false;
//--- mark the position to work with
   PositionSelect(_Symbol);
   double vol=PositionGetDouble(POSITION_VOLUME);
   long type=PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type_order;
   if(type==POSITION_TYPE_BUY)type_order=ORDER_TYPE_SELL;
   else type_order=ORDER_TYPE_BUY;
//--- オーダー送信のために変数を宣言
   MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- オーダー送信のために変数をセット
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=vol;
   if(type_order==ORDER_TYPE_BUY)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   if(type_order==ORDER_TYPE_SELL)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
//--- オーダー送信
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
//--- 結果の確認
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }

チャート上の新しい足の出現を決定づける補助関数func_new_barもまた、コードに含まれています。上記は説明済みで、コードに記述する必要はありません。

すべての標準関数を記述うした後、計算の"心臓部"を考えます。

すべての動きの処理は OnTick 関数で行われます。まず、計算に使われるバッファをCopyBuffer 関数で埋めます。次に、現在のシンボルがポジションを持ったかどうかを確認します。これは他のオーダー(ポジション)と関数を制御するために必要です。その後、パターン認識を準備します。現在の足の実体(始値と終値の距離)と影(高値と安値の距離)と、func_one関数を通したその比率(図7, コラム2)がそうです。

次に、func_two関数 と func_three関数をそれぞれ図7のコラム3 と 3-3のように使います。その後、スピンドルの色を図7のコラム1にしたがって、switch を用いて確認します。このようにして、次のswitch演算子が、変数afun_1_1の値によってfunc_pre_work (後ほど説明)関数に変わったとき、関数のツリーを得ます。これは図7のコラム2の通りであり、実体と影のサイズに基づいています。

//+------------------------------------------------------------------+
//| ティック関数                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      //--- copy the indicator buffers
      CopyBuffer(handle_SPC,0,1,3,Buff_up);
      CopyBuffer(handle_SPC,1,1,3,Buff_down);
      CopyBuffer(handle_SPC,2,1,3,Buff_color_up_down);
      CopyBuffer(handle_SPC,3,1,3,Buff_open_ext);
      CopyBuffer(handle_SPC,4,1,3,Buff_close_ext);
      CopyBuffer(handle_SPC,5,1,3,Buff_VWMA_ext);
      //--- 状況分析
      //--- オーダーがあるか確認
      if(PositionSelect(_Symbol)==true)
        {
         position_type=PositionGetInteger(POSITION_TYPE); // BUY=0, SELL=1
        }
      else
        {
         position_type=-1; // シンボルのポジションなし
        }
      //--- 比較のための値の準備
      double body=Buff_open_ext[2]-Buff_close_ext[2];
      body=MathAbs(body);
      double shadow=Buff_up[2]-Buff_down[2];
      shadow=MathAbs(shadow);
      if(shadow==0)shadow=1;// 0での割り算を回避
      double body_shadow=body/shadow;
      //--- 関数結果の変数
      char afun_1_1=func_one(body_shadow);
      char afun_2_1=func_two(Buff_up[2],Buff_down[2],Buff_VWMA_ext[2]);
      char afun_3_1=func_three(Buff_open_ext[2],Buff_close_ext[2],Buff_VWMA_ext[2]);
      //---
      switch(int(Buff_color_up_down[2]))
        {
         case 0:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p1,p2,p3,p4,p5);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p6,p7,p8,p9,p10);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p11,p12,p13,p14,p15);
                  break;
              }
           }
         break;
         case 1:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p16,p17,p18,p19,p20);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p21,p22,p23,p24,p25);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p26,p27,p28,p29,p30);
                  break;
              }
           }
         break;
        }
     }
  }

func_pre_work関数は、すでに形成された関数ツリーを枝分かれさせます。図7コラム3(変数 f_2) と 3-3 (変数 f_3)に基づいたコードが得られました。切り替えはツリーの最後の関数 func_workで行われます。

//+------------------------------------------------------------------+
//| プリ関数                                                    |
//+------------------------------------------------------------------+
void func_pre_work(char f_2,     // 2つの関数の結果の関数
                   char f_3,     // 3つの関数の結果
                   char pat_1,   // パターン1
                   char pat_2,   // パターン2
                   char pat_3_1, // パターン3_1
                   char pat_3_2, // パターン3_2
                   char pat_3_3) // パターン3_3
  {
   switch(f_2)
     {
      case 1: //1
         func_work(pat_1);
         break;
      case 2: //2
         func_work(pat_2);
         break;
      case 3:
        {
         switch(f_3)
           {
            case 1: //3_1
               func_work(pat_3_1);
               break;
            case 2: //3_2
               func_work(pat_3_2);
               break;
            case 3: //3_3
               func_work(pat_3_3);
               break;
           }
        }
      break;
     }
  }

func_work関数は、ポジションをどうするかを決定します。この際4つから1つを選びます。: 買い、売り、決済、なにもしない。  最後に、すでに説明したfunc_send_order関数 と func_delete_position 関数に移します。

//+------------------------------------------------------------------+
//| Func Work                                                        |
//+------------------------------------------------------------------+
void func_work(char pattern)
  {
   switch(pattern)
     {
      case 1: // 買い
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_BUY,lot);
         break;
      case 2: // 売り
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_SELL,lot);
         break;
      case 3: // 決済
         if(position_type!=-1)func_delete_position();
         break;
      case 4: // 何もしない
         break;
     }
  }

最後に、前で言及した関数が残っています。: func_one, func_two 及び func_threeです。これらは転移されたデターを変換します。switch演算子で価格を整数型にします。図7に戻ると、func_one — は、コラム2で使われています。func_two — はコラム3で使われ、func_three — はコラム3-3で使われています。この関数の返り値は、図7に対応する整数1,2,3です。

//+------------------------------------------------------------------+
//| Func One                                                         |
//+------------------------------------------------------------------+
char func_one(double body_shadow_in)
  {
   char x=0; // 変数を返す
   if(body_shadow_in<=(double(1)/double(3))) x=1;
   if(body_shadow_in>(double(1)/double(3)) && body_shadow_in<=(double(2)/double(3))) x=2;
   if(body_shadow_in>(double(2)/double(3)) && body_shadow_in<=1) x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Two                                                         |
//+------------------------------------------------------------------+
char func_two(double up,// high [Buff_up]
              double down,// low [Buff_down]
              double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // 変数を返す
   if(VWMA>=up) x=1;
   if(VWMA<=down) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Three                                                       |
//+------------------------------------------------------------------+
char func_three(double open,// open [Buff_open_ext]
                double close,// close [Buff_close_ext]
                double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // 変数を返す
   if(open>=VWMA && close>=VWMA) x=1;
   if(open<=VWMA && close<=VWMA) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+

これで、EAが使えるようになったので、テストしてみましょう。まず、パラメーターを決定します。:

  • シンボルと時間軸 — EURUSD, H1;
  • テスト期間 — 01.01.2013 から 01.01.2015 (2年);
  • ストップロス — 1000;
  • サーバー — MetaQuotes-Demo.

最適化は実行系(つまり、買い、売り、決済、なにもしない、VWMAの期間) に対してのみ行います。つまり、それぞのパターンに対してどのやり方が最も利益をもたらすかが分かり、VWMAのH1での最適な期間が判明します。

図8は、ストラテジーテスターの設定です。:

図8. ストラテジーテスターの設定

図8. ストラテジーテスターの設定

以前説明したように、最適化は実行系だけに行います。VWMAの期間は10から500まで行います。図9:

図9. 最適化パラメーター

図9. 最適化パラメーター

この最適化プロセスでは、下記の結果が得られました。図10

図10. 最適化グラフ

図10. 最適化グラフ

最適化の結果として、138.71の利益がありました。これは、0.01ロットで初期資産10000の場合です。損失は2.74%です。(約28ユニット) 図11:

図11. 最適化結果

図11. 最適化結果

ロットサイズを0.1まで増やして、初期資産を1000まで減らしてみましょう。そして、最適化結果からえられたパラメーターを使って2回目のテストを行います。テストの正確さをだすため、テストモードをM1のOHLCに変更します。図12が結果です。:

図12. テスト結果 (バックテスト)

図12. テスト結果 (バックテスト)

結果として、2年間で742回のトレードが行われ(約1日に3回)、最大ドローダウンは252、純利益は1407、月60 (全体の投資額に対して6%)の利益となりました。理論上では、すべてが順調に稼働します。しかし、実践で同じように動くという保証はありません。

もちろん、このEAはさらなる改良が必要です。恐らくスピンドルの追加パターンの導入とVR基準の追加が必要になるでしょう。これは更なる考察の糧ですが、このような少ないパラメーターでもEAは興味深い結果を示してくれます。

インジケーターを使って取引する際は、トレード手法は極めてシンプルです。 — 矢じりが上向きになったとき買い、下向きになったとき売ります。コマは一種の反転です。これは図13から明らかです。:

図13. インジケーターの動作

図13. インジケーターの動作

図13でわかるように、このインジケーターは番号が1になるまで、上向きの矢じりを描写します。そして青いコマが発生します。これは、トレンドの方向の変換を示唆しています。トレンドは変化し、価格は番号2が出るまで下降します。その後、赤いコマが出現し、トレンドの変化となります。


結論

このインジケーターに関してあまり情報を持ち合わせていませんが、独創的で興味深いものです。恐らく、コード上のその複雑性が何か関係しているのかもしれません。時間を無駄に過ごすことはできません。このインジケーターが多くの人にとって役に立つことを祈っています。きわめて広大なので、この件に関して十分に開発されてはいないと思います。しかし、これは次第にフォーラムで取り扱われるようになるでしょう。最初の段階でほんの少しの結果しか得られなくても、EAの議論と改良には目を向けるべきです。記事の下でもプライベートメッセージでも、どんなコメントや議論も歓迎いたします。

これは、インジケーターに関するの別枠の記事でした。新しい面白いインジケーターのアイディアをお持ちでしたら、プライベートメッセージをください。開発をお約束することはできませんが、必ず一度考えてみて、おそらく何かアドバイスはします。私の次の記事は、テーマが大きく変わります。しかし、このことについて考えをやめることはないと思います。なぜならこれは単にアイディアで、このコードはまたプランの段階に過ぎないと思っているからです。

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

添付されたファイル |
images.zip (1210.6 KB)
reporttester.zip (125.3 KB)
easpc_ja.mq5 (24.74 KB)
spc_ja.mq5 (73 KB)
MQL5プログラミングベージックス:リスト MQL5プログラミングベージックス:リスト
トレーディング戦略開発のためのプログラミング言語の新バージョン、MQL5は、以前のバージョン、MQL4と比較してより強力で効果的な機能を提供しています。その利点は本質的にオブジェクト指向プログラミングの機能にあります。この記事は、ノードやリストなど複雑なカスタムデータ型を用いることについて詳しく見ていきます。また、MQL5での実際的なプログラミングにてリストを用いる例を紹介します。
MetaTrader 5 の注文、ポジション、取引 MetaTrader 5 の注文、ポジション、取引
メタトレーダー5の取引システムの仕組みへの理解がなければ、信頼性のある自動取引ロボットの作成はできません。クライアントターミナルが売買サーバーにアクセスし、ポジションや注文、取引に関する情報を取得します。MQL5を使用し適切に取得したデータを扱うには、MQL5とクライアント側の相互の連携に関するより良い理解が必要になります。
MQL5 クックブック: 価格の乖離を分析するマルチシンボルインジケーターの開発 MQL5 クックブック: 価格の乖離を分析するマルチシンボルインジケーターの開発
この記事では、特定の期間における価格の分離を分析するためのマルチシンボルインジケーターの開発を紹介します。そのトピックは、「MQL5クックブック:MQL5のマルチシンボルボラティリティインジケーターの開発」というマルチカレンシーインジケーターのプログラミングに関する以前の記事にてすでに紹介されています。なので、今回は新しい特徴や劇的に変更された機能についてのみ紹介します。マルチカレンシーインジケーターのプログラミングに詳しくなければ、以前の記事をまずお読みいただくことをお勧めします。
われわれはいかにして MetaTrader シグナルサービスとソーシャルトレーディングを発展させたのでしょうか われわれはいかにして MetaTrader シグナルサービスとソーシャルトレーディングを発展させたのでしょうか
われわれはシグナルサービスを強化し、メカニズムを改良し、新しい関数を追加し、欠陥を修正し続けています。2012年の MetaTrader シグナルサービスと現在の MetaTrader シグナルサービスはまったく異なる2つのサービスのようなものです。現在、特定バージョンの MetaTrader クライアントターミナルをサポートするサーバーのネットワークで構成される仮想ホスティングクラウドサービスを導入中です。