トレンドラインからフィボナッチリトレースメントを自動描画するインジケータ

スポンサーリンク

前回

「一番大変なフィボナッチリトレースメント書く作業は手作業でやらないといけない」
と言ったな。

あれは嘘だ。

ども、とのです。

フィボナッチリトレースメント手動で書くのが嫌過ぎて自動化してしまいました。

完全自動化は無理だけど

そもそもフィボナッチリトレースメントはチャート上に示された5波もしくは3波のエリオット波動が完成しかかっている兆候を発見して、
その最終波の終わりを予測するものです。

その波動パターンが形成されていることまでを自動認識させようとすると、
割と高度なパターン認識が必要になってきます。
(ZigZagをちょいと改造すればできそうですが)

今回はそこまですることはせず、
手動で書いたトレンドラインからフィボナッチリトレースメントを自動算出するという、
簡易な手法を用いました。

表示サンプル

このインジケータを使うと

20130904_1

このようなトレンドラインから

20130904_2

こんなフィボナッチリトレースメントを引いてくれます。

複数のラインが密集している価格帯で反転していますよね。
そこが抵抗帯になると予測して取引します。

密集している部分はその下にもあるので、どっちで反転するかはフィボナッチリトレースメントだけではわかりません。
別ウィンドウで開いているDTオシレーターがここで反転しているため、ここで反転する可能性が高いと判断するわけですね。

縦線はタイムサイクルなんですが未完成品です…

これ手で引くの超面倒くさそうでしょ?

実際マジで面倒です。
特に色の設定とレベルの指定。

これを自動でやっちゃってくれるのが今回のインジケータです。

ソース解説

そんでできたのがこちら

例によって自分用に書いたので超汚い上に未完成コードも残っていますが、
もう実践乗せちゃってるので公開します。

#property  indicator_chart_window
#property indicator_buffers  1
#property  indicator_color1  Black
 
string TrendLineNames[5];
datetime MaxDatetime[5];
datetime MinDatetime[5];
double MaxDatePrice[5];
double MinDatePrice[5];
int Index = 0; 
int VLIndex = 0; 
bool isFiveTrend = false;
bool useBigInner = false;

double OuterFiboLevel[3] = {1.268, 1.618, 2.618};
double AlterFiboLevel[3] = {0.618, 1, 1.618};
double InnerFiboLevel[4] = {0.382, 0.5, 0.618, 0.786};
double VLFiboLevel[3] = {0.382, 0.618, 1};

今回はインジケータなのですが、一般的なインジケータとして動作しません。
EAでもスクリプトでも何でも良いと思うのですが、たまたまベースに使ったのがインジケータだっただけです。

さらに、extern変数もありません。
動作はチャート上に引かれたトレンドラインの本数によって自動的に変化します。

ちょっとかっこいいでしょ?
実は変数設定するのが面倒くさかっただけ。

int init() {
   IndicatorShortName("AutoFibo_From_TrendLine");
   
   getTrendLines();
   
   // FiveTrend needs more than 3 trendline
   if (Index > 3) isFiveTrend = true;
   // AB+Inner or 1234+Inner trendline
   if (Index == 3 || Index == 5) useBigInner = true;
   
   if (Index < 1) return(0);   
   // 4 or B
   string FiboName;
   if (isFiveTrend) FiboName = "AutoFibo 4"; else FiboName = "AutoFibo B";
   bool isUpTrend = MinDatePrice[0] > MaxDatePrice[0];
   
   ObjectCreate(FiboName, OBJ_FIBO, 0, MinDatetime[0],MinDatePrice[0],MaxDatetime[0],MaxDatePrice[0]);
   setFiboLevel(FiboName, DarkOrange, OuterFiboLevel);
   createVLine(FiboName, 0, DarkOrange, VLFiboLevel);
   if (Index < 2) return(0);
   // 3 or A
   if (isFiveTrend) FiboName = "AutoFibo 3"; else FiboName = "AutoFibo A";
   getAlternativeFibo(FiboName, 1, Red);
   
   if (isFiveTrend && Index >= 4) {
      // 1
      getAlternativeFibo("AutoFibo 1", 3, Teal);
   }
   
   create100percentVLLine(); 
   
   if (!useBigInner ||
       (!((isFiveTrend && Index >= 5) || (!isFiveTrend && Index >= 3)))) {
       return(0);
   }
   // big inner
   FiboName = "AutoFibo inner";
   int InnerIdx;
   if (isFiveTrend)  InnerIdx = 4; else InnerIdx = 2;
   ObjectCreate(FiboName, OBJ_FIBO, 0, MinDatetime[InnerIdx],MinDatePrice[InnerIdx],MaxDatetime[InnerIdx],MaxDatePrice[InnerIdx]);
   setFiboLevel(FiboName, Teal, InnerFiboLevel);
   
   return(0);
}

ちょっと長いですけどここがメインの処理。
initだけで処理が完結しています。
処理の流れを示すと、

  1. getTrendLinesでトレンドラインを取得し、
  2. その本数で5波動、3波動、および内部リトレースメント用トレンドラインの存否を確認
  3. 本数に応じてフィボナッチリトレースメントを描画する
  4. タイムサイクルを描画する

こんな感じになります。

では個別に見ていきましょう。

トレンドラインを取得する

void getTrendLines() {
   string TmpTrendLineNames[5];
   datetime TmpMaxDatetime[5];
   datetime TmpMinDatetime[5];
   double TmpMaxDatePrice[5];
   double TmpMinDatePrice[5];
   
   for (int i = 0; i < ObjectsTotal(); i++) {
      string lineName = ObjectName(i);
      if (ObjectType(lineName) == OBJ_TREND) {
         Print("get! name:" + lineName);
         TmpTrendLineNames[Index] = lineName;
         datetime time1 = ObjectGet(lineName, OBJPROP_TIME1);
         datetime time2 = ObjectGet(lineName, OBJPROP_TIME2);
         double price1 = ObjectGet(lineName, OBJPROP_PRICE1);
         double price2 = ObjectGet(lineName, OBJPROP_PRICE2);
         if (time1 < time2) {
            TmpMaxDatetime[Index] = time2;
            TmpMinDatetime[Index] = time1;
            TmpMaxDatePrice[Index] = price2;
            TmpMinDatePrice[Index] = price1;
         } else {
            TmpMaxDatetime[Index] = time1;
            TmpMinDatetime[Index] = time2;
            TmpMaxDatePrice[Index] = price1;
            TmpMinDatePrice[Index] = price2;
         }
         Index++;
         if (Index > 4 ) break;
      }
   }

これがトレンドラインを取得する関数getTrendLinesの前半部分です。
前半部分では、表示されているトレンドラインをとりあえず取得して
“Tmp”と名の付いている配列に格納します。

チャート上に表示されているトレンドラインやラベルなどは「オブジェクト」と呼ばれています。
オブジェクトはそれぞれ名前を持っているため、オブジェクトのプロパティ取得には名前を用います。
しかし、手動で配置したオブジェクトは自動的に名前が付けられるため、
どの名前のオブジェクトが目的のオブジェクトかはわかりません。

そのため、チャート上のオブジェクトの数を返すObjectsTotal()と
インデックス指定でオブジェクトの名前を返すObjectName()を用い、
チャート上に表示されているオブジェクトを虱潰しに探していきます。

今回は最大で5本しか必要ないため、5本取得した時点で探索を終了します。

   ArrayCopy(MaxDatetime, TmpMaxDatetime, 0, 0);
   ArraySort(MaxDatetime, WHOLE_ARRAY, 0, MODE_DESCEND);
   
   for (i = 0; i < Index; i++) {
       for (int j = 0; j < Index; j++) {
           if (MaxDatetime[i] == TmpMaxDatetime[j]) {
               TrendLineNames[i] = TmpTrendLineNames[j];
               MinDatetime[i] = TmpMinDatetime[j];
               MinDatePrice[i] = TmpMinDatePrice[j];
               MaxDatePrice[i] = TmpMaxDatePrice[j];
           }
       }
   }
}

こちらが後半部分。

とりあえず取得していたトレンドラインを時間の降順に並び替えます。
これにより、配列の並び順は下記の4通りに限定されます。

  • B波、A波
  • B波、A波、内部リトレースメント
  • 4波、3波、2波、1波
  • 4波、3波、2波、1波、内部リトレースメント

パターンを限定することで簡略化

上記の並び順一覧を観てみると、そのトレンドラインの数も上から2本、3本、4本、5本とダブるものがありません。
これを利用して、チャート上のトレンドラインの本数で内部の動作を変更します。

再度initを見てみましょう。

   // FiveTrend needs more than 3 trendline
   if (Index > 3) isFiveTrend = true;
   // AB+Inner or 1234+Inner trendline
   if (Index == 3 || Index == 5) useBigInner = true;

トレンドラインが4本以上あれば5波動トレンドパターンであり、
3本か5本の場合は内部リトレースメントが存在するモードであると判定しています。

開発当初はこの2つの変数をextern変数にしていたのですが、
実際使ってみると本数とモードが乖離する場合が全くなかったのでこの形にしました。

フィボナッチリトレースメントを描画する

さて、ここからが本題。
取得したトレンドラインを元にフィボナッチリトレースメントを描画します。

トレンドラインがABCパターンの場合、Bの外部リトレースメントとAの代替価格予想、
5波動トレンドパターンの場合、上記に加えて1波の代替価格予想も加えます。
さらに、内部リトレースメントがある場合はそれも加えることになります。

内部および外部リトレースメントはそのトレンドラインに這わせるだけで良いので楽なのですが、
代替価格予想はずらさなければならないのでちょっと複雑です。

void getAlternativeFibo(string FName, int idx, color LColor) {
   int MaxDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MaxDatePrice[idx]);
   int MinDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MinDatePrice[idx]);
   int BarCount = MinDateBar - MaxDateBar;
   int BaseDateBar = ObjectGetShiftByValue(TrendLineNames[0], MaxDatePrice[0]);
   int ShiftDist = BaseDateBar - BarCount;
   datetime DatetimeDist = iTime(NULL, 0, ShiftDist);
   if (DatetimeDist == 0) {
      DatetimeDist = MaxDatetime[0] + (MaxDatetime[idx] - MinDatetime[idx]);
   }
   double PriceDist = MaxDatePrice[idx] - MinDatePrice[idx];
   ObjectCreate(FName, OBJ_FIBO, 0, DatetimeDist ,MaxDatePrice[0] + PriceDist, MaxDatetime[0], MaxDatePrice[0]);
   setFiboLevel(FName, LColor, AlterFiboLevel);
   createVLine(FName, idx,LColor, AlterFiboLevel);
}

代替価格予想のフィボナッチリトレースメントを作成する部分です。

基本的に代替価格予想は4波およびB波から伸ばして5波およびC波の終わりを予測するものなので、
そこが基準になります。
ObjectCreateの引数にMaxDatePrice[0]というふうに0(4波およびB波の配列index)を指定しているのはそういうわけです。

問題はそこからどれだけ伸ばすか
前半でぐちゃぐちゃやっている部分がそうですね。

前半ではトレンドラインの始端と終端のバーの差分を計算しています。
当初はMaxDatetimeとMinDatetimeを単純に引き算してたしていたのですが、
思ったようなバーの位置に配置されませんでした。
休日とかでバーの本数が変わってしまうからですかね、全然調べてませんが。
そこで、日付で指定するのではなくバーの本数で指定しようとしたというわけです。

MQL5なら…?

ところがここで問題発生。

オブジェクトの横方向位置は基本的に時間で指定しなければならないので、
バーの位置を確定させた上でそのバーの位置の時間を取得しなければなりません。

   datetime DatetimeDist = iTime(NULL, 0, ShiftDist);

これで取得できるはずでした。

が、このiTimeにマイナスのバー位置を指定すると0が返ってくる(´・ω・`)

おそらく、iTimeは時間情報が入っている配列から時間を取得しており、
未来の時間を取得しようとすると配列の範囲外になってしまうのでしょう。

この点MQL5だと配列の入れ方が逆になっていて古いバーが0になっているらしいので、
こんなことは起きないのでしょうけど…

そんなわけで、結局バーから時間を取得することは完全にはできなかったため、
iTimeの取得に失敗したらおとなしくMaxDatetimeとMinDatetimeの差分を利用しています。

まぁフィボナッチリトレースメントは時間ではなく価格差が重要なのでここらへんはそれほど神経質にならなくてもいいでしょう。
(時間リトレースメントでは大問題ですが)

void setFiboLevel(string FName, color LColor, double FiboLevel[]) {
   int LevelSize =  ArraySize(FiboLevel);
   ObjectSet(FName, OBJPROP_FIBOLEVELS, LevelSize);
   for (int i = 0; i < LevelSize; i++) {
      ObjectSet(FName, OBJPROP_FIRSTLEVEL + i, FiboLevel[i]);  
      ObjectSetFiboDescription(FName, i, FName + " "+ DoubleToStr((FiboLevel[i] * 100), 1));  
   }
   ObjectSet(FName, OBJPROP_LEVELCOLOR, LColor);
}

こちらは作成したフィボナッチリトレースメントにレベルを付与している部分です。
冒頭の変数宣言部分でレベルの配列を宣言しており、それを引数に渡すことで自動的にレベルを描画しています。

これ…いままでいちいち手動で行ってたんですよ。
ここが自動化ことでめちゃくちゃ時間短縮になりました。

未完:時間リトレースメントの描画

「フィボナッチブレイクアウト売買法」の手法の1つに時間リトレースメントによる反転時期予測があります。
こちらも既存のトレンドラインのからフィボナッチ比率に基づいてリトレースメントを算出するのですが、
さっきも言及したように時間の計算は単純に行っても合わないし、バー計算も限界があるのであまり信用ができません。

void createVLine(string FName, int idx, color LColor,  double FiboLevel[]){
   int MaxDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MaxDatePrice[idx]);
   int MinDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MinDatePrice[idx]);
   int BarCount = MinDateBar - MaxDateBar;
   int BaseDateBar = ObjectGetShiftByValue(TrendLineNames[0], MaxDatePrice[0]);
   for (int i = 0; i < ArraySize(FiboLevel); i++) {
      int FiboBarCount = BarCount * FiboLevel[i];
      int ShiftDist = BaseDateBar - FiboBarCount;
      datetime DatetimeDist = iTime(NULL, 0, ShiftDist);
      if (DatetimeDist == 0) {
         DatetimeDist = MaxDatetime[0] + (MaxDatetime[idx] - MinDatetime[idx]);
      }
      ObjectCreate("AutoFibo VL" + VLIndex, OBJ_VLINE, 0, DatetimeDist, 0);
      ObjectSet("AutoFibo VL" + VLIndex, OBJPROP_COLOR, LColor);   
   ObjectSetText("AutoFibo VL" + VLIndex, "trendLine " + idx);    
      VLIndex++;
   }
}

void create100percentVLLine() {
   if (isFiveTrend) {
      int FirstBar = ObjectGetShiftByValue(TrendLineNames[3], MaxDatePrice[3]);
      int ThirdBar = ObjectGetShiftByValue(TrendLineNames[1], MaxDatePrice[1]);
      int BarCount = FirstBar - ThirdBar;
      int ShiftDist = ThirdBar- BarCount;
      datetime DatetimeDist = iTime(NULL, 0, ShiftDist);
      if (DatetimeDist == 0) {
          DatetimeDist = MaxDatetime[1] + (MaxDatetime[3] - MinDatetime[3]);
      }
   }
   ObjectCreate("AutoFibo VL" + VLIndex, OBJ_VLINE, 0, DatetimeDist, 0);
   ObjectSet("AutoFibo VL" + VLIndex, OBJPROP_COLOR, MediumSlateBlue);   
   ObjectSetText("AutoFibo VL" + VLIndex, "100percentHighLow");   
   VLIndex++;
}

という訳でソース載っけるのみで解説は割愛。

まぁ、この手法でも日や週をまたがない場合(5分足など)での使用はうまくいくので、
短期トレードでこの手法を使うようになったら参考にできるんじゃないですかね。

あと、下の方にあるcreate100percentVLLineというメソッドですが、
これだけフィボナッチではなく直近の高値-高値か安値-安値を結んだ時間を100%右にずらした位置にラインを引いています。

以上で解説終わり。
超やっつけですが。

まぁビジュアルで見やすいので実際使ってみたほうが早いかもしれないっすね。

ソース全文

#property copyright "Copyright 2013, Tono"
#property link      "https://tono-n-chi.com"
#property  indicator_chart_window
#property indicator_buffers  1
#property  indicator_color1  Black
 
extern bool isFiveTrend = true;
extern bool useBigInner = true;
 
string TrendLineNames[5];
datetime MaxDatetime[5];
datetime MinDatetime[5];
double MaxDatePrice[5];
double MinDatePrice[5];
int Index = 0; 
int VLIndex = 0; 

double OuterFiboLevel[3] = {1.268, 1.618, 2.618};
double AlterFiboLevel[3] = {0.618, 1, 1.618};
double InnerFiboLevel[4] = {0.382, 0.5, 0.618, 0.786};
double VLFiboLevel[3] = {0.382, 0.618, 1};
 
int init() {
   IndicatorShortName("AutoFibo_From_TrendLine");
   
   getTrendLines();
   
   if (Index < 1) return(0);   
   // 4 or B
   string FiboName;
   if (isFiveTrend) FiboName = "AutoFibo 4"; else FiboName = "AutoFibo B";
   bool isUpTrend = MinDatePrice[0] > MaxDatePrice[0];
   
   ObjectCreate(FiboName, OBJ_FIBO, 0, MinDatetime[0],MinDatePrice[0],MaxDatetime[0],MaxDatePrice[0]);
   setFiboLevel(FiboName, DarkOrange, OuterFiboLevel);
   createVLine(FiboName, 0, DarkOrange, VLFiboLevel);
   if (Index < 2) return(0);
   // 3 or A
   if (isFiveTrend) FiboName = "AutoFibo 3"; else FiboName = "AutoFibo A";
   getAlternativeFibo(FiboName, 1, Red);
   
   if (isFiveTrend && Index >= 4) {
      // 1
      getAlternativeFibo("AutoFibo 1", 3, Teal);
   }
   
   if (!useBigInner ||
       (!((isFiveTrend && Index >= 5) || (!isFiveTrend && Index >= 3)))) {
       return(0);
   }
   // big inner
   FiboName = "AutoFibo inner";
   int InnerIdx;
   if (isFiveTrend)  InnerIdx = 4; else InnerIdx = 2;
   ObjectCreate(FiboName, OBJ_FIBO, 0, MinDatetime[InnerIdx],MinDatePrice[InnerIdx],MaxDatetime[InnerIdx],MaxDatePrice[InnerIdx]);
   setFiboLevel(FiboName, Teal, InnerFiboLevel);
   
   return(0);
}

void getTrendLines() {
   string TmpTrendLineNames[5];
   datetime TmpMaxDatetime[5];
   datetime TmpMinDatetime[5];
   double TmpMaxDatePrice[5];
   double TmpMinDatePrice[5];
   
   for (int i = 0; i < ObjectsTotal(); i++) {
      string lineName = ObjectName(i);
      if (ObjectType(lineName) == OBJ_TREND) {
         Print("get! name:" + lineName);
         TmpTrendLineNames[Index] = lineName;
         datetime time1 = ObjectGet(lineName, OBJPROP_TIME1);
         datetime time2 = ObjectGet(lineName, OBJPROP_TIME2);
         double price1 = ObjectGet(lineName, OBJPROP_PRICE1);
         double price2 = ObjectGet(lineName, OBJPROP_PRICE2);
         if (time1 < time2) {
            TmpMaxDatetime[Index] = time2;
            TmpMinDatetime[Index] = time1;
            TmpMaxDatePrice[Index] = price2;
            TmpMinDatePrice[Index] = price1;
         } else {
            TmpMaxDatetime[Index] = time1;
            TmpMinDatetime[Index] = time2;
            TmpMaxDatePrice[Index] = price1;
            TmpMinDatePrice[Index] = price2;
         }
         Index++;
         if (Index > 4 ) break;
      }
   }
   
   ArrayCopy(MaxDatetime, TmpMaxDatetime, 0, 0);
   ArraySort(MaxDatetime, WHOLE_ARRAY, 0, MODE_DESCEND);
   
   for (i = 0; i < Index; i++) {
       for (int j = 0; j < Index; j++) {
           if (MaxDatetime[i] == TmpMaxDatetime[j]) {
               TrendLineNames[i] = TmpTrendLineNames[j];
               MinDatetime[i] = TmpMinDatetime[j];
               MinDatePrice[i] = TmpMinDatePrice[j];
               MaxDatePrice[i] = TmpMaxDatePrice[j];
           }
       }
   } 
}

void getAlternativeFibo(string FName, int idx, color LColor) {
   int MaxDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MaxDatePrice[idx]);
   int MinDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MinDatePrice[idx]);
   int BarCount = MinDateBar - MaxDateBar;
   int BaseDateBar = ObjectGetShiftByValue(TrendLineNames[0], MaxDatePrice[0]);
   int ShiftDist = BaseDateBar - BarCount;
   datetime DatetimeDist = iTime(NULL, 0, ShiftDist);
   if (DatetimeDist == 0) {
      DatetimeDist = MaxDatetime[0] + (MaxDatetime[idx] - MinDatetime[idx]);
   }
   double PriceDist = MaxDatePrice[idx] - MinDatePrice[idx];
   ObjectCreate(FName, OBJ_FIBO, 0, DatetimeDist ,MaxDatePrice[0] + PriceDist, MaxDatetime[0], MaxDatePrice[0]);
   setFiboLevel(FName, LColor, AlterFiboLevel);
   createVLine(FName, idx,LColor, VLFiboLevel);
}

void setFiboLevel(string FName, color LColor, double FiboLevel[]) {
   int LevelSize =  ArraySize(FiboLevel);
   ObjectSet(FName, OBJPROP_FIBOLEVELS, LevelSize);
   for (int i = 0; i < LevelSize; i++) {
      ObjectSet(FName, OBJPROP_FIRSTLEVEL + i, FiboLevel[i]);  
      ObjectSetFiboDescription(FName, i, FName + " "+ DoubleToStr((FiboLevel[i] * 100), 1));  
   }
   ObjectSet(FName, OBJPROP_LEVELCOLOR, LColor);
}

void createVLine(string FName, int idx, color LColor,  double FiboLevel[]){
   int MaxDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MaxDatePrice[idx]);
   int MinDateBar = ObjectGetShiftByValue(TrendLineNames[idx], MinDatePrice[idx]);
   int BarCount = MinDateBar - MaxDateBar;
   int BaseDateBar = ObjectGetShiftByValue(TrendLineNames[0], MaxDatePrice[0]);
   for (int i = 0; i < ArraySize(FiboLevel); i++) {
      int FiboBarCount = BarCount * FiboLevel[i];
      int ShiftDist = BaseDateBar - FiboBarCount;
      datetime DatetimeDist = iTime(NULL, 0, ShiftDist);
      if (DatetimeDist == 0) {
         DatetimeDist = MaxDatetime[0] + (MaxDatetime[idx] - MinDatetime[idx]);
      }
      ObjectCreate("AutoFibo VL" + VLIndex, OBJ_VLINE, 0, DatetimeDist, 0);
      ObjectSet("AutoFibo VL" + VLIndex, OBJPROP_COLOR, LColor);   
      VLIndex++;
   }
}

int deinit() {
   ObjectDelete("AutoFibo 4");
   ObjectDelete("AutoFibo B");
   ObjectDelete("AutoFibo 3");
   ObjectDelete("AutoFibo A");
   ObjectDelete("AutoFibo 1");
   ObjectDelete("AutoFibo inner");
   for (int i = 0; i < 20; i++) {
       ObjectDelete("AutoFibo VL" + i);
   }
}

int start() {
   return(0);
}
スポンサーリンク