エキスパートアドバイザーから任意のインジケーターを読み取る:CopyBufferで扱う矢印、ライン、カラーインデックス

13 11月 2026, 08:31
Bohdan Klok
0
4

はじめに

インジケーターを使うトレーダーは、遅かれ早かれ「これと同じものを、自動で売買するようにしてほしい」と言い出します。プログラマーの答えは簡単に聞こえます。iCustom()かIndicatorCreate()でインジケーターを作成し、CopyBuffer()で値を読み取り、シグナルが出たらポジションを開く、というものです。しかし実際には、最初に出てくる疑問は売買とはまったく関係がありません。シグナルはどのバッファに入っているのか、という疑問です。

矢印インジケーターには、買いの矢印用と売りの矢印用に別々のバッファがある場合もあれば、両方を1つのバッファにまとめ、その隣にカラーインデックスを置いている場合もあります。「トレンド」インジケーターは色の変わる1本のラインを描くことがあり、その色は一度も描画されない別のバッファに保存されています。Marketで購入したコンパイル済みのインジケーターにはソースコードが付いていないため、SetIndexBuffer()の呼び出しを読むことはできません。データウィンドウは多少役に立ちますが、表示されるのは一度に1本の足の描画値だけで、カラーバッファと計算用バッファはまったく表示されません。

この記事では、どのインジケーターについても、この疑問に外側から答える方法を紹介します。バッファがどのように保存されているか、別のプログラムからそのすべてを読み取る方法、値だけを手がかりに矢印のバッファをラインやカラーインデックスと見分ける方法、シグナルがリペイントするかどうかを確認する方法、そしてどの番号がどの矢印に対応するのかがユーザーに見えるよう、答えをチャート上に書き戻す方法です。成果物はソースコード付きの小さなプログラム3つです。チャート上のすべてのインジケーターを調べるスクリプト(Code Baseで無料公開)、あらゆる種類のバッファを持つデモ用インジケーター、そして4つの汎用ルールで任意のインジケーターを売買するエキスパートアドバイザーです(後の2つは記事の末尾に全文を掲載しています)。

1. インジケーターバッファの正体

インジケーターは、SetIndexBuffer()で結び付けた配列に出力を保存します。種類は3つあります。

  • INDICATOR_DATA:描画され(ライン、ヒストグラム、矢印)、データウィンドウに表示される値。
  • INDICATOR_COLOR_INDEX:DRAW_COLOR_*スタイルで、各足に使われる色の番号。
  • INDICATOR_CALCULATIONS:描画も表示もされない中間的な配列。

3種類とも、インジケーターが宣言した順に1つの通し番号が振られ、どれもCopyBuffer()で読み取れます。多くのプログラマーにとって、これが最初の驚きです。チャート上では一度も目にしないバッファでも読み取ることができ、色付きのインジケーターでは、それが最も役に立つバッファであることが少なくありません。

プロットとバッファは同じものではありません。DRAW_COLOR_LINEのプロットは2つのバッファ(値とカラーインデックス)を使い、DRAW_COLOR_CANDLESは5つ(4つの価格とカラーインデックス)、DRAW_COLOR_ARROWは2つ(矢印の価格とその色)を使います。標準で付属しているHeiken Ashiインジケーターがよい例です。バッファ0〜3がローソク足の価格、バッファ4がカラーインデックスで、陽線なら0、陰線なら1です。

2つ目のポイントは、「何もない」ことがどのように保存されるかです。ラインのバッファは通常、すべての足に値を持っています。矢印のバッファは、矢印のある足にだけ価格を持ち、それ以外の足には「空」の値が入ります。どの値を空とみなすかは作者次第です。PlotIndexSetDouble(PLOT_EMPTY_VALUE)で設定するEMPTY_VALUE(DBL_MAX)が一般的ですが、0.0を使うインジケーターも多くあります。0.0を有効な矢印の価格として扱う読み取り側には、すべての足に矢印があるように見えてしまいます。

具体的に見られるものを用意するため、DemoBuffers.mq5インジケーター(ソースは末尾に掲載)ではあらゆる種類のバッファを宣言しています。ラインとして描く単純移動平均、終値が移動平均を上抜けた足に出る矢印、カラーラインとして描く指数移動平均、そしてその移動平均の傾きを入れた計算用バッファです。

SetIndexBuffer(0,ExtSma,INDICATOR_DATA);             // line
SetIndexBuffer(1,ExtCross,INDICATOR_DATA);           // arrows, EMPTY_VALUE where there is none
SetIndexBuffer(2,ExtEma,INDICATOR_DATA);             // colour line: the value
SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); // colour line: 0 rising, 1 falling
SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);   // not drawn, not in the Data Window
PlotIndexSetInteger(1,PLOT_ARROW,233);
PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);

データウィンドウには、このインジケーターの値が3つ表示されます。移動平均、矢印、指数移動平均です。バッファ3と4はそこには表示されません。それなのに、このインジケーターで最も自然な「トレンド」シグナルであるラインの色は、バッファ3に入っています。図1は、5つすべてを読み取ったときにプログラムから見える内容です。

図1. EURUSD H1上のDemoBuffers:Buffer Inspector Kbスクリプトが読み取った2本のライン、矢印のバッファ、カラーインデックス、計算用バッファ。

2. インジケーターのすべてのバッファを読み取る

すでにチャートに適用されているインジケーターなら、入力パラメータを知らなくてもハンドルを取得できます。

for(int w=0; w<(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); w++)
   for(int i=0; i<ChartIndicatorsTotal(0,w); i++)
     {
      string name=ChartIndicatorName(0,w,i);
      int    h=ChartIndicatorGet(0,w,name);   // the same instance the user sees
      ...
      IndicatorRelease(h);                    // releases our handle, not the indicator
     }

これは見た目以上に重要です。ユーザーはチャート上ですでにインジケーターの入力パラメータを選んでおり、プログラムはまさにそのインスタンスを読み取るからです。チャートにないインジケーターの場合は、IND_CUSTOMを指定したIndicatorCreate()に、MQL5\Indicatorsを基準とした名前とMqlParamの配列を渡します。入力パラメータがテキストフィールドから与えられる場合(例えば"14,0,1.5,true")は、各項目に正しい型を与える必要があります。

int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;               // the first element is always the name
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        { p[i+1].type=TYPE_BOOL;   p[i+1].integer_value=(s=="true"); }
      else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
        { p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); }
      else if(s=="0" || StringToInteger(s)!=0)
        { p[i+1].type=TYPE_INT;    p[i+1].integer_value=StringToInteger(s); }
      else
        { p[i+1].type=TYPE_STRING; p[i+1].string_value=s; }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }

この関数を使う際は、3つのルールを押さえておくと時間を大きく節約できます。値はインジケーターの入力パラメータ画面に並ぶ順番に従う必要があり、入力パラメータを飛ばすことはできません。3番目の入力パラメータを設定するには、最初の2つも指定しなければなりません。列挙型はその数値で渡すので、MODE_EMAは1、PRICE_CLOSEは1です。そして、指定しなかった入力パラメータはデフォルト値のままなので、空の文字列を渡せばインジケーターは初期設定のまま作成されます。

バッファはいくつあるのでしょうか。ハンドルからはわかりませんが、CopyBuffer()からはわかります。存在しないバッファ番号に対しては-1を返すからです。バッファ0、1、2…と、最初に失敗するまで順に読み取れば、データ、カラー、計算用のバッファを合わせた数がわかります。

実用上の注意点が2つあります。

  • ハンドルを作成した後、インジケーターには計算する時間が必要です。計算が終わるまでBarsCalculated(handle)は-1を返すので、スクリプトはSleep()を使った短いループで待ち、エキスパートアドバイザーは単に次のティックでやり直します。
  • コピーは、最後に確定した足である1本前の足(インデックス1)から始めます。多くの矢印インジケーターでは形成中の足が確定するまで変化し続けるため、それに基づいた分析では、後で消える矢印が表示されてしまいます。

3. 矢印、ライン、カラーインデックスを見分ける

確定した足N本分のバッファの値を配列に読み込んだら、3つの簡単な統計量で十分です。

  • valid(有効値):EMPTY_VALUEでもNaNでもない値。
  • arrows(矢印):有効で、かつ0.0ではない値。
  • 小さな整数:絶対値が小さい整数の値。

これで分類は次のようになります。

バッファの中身 500本の足でどう見えるか
何もない 有効値がない、またはゼロだけ
矢印 値はあるが、足の30%以下にしかない
カラーインデックス 狭い範囲の小さな整数だけ(例えば0と1)
ライン それ以外すべて:(ほぼ)すべての足に値がある
int valid=0, arrows=0, ints=0;
double lo=DBL_MAX, hi=-DBL_MAX;
for(int i=0; i<n; i++)
  {
   if(v[i]==EMPTY_VALUE || !MathIsValidNumber(v[i])) continue;
   valid++; lo=MathMin(lo,v[i]); hi=MathMax(hi,v[i]);
   if(v[i]!=0.0) arrows++;
   if(MathAbs(v[i]-MathRound(v[i]))<1e-9 && MathAbs(v[i])<64) ints++;
  }
if(valid==0 || (arrows==0 && hi==0.0 && lo==0.0))  kind="empty";
else if(arrows>0 && arrows<=n*0.3)                  kind="arrows";
else if(ints==valid && hi-lo<=16)                    kind="colour index";
else                                                 kind="line";

なぜ30%なのでしょうか。矢印インジケーターは転換点に印を付けるもので、転換点はそもそもまれにしか現れません。Fractalsが印を付けるのは足の数パーセント、典型的な「シグナル」インジケーターでは10%未満です。一部の足で空になるライン(履歴の先頭部分の移動平均など)でも、大部分の足には値があります。このしきい値は意図的に緩くしてあり、しきい値に近いケースは黙って推測せずにユーザーに報告します。

色について「狭い範囲の小さな整数」とするのはなぜでしょうか。カラーインデックスは、プロットの色1つにつき1つずつ、0、1、2…という値を取ります。ラインが整数になることもあります(作者が丸めたオシレーターや階段関数など)が、その場合は、1つのプロットが持てるわずかな色の数よりもはるかに広い範囲で動きます。

図2はHeiken Ashiでの結果です。4本の価格ラインと、バッファ4に0と1の値を取るカラーインデックスがあります。これこそ、エキスパートアドバイザーがローソク足の色の変化に反応するのに必要なものです。

図2. Heiken Ashi:4本の価格ラインと、データウィンドウには表示されないバッファ4のカラーインデックス。

いくつかのケースには補足が必要です。ざっと見ただけでは間違った答えになりやすいところだからです。

  • EMPTY_VALUEではなく0.0で保存された矢印。上の分類ではすでに0.0を「矢印なし」として扱っていますが、EMPTY_VALUEとだけ比較するプログラムは、すべての足でシグナルを出してしまいます。必ず両方をチェックしてください。
  • Parabolic SARのように、すべての足に表示されるドット。チャート上では矢印のように見えますが、バッファはすべての足に値を持つのでラインです。シグナルはドットがあることではなく、ドットが価格の反対側へ移る瞬間です。
  • カラー矢印(DRAW_COLOR_ARROW)。矢印のバッファが足と価格を示し、その隣のカラーインデックスが買いの矢印か売りの矢印かを示します。1つのシグナルに両方のバッファが必要です。
  • 計算用バッファ。通常はラインに分類され、価格から大きく離れた値を持つことがよくあります。一度は確認する価値があります。平滑化やフィルタリングを行う前の「生の」シグナルをそこに保持しているインジケーターもあるからです。

4. どの矢印がどのバッファかをユーザーに見せる

「バッファ1:矢印、37個、最新は2026.10.07 08:00」という表は正確ですが、それでもトレーダーは、バッファ1が上向きの矢印なのか下向きの矢印なのかを知るために、日付をチャートと照らし合わせなければなりません。矢印そのものの横にバッファ番号を書き込むほうが、はるかに早くわかります。

for(int i=n-1, done=0; i>=0 && done<20; i--)
  {
   if(v[i]==EMPTY_VALUE || v[i]==0.0) continue;
   int      shift=n-i;                          // v[] runs from shift n (oldest) to shift 1
   datetime t=iTime(_Symbol,_Period,shift);
   string   obj=StringFormat("BufInspector_%d_%d",buffer,shift);
   ObjectCreate(0,obj,OBJ_TEXT,0,t,v[i]);       // at the arrow's own price
   ObjectSetString(0,obj,OBJPROP_TEXT,"B"+(string)buffer);
   done++;
  }

ラベルはバッファに保存されている値の位置に置かれます。矢印インジケーターではそれが矢印の描かれる価格なので、"B0"や"B1"は矢印のちょうどその位置に表示されます。バッファごとに1色を割り当てれば、2つの矢印バッファを簡単に見分けられます。

図3. Examples\Fractals:上フラクタルの上にB0、下フラクタルの下にB1が書き込まれています。

5. インジケーターはリペイントするか

矢印を持つバッファが見つかっても、それは答えの半分にすぎません。残りの半分は、確定した足に一度現れた矢印がそのまま残るかどうかです。新しい足ができると矢印を移動したり削除したりするインジケーターがあります。Fractalsは仕様として直近の2本の足でそうしますし、ZigZag系のインジケーターはさらに長い期間にわたってそうします。後で消える矢印で売買するエキスパートアドバイザーは、振り返ってみれば存在しなかったシグナルで売買していることになり、そのストラテジーテスターの結果はチャートと一致しません。

このチェックは簡単に自動化できます。直近の確定した足の値をその足の開始時刻とともに記憶し、新しい足が数本できるのを待ってから、同じ足をもう一度読み取って比較します。

datetime ExtTimes[];   // open times of the remembered bars
double   ExtValues[];  // buffer values on those bars
//--- remember the last 100 closed bars of buffer b
int Remember(const int handle,const int b)
  {
   if(CopyTime(_Symbol,_Period,1,100,ExtTimes)!=100) return(0);
   return(CopyBuffer(handle,b,1,100,ExtValues));
  }
//--- later: how many of the remembered bars changed their value?
int Changed(const int handle,const int b)
  {
   int changed=0;
   for(int i=0; i<ArraySize(ExtTimes); i++)
     {
      double v[];
      if(CopyBuffer(handle,b,ExtTimes[i],1,v)==1 && v[0]!=ExtValues[i])
         changed++;
     }
   return(changed);
  }

ここでは、インデックスではなく時刻で読み取ることが重要です。新しい足ができると、同じ足のインデックスが大きくなるからです。結果から、インジケーターが何本前の足までまだ判断を変えるのかがわかり、その本数から使うべきシグナル足が決まります。Fractalsでは答えは常に2本で、フラクタルを1本前の足ではなく3本前の足で読み取るのはそのためです。10本前の足でも矢印を変え続けるインジケーターは、ストラテジーテスターでの見た目がどうであれ、矢印が最初に現れた足で売買することはできません。

6. 読み取るバッファがない場合

インジケーターの中には、バッファではなくObjectCreate()で矢印を描くものがあります。チャートには矢印があるのに、データウィンドウは空で、CopyBuffer()はラインしか返さないか、何も返しません。こうしたシグナルは、どのプログラムもインジケーターのハンドル経由では読み取れません。このことはユーザーにはっきり伝える価値があります。そうしないと、「正しいバッファ番号」探しがいつまでも終わらないからです。スクリプトはこの状況を間接的に検出できます。ユーザーには矢印が見えているのに矢印のバッファがないインジケーターは、ほぼ間違いなくオブジェクトを描いています。Buffer Inspector Kbはレポートでその旨を伝えます。

7. バッファから売買ルールへ

バッファがわかれば、トレーダーが持ち込むほぼすべてのインジケーターを4つのルールでカバーできます。

ルール 代表的なインジケーター シグナル足での条件
矢印の出現 Fractals、矢印インジケーター 値が空でなく、0でもない
バッファがレベルを交差 RSI 30/70、CCI +-100、0を中心とするオシレーター 前の値 < レベル <= 現在の値
バッファ同士の交差 MACDのメイン/シグナル、Stochasticの%K/%D、Alligatorのライン 前の足でa < b、かつ現在の足でa >= b
カラーインデックスが特定の値になる Heiken Ashi、色付き移動平均、「トレンド」バー 前の値 != k、かつ現在の値 == k

BufferSignalEA.mq5(ソースは末尾に掲載)はまさにこれらのルールを実装しており、インジケーター名、その入力パラメータ、ルール、バッファ番号を入力パラメータとして受け取るので、1つのエキスパートアドバイザーを任意のインジケーターに向けることができます。その中心となるのは、シグナル足とその1本前の足の2つの値を読み取る1つの関数です。

bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }

カラーインデックスはdouble型で保存されているため、0.5の許容幅で比較します。判断は新しい足ごとに1回、OnTick()の中で他の何よりも先に行います。

const datetime bar=iTime(_Symbol,_Period,0);
if(bar==ExtLastBar)
   return;
if(BarsCalculated(ExtHandle)<InpSignalBar+2)
   return;                                     // the indicator is not ready yet: try on the next tick
ExtLastBar=bar;
const bool buy =RuleFires(InpBuyRule, InpBuyBuffer, InpBuyLevel, InpBuyBuffer2);
const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
if(buy==sell || HasPosition())
   return;                                     // no signal, both at once, or already in the market

形成中の足のティックごとにルールを評価すると、何度もシグナルが出るうえ、消えてしまうシグナルでも発動します。確定した足で1本につき1回だけ判断すれば、ストラテジーテスターと実際のチャートの動きが一致します。

各ルールが実際に発動することを確かめるため、このエキスパートアドバイザーをストラテジーテスターで5つのインジケーターに対して実行しました。決済はすべて同じ単純なもの(0.1ロット、ストップロス300ポイント、テイクプロフィット600ポイント、ポジションは常に1つまで)です。以下の数字はルールの動作テストであり、売買成績ではありません。フィルターもポジション管理もなしにインジケーターで売買するだけでは、ストラテジーとは言えません。

インジケーターとルール バッファ トレード数 利益トレード
Fractals、矢印の出現(シグナル足3) 買い1、売り0 184 53
MACD、メインがシグナルを交差 0と1 160 46
RSI(14)、30を上抜け / 70を下抜け 0 113 36
Heiken Ashi、色が0 / 1になる 4 170 53
DemoBuffers、買いは矢印、売りは色1 1と3 152 39

EURUSD H1、2025.10.01 - 2026.10.01、モデル「1 minute OHLC」。5つとも、失敗したリクエストは1件もありませんでした。トレード数の違いは、ポジションがないときに各インジケーターがどれだけの頻度でシグナルを出すかによるものです。

8. 別の時間足のインジケーターを読み取る

同じ読み取り方は、上位時間足のインジケーターにも使えます。ほとんどのフィルター(「H4で価格が200 EMAより上のときだけ買う」)はこの方法で作られています。ハンドルはその時間足を指定して作成し、そのハンドルの1本前の足が、その時間足で最後に確定した足になります。

int h4=iMA(_Symbol,PERIOD_H4,200,0,MODE_EMA,PRICE_CLOSE);
...
double ma[];
if(CopyBuffer(h4,0,1,1,ma)==1 && iClose(_Symbol,PERIOD_H4,1)>ma[0])
   allowBuy=true;

注意点が2つあります。第一に、プログラムの起動時には、別の時間足のデータがまだ準備できていないことがあります。ターミナルが履歴を構築するまでBarsCalculated()は-1を返し、CopyBuffer()は失敗するので、これを「シグナルなし」とみなさず、チェックを繰り返す必要があります。第二に、下位時間足で行う判断に、上位時間足のインデックス0の足を決して使ってはいけません。H1チャートでは形成中のH4の足が1時間ごとに変化するため、それを読むフィルターは、H4の足がどう終わるかをすでに知っているテスターでは完璧に見えても、実際のチャートでは違う動きをします。

9. プログラム

  • Buffer Inspector Kb(スクリプト、MQL5\Scripts、Code Baseで無料公開):インジケーターがすでに表示されているチャートにドロップします。すべてのウィンドウのすべてのインジケーターを調べ、表を「エキスパート」タブに出力し、Comment()でチャート上にも表示し、各矢印バッファの直近の矢印にラベルを付けます。入力パラメータにインジケーター名を指定すると、スクリプト自身がそのインジケーターを作成するので、Marketのコンパイル済みインジケーターに便利です。「Only remove the labels」を指定して実行すると、チャートがきれいに片付きます。
  • DemoBuffers.mq5(インジケーター、MQL5\Indicators):ライン、矢印、カラーインデックス付きのカラーライン、計算用バッファを1つにまとめた、実験用のインジケーターです。ソースは下に掲載しています。
  • BufferSignalEA.mq5(エキスパートアドバイザー、MQL5\Experts):上記の4つのルールで任意のインジケーターを売買します。インジケーター、その入力パラメータ、ルール、バッファ番号はすべて入力パラメータで指定します。ソースは下に掲載しています。

まとめ

インジケーターのバッファは、ソースコードが手に入らない場合でも、そのインジケーターの公開インターフェースです。すべてのバッファを読み取り、値で分類し、チャート上の矢印にラベルを付け、値が何本前まで変化するかを確認すれば、「どのバッファ番号を使えばいいのか」は、インジケーターの作者に尋ねるべき質問から、10秒で済む確認作業に変わります。同じ分類が、4つの汎用売買ルール(矢印、レベル、ラインの交差、色の変化)の土台になります。このルールによって、1つのエキスパートアドバイザーが初めて見るインジケーターでも確定した足で動作し、ストラテジーテスターに表示される結果が、実際のチャートでもそのまま起こるようになります。

ソースコード

各コードを記載のファイル名で、示されたフォルダー(ターミナルの「ファイル」→「データフォルダを開く」)に保存し、MetaEditorでF7キーを押してコンパイルしてください。

DemoBuffers.mq5 (MQL5\Indicators)

//+------------------------------------------------------------------+
//|                                                  DemoBuffers.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: one indicator with every kind of buffer - a line, arrows,"
#property description "a colour line with its colour index, and a calculation buffer that is not drawn."
#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   3
//--- plot 0: a plain line (buffer 0)
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrSilver
#property indicator_width1  1
//--- plot 1: arrows (buffer 1), drawn only where the close crosses the SMA upward
#property indicator_label2  "Cross up"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrDodgerBlue
#property indicator_width2  2
//--- plot 2: a colour line (buffer 2 = value, buffer 3 = colour index)
#property indicator_label3  "Trend EMA"
#property indicator_type3   DRAW_COLOR_LINE
#property indicator_color3  clrSeaGreen,clrOrangeRed
#property indicator_width3  2
//--- input parameters
input int InpSmaPeriod=20;   // SMA period
input int InpEmaPeriod=50;   // EMA period
//--- indicator buffers
double ExtSma[];             // 0: line
double ExtCross[];           // 1: arrows, EMPTY_VALUE where there is none
double ExtEma[];             // 2: colour line value
double ExtEmaColor[];        // 3: colour index, 0 rising, 1 falling
double ExtSlope[];           // 4: calculations, EMA change over one bar
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,ExtSma,INDICATOR_DATA);
   SetIndexBuffer(1,ExtCross,INDICATOR_DATA);
   SetIndexBuffer(2,ExtEma,INDICATOR_DATA);
   SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);
   PlotIndexSetInteger(1,PLOT_ARROW,233);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpSmaPeriod);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,InpEmaPeriod);
   IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("DemoBuffers(%d,%d)",InpSmaPeriod,InpEmaPeriod));
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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(rates_total<InpEmaPeriod+2)
      return(0);
   const double k=2.0/(InpEmaPeriod+1.0);
   int start=(prev_calculated>1) ? prev_calculated-1 : 1;
   if(prev_calculated==0)
     {
      ExtEma[0]=close[0];
      ExtSlope[0]=0.0;
      ExtEmaColor[0]=0.0;
     }
   for(int i=start; i<rates_total; i++)
     {
      //--- SMA, empty until there are enough bars
      if(i>=InpSmaPeriod-1)
        {
         double sum=0.0;
         for(int j=0; j<InpSmaPeriod; j++)
            sum+=close[i-j];
         ExtSma[i]=sum/InpSmaPeriod;
        }
      else
         ExtSma[i]=EMPTY_VALUE;
      //--- EMA, its slope and the colour of the slope
      ExtEma[i]=ExtEma[i-1]+k*(close[i]-ExtEma[i-1]);
      ExtSlope[i]=ExtEma[i]-ExtEma[i-1];
      ExtEmaColor[i]=(ExtSlope[i]>=0.0) ? 0.0 : 1.0;
      //--- an arrow under the bar where the close crosses the SMA upward
      ExtCross[i]=EMPTY_VALUE;
      if(i>=InpSmaPeriod && ExtSma[i-1]!=EMPTY_VALUE && close[i-1]<ExtSma[i-1] && close[i]>=ExtSma[i])
         ExtCross[i]=low[i];
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+

BufferSignalEA.mq5 (MQL5\Experts)

//+------------------------------------------------------------------+
//|                                               BufferSignalEA.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: trades the signals of any indicator by four generic rules on its"
#property description "buffers - an arrow appears, a level is crossed, one buffer crosses another, a colour"
#property description "index becomes a value. Signals are read on the closed bar, once per bar."
#include <Trade\Trade.mqh>
//--- the four generic rules
enum ENUM_SIGNAL_RULE
  {
   SIGNAL_OFF=0,          // Disabled
   SIGNAL_ARROW=1,        // Arrow appears in the buffer
   SIGNAL_CROSS_UP=2,     // Buffer crosses the level upward
   SIGNAL_CROSS_DOWN=3,   // Buffer crosses the level downward
   SIGNAL_LINE_UP=4,      // Buffer crosses the second buffer upward
   SIGNAL_LINE_DOWN=5,    // Buffer crosses the second buffer downward
   SIGNAL_BECOMES=6       // Buffer becomes equal to the level (colour index)
  };
//--- input parameters
input string           InpIndicator="Examples\\Fractals"; // Indicator name inside MQL5\Indicators
input string           InpParams="";                      // Its inputs, comma separated (empty = defaults)
input int              InpSignalBar=3;                    // Signal bar (1 = last closed bar)
input ENUM_SIGNAL_RULE InpBuyRule=SIGNAL_ARROW;           // Buy rule
input int              InpBuyBuffer=1;                    // Buy: buffer
input double           InpBuyLevel=0.0;                   // Buy: level
input int              InpBuyBuffer2=0;                   // Buy: second buffer
input ENUM_SIGNAL_RULE InpSellRule=SIGNAL_ARROW;          // Sell rule
input int              InpSellBuffer=0;                   // Sell: buffer
input double           InpSellLevel=0.0;                  // Sell: level
input int              InpSellBuffer2=1;                  // Sell: second buffer
input double           InpLots=0.1;                       // Lot
input int              InpStopPoints=300;                 // Stop loss, points
input int              InpTakePoints=600;                 // Take profit, points
input ulong            InpMagic=20261010;                 // Magic number
//--- global variables
CTrade   ExtTrade;
int      ExtHandle=INVALID_HANDLE;
datetime ExtLastBar=0;
//+------------------------------------------------------------------+
//| Indicator inputs from "14,0,1.5,true,text"                       |
//+------------------------------------------------------------------+
int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        {
         p[i+1].type=TYPE_BOOL;
         p[i+1].integer_value=(s=="true");
        }
      else
         if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
           {
            p[i+1].type=TYPE_DOUBLE;
            p[i+1].double_value=StringToDouble(s);
           }
         else
            if(s=="0" || StringToInteger(s)!=0)
              {
               p[i+1].type=TYPE_INT;
               p[i+1].integer_value=StringToInteger(s);
              }
            else
              {
               p[i+1].type=TYPE_STRING;
               p[i+1].string_value=s;
              }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }
//+------------------------------------------------------------------+
//| A value is empty if it is EMPTY_VALUE or not a number            |
//+------------------------------------------------------------------+
bool IsEmpty(const double v)
  {
   return(v==EMPTY_VALUE || !MathIsValidNumber(v));
  }
//+------------------------------------------------------------------+
//| Does the rule fire on the signal bar?                            |
//+------------------------------------------------------------------+
bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- two values: a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }
//+------------------------------------------------------------------+
//| Is there an open position of this EA?                            |
//+------------------------------------------------------------------+
bool HasPosition()
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==(long)InpMagic)
         return(true);
   return(false);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpSignalBar<1)
     {
      Print("Signal bar must be 1 or more: the forming bar can still change");
      return(INIT_PARAMETERS_INCORRECT);
     }
   ExtHandle=CreateByName(InpIndicator,InpParams);
   if(ExtHandle==INVALID_HANDLE)
     {
      PrintFormat("Cannot load %s (error %d)",InpIndicator,GetLastError());
      return(INIT_FAILED);
     }
   ExtTrade.SetExpertMagicNumber(InpMagic);
   ExtLastBar=0;
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(ExtHandle!=INVALID_HANDLE)
      IndicatorRelease(ExtHandle);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- one decision per bar
   const datetime bar=iTime(_Symbol,_Period,0);
   if(bar==ExtLastBar)
      return;
   if(BarsCalculated(ExtHandle)<InpSignalBar+2)
      return;
   ExtLastBar=bar;
   const bool buy=RuleFires(InpBuyRule,InpBuyBuffer,InpBuyLevel,InpBuyBuffer2);
   const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
   if(buy==sell || HasPosition())
      return;
   const double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   if(buy)
     {
      const double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      ExtTrade.Buy(InpLots,_Symbol,ask,ask-InpStopPoints*point,ask+InpTakePoints*point);
     }
   else
     {
      const double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      ExtTrade.Sell(InpLots,_Symbol,bid,bid+InpStopPoints*point,bid-InpTakePoints*point);
     }
  }
//+------------------------------------------------------------------+