※当サイトはアフィリエイト広告(Amazonアソシエイツ等)を利用しています。
こんにちは。trade-engineer.com 運営者のHです。MQL4でカスタムインジケーターを作り始めたとき、「アローを表示したいけどSetIndexArrowの引数が何を意味するか分からない」「コンパイルは通るのにチャートにアローが出ない」という壁に必ずぶつかります。自分もこのフェーズで丸2日詰まった記憶があります。
MQL4でアローを表示する仕組み自体は複雑ではありません。DRAW_ARROWという描画スタイルを指定し、SetIndexStyle・SetIndexArrow・SetIndexBufferの3関数をOnInit内で正しい順序で呼び出す。あとはOnCalculate内でバッファに座標値を代入するだけです。ただし設定の順序・バッファ数の宣言・EMPTY_VALUEによる初期化のどれかを省くと動かなくなるので、構造を正確に把握する必要があります。
この記事では、MQL4でDRAW_ARROWを使ったアロー表示をコピペで動くコード付きで完全解説します。RSIを使った実践コード例、Wingdingsコード一覧、よくあるエラーと対処法まで一気に押さえます。
- MQL4のDRAW_ARROWはSetIndexStyle・SetIndexArrow・SetIndexBufferの3関数をセットで使う
- indicator_buffersの宣言数と実際に使うバッファ数を一致させないとランタイムエラーになる
- OnCalculate内でEMPTY_VALUEを毎ループ初期化してから座標値を代入するのが正しい構造
- Wingdingsコード番号を変えるだけで矢印の形状・向きを自由にカスタマイズできる
MQL4でアロー表示に必要な基礎知識と実装構造
アロー実装を正確に動かすには、プロパティ宣言・グローバル配列・OnInit設定・OnCalculate条件記述の4ブロックをすべて揃える必要があります。どれか一つ欠けても表示されないので、まず全体の流れを把握してから個別のブロックを理解していきましょう。全文コードを先に示してから各パーツを解説する順番で進めます。
DRAW_ARROWとSetIndexArrowの役割と違い
MQL4でチャート上に矢印を描画する場合、まず「矢印を使って描画する」というスタイルを宣言し、次に「どの形状の矢印を使うか」を指定します。この2ステップをそれぞれ担当するのがSetIndexStyleの第2引数DRAW_ARROWとSetIndexArrow関数です。
SetIndexStyleはインジケーターバッファの描画スタイル全体を設定する関数で、引数は5つあります。順番に説明すると:第1引数はバッファのインデックス番号(0始まり)、第2引数は描画スタイル(DRAW_ARROW、DRAW_LINE等)、第3引数はライン種別(DRAW_ARROWの場合は0を指定)、第4引数はサイズ(1〜5の整数、2が標準的)、第5引数は色(clrRedなどの定数またはRGB値)。この5つをすべて指定することで、アローの視覚的な外見の骨格が決まります。
SetIndexArrowはその後でWingdingsフォントのコード番号を指定し、矢印の具体的な形状を決めます。第1引数はバッファインデックス(SetIndexStyleと合わせる)、第2引数がコード番号です。コード233が上向き中矢印、234が下向き中矢印で、これが最もよく使われる組み合わせです。
混乱しやすいポイントとして、SetIndexStyleでDRAW_ARROWを指定するだけでは矢印の形状が未定義になります。SetIndexArrowを呼び出さないと、環境によってはデフォルトの不明なシンボルが表示されたり、何も表示されないことがあります。両方を必ずセットで呼び出すことを習慣にしてください。なお、この2つの関数の呼び出し順序はどちらが先でも動作上は問題ありませんが、リファレンスの記述順に合わせてSetIndexStyle→SetIndexArrowの順で書くのが慣例です。
indicator_buffersとグローバル配列の正しい宣言
MQL4のカスタムインジケーターでアローを使う場合、使うバッファ数を必ずファイル冒頭の#propertyセクションで宣言する必要があります。この宣言が抜けていると、SetIndexBufferを呼び出した時点でランタイムエラーが発生しチャートに何も表示されません。
アローを上向きと下向きの2種類使う場合は#property indicator_buffers 2と書きます。1方向だけなら1、さらにRSI値などの計算用バッファを追加するなら3や4に増やします。重要なのは、宣言した数と実際にSetIndexBufferに渡す配列の数が完全に一致していることです。宣言数より多くSetIndexBufferを呼び出すとエラーになります。
グローバル変数としてdouble型の配列を宣言します。
#property indicator_chart_window #property indicator_buffers 2 double UpArrow[]; double DownArrow[];
配列名は自由に決められますが、用途が分かる名前にしておくのがベストです。HighSignal/LowSignalという命名パターンもよく見かけますが、「上向き」「下向き」を直接表すUpArrow/DownArrowの方が後でコードを読み返したときに混乱が少なくなります。グローバル変数として宣言する理由はOnInitとOnCalculateの両方からアクセスする必要があるためです。OnInit内のローカル変数として宣言するとOnCalculateから参照できないので、必ずグローバルスコープに置きます。
計算用の中間バッファを追加したい場合(例:RSI値を一時保存するバッファ)は、同様にグローバル配列を追加してindicator_buffersの数も増やします。この計算専用バッファはSetIndexStyleでDRAW_NONEを指定して描画を無効化するのが正しい実装です。DRAW_NONEを忘れるとチャート上に不要な線が表示されることがあります。
OnInit関数でアロー用バッファを設定する手順
OnInit関数はインジケーターがチャートに読み込まれた瞬間に1回だけ実行されます。ここでアロー表示に必要な3つの設定を行います。設定漏れがあると後からデバッグが難しくなるので、この3関数をセットで書くことを徹底しましょう。
int OnInit()
{
// 上向きアロー(バッファインデックス0)
SetIndexStyle(0, DRAW_ARROW, 0, 2, clrDodgerBlue);
SetIndexArrow(0, 233);
SetIndexBuffer(0, UpArrow);
SetIndexLabel(0, "Buy Signal");
// 下向きアロー(バッファインデックス1)
SetIndexStyle(1, DRAW_ARROW, 0, 2, clrOrangeRed);
SetIndexArrow(1, 234);
SetIndexBuffer(1, DownArrow);
SetIndexLabel(1, "Sell Signal");
return(INIT_SUCCEEDED);
}
SetIndexBufferはグローバル配列をMT4のバッファ管理システムに紐付ける関数です。この関数を呼び出して初めて、OnCalculate内で配列に値を代入するとチャートに反映されるようになります。呼び出し忘れが一番多いミスです。
SetIndexLabelはMT4のデータウィンドウ(Ctrl+Dで表示)に出るラベル名を設定します。必須ではありませんが、チャート上でカーソルを合わせたときにシグナル名が表示されるので、複数バッファがあるインジケーターのデバッグ時に役立ちます。
バッファインデックス(第1引数の0や1)は0始まりで、indicator_buffersの宣言数-1が最大値です。indicator_buffers 2の場合は0と1が有効です。インデックス2以上を指定するとエラーになります。また、すべてのバッファにSetIndexBufferを呼び出すこと。宣言したバッファ数分の登録が揃っていないと意図しない動作になることがあります。
OnInit内のSetIndexStyle・SetIndexArrow・SetIndexBufferは必ずセットで呼び出す。バッファインデックスの番号は3つの関数すべてで一致させること。1つでもずれるとアローが表示されない。
Wingdingsコードで矢印の形状を選ぶ方法
MQL4のアロー表示で使える矢印の形状は、WingdingsフォントのASCIIコード番号で指定します。SetIndexArrowの第2引数に整数を渡すだけで変更できます。よく使うコードを以下にまとめます。
| コード番号 | 形状の特徴 | 用途例 |
|---|---|---|
| 159 | 上向き大矢印(▲) | 強い買いシグナル |
| 160 | 下向き大矢印(▼) | 強い売りシグナル |
| 233 | 上向き中矢印 | 通常の買いシグナル |
| 234 | 下向き中矢印 | 通常の売りシグナル |
| 108 | ドット(●) | 補助マーカー、警告 |
| 110 | 円形(○) | フィルター条件の可視化 |
| 251 | チェックマーク(✓) | 条件確定の目印 |
自分が一番使うのは233/234のペアです。視認性が高く、買い/売りどちらのシグナルか直感的に分かりやすい形状です。大矢印(159/160)はシグナルが少なく重要度を強調したい場合に使いますが、シグナルが頻繁に出るインジケーターに使うとチャートが煩雑になります。ドット(108)は本命シグナルとは別の補助マーカー用バッファとして追加すると、トレンドフィルターの状態を可視化するのに便利です。
全Wingdingsコードの一覧はMQL5.com公式ドキュメントのWingdingsページで確認できます。数百種類が掲載されており、コードを変えて再コンパイルするだけで即確認できるので、気軽に試してみてください。
なお、Wingdingsコードはフォントに依存するため、MT4のバージョンや動作OSによって微妙に見た目が変わることがあります。本番のトレード環境で実際に表示確認してから採用するのが安全です。特にリモートVPS環境と手元の開発環境で見た目が異なるケースに注意してください。
SetIndexStyleでアローの色とサイズを制御する
アロー表示の見た目は色とサイズの2つのパラメータで主に制御します。どちらもSetIndexStyleの引数で設定します。OnInit内で一度設定すれば、インジケーターが動いている間はその設定が維持されます。
色の指定方法は2種類です。定数(clrRed、clrGreen等のWeb Color名)またはRGB値(C’255,0,0’形式)。定数を使うと可読性が高く、初めて読む人にも色が分かりやすくなります。MQL4で使える定数の一覧はMetaEditorのオートコンプリートで確認できます。
// 定数で指定 SetIndexStyle(0, DRAW_ARROW, 0, 2, clrDodgerBlue); // RGB値で指定 SetIndexStyle(0, DRAW_ARROW, 0, 2, C'30,144,255');
サイズは1〜5の整数で指定します。1が最小で5が最大です。チャートの時間足やシグナルの頻度によって適切なサイズが変わります。5分足や1分足のような短い時間足でシグナルが多く出る場合は1〜2、日足や4時間足のような長い時間足で重要なシグナルだけ目立たせる場合は3〜4が見やすいことが多いです。自分の検証では標準の2が最もバランスが良く、ほとんどの場面で使い回せます。
色をランタイムで動的に変えたい場合(例:ボラティリティが高いときは色を変える)は、OnCalculate内でSetIndexStyleを再度呼び出す方法があります。ただし毎ティックのOnCalculate内でSetIndexStyleを呼び出すと処理負荷が上がります。代替として、条件別に別バッファを用意してそれぞれ異なる色を設定しておく方が、パフォーマンス面では有利です。
OnCalculate内のアロー条件記述とエラー対処・応用
OnInit側の設定が整ったら、次はOnCalculate内で条件を記述してアローを実際に表示します。EMPTY_VALUE初期化のパターン、実用的なRSI条件の実装、よくあるエラーの解決法、バックテストとの連携まで順番に解説します。
OnCalculate内でアロー表示条件を記述する
OnCalculate関数はティックが来るたびに呼び出されます。ここでfor文を使って各バーの条件を判定し、アローを表示するバーには座標値を、そうでないバーにはEMPTY_VALUEを設定します。以下が基本的な実装パターンです。
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[])
{
int limit;
if(prev_calculated == 0) {
limit = rates_total - 100 - 1; // 初回は全バーを処理(先頭100本除く)
} else {
limit = rates_total - prev_calculated; // 2回目以降は新バーだけ
}
for(int i = limit; i >= 0; i--) {
// 毎ループの先頭でEMPTY_VALUEを設定(必須)
UpArrow[i] = EMPTY_VALUE;
DownArrow[i] = EMPTY_VALUE;
double closeVal = iClose(NULL, PERIOD_CURRENT, i);
double openVal = iOpen(NULL, PERIOD_CURRENT, i);
if(closeVal - openVal > 0) {
UpArrow[i] = Low[i] - 10 * Point; // 安値の下に上向きアロー
} else if(closeVal - openVal < 0) {
DownArrow[i] = High[i] + 10 * Point; // 高値の上に下向きアロー
}
}
return(rates_total);
}
prev_calculatedを使ったlimit計算は処理効率化のための基本パターンです。初回ロード時(prev_calculated == 0)は全バーを再計算しますが、2回目以降の呼び出し(新しいティックが来るたび)は新しく追加されたバーだけを処理します。この最適化をしないと、バーが数千本あるチャートでは毎ティックの処理が重くなります。
EMPTY_VALUE初期化が必要な理由は、初期化しないと前のループで設定した値が残り続けるためです。具体例として、あるバーでアロー条件が成立して値を代入した後、次のティックで条件が不成立になった場合でも、以前の値が残っているためアローが消えません。毎ループの先頭でEMPTY_VALUEを代入することで確実にリセットされます。
for文の制御変数iは「バーインデックス」で、0が現在の足、1が1本前の確定足、数字が大きいほど過去の足を指します。i >= 0のループが終わる方向に走ることで、過去→現在の順にバーを処理します。MQL4のバーインデックスの仕組みはMQL4のif文とfor文を完全攻略する制御文実装ガイドでも詳しく解説しています。
RSI条件でアローを出すコード実装例
実用的なアロー実装の例として、RSIが売られすぎ水準を下抜けたら買いアロー、買われすぎ水準を上抜けたら売りアローを出すコードを紹介します。このパターンはバックテストのシグナル可視化に直接使える実用性の高いものです。
#property copyright "Copyright 2024, trade-engineer.com"
#property indicator_chart_window
#property indicator_buffers 2
extern int RSI_Period = 14;
extern double OverBought = 70.0;
extern double OverSold = 30.0;
double UpArrow[];
double DownArrow[];
int OnInit()
{
SetIndexStyle(0, DRAW_ARROW, 0, 2, clrDodgerBlue);
SetIndexArrow(0, 233);
SetIndexBuffer(0, UpArrow);
SetIndexLabel(0, "RSI Buy");
SetIndexStyle(1, DRAW_ARROW, 0, 2, clrOrangeRed);
SetIndexArrow(1, 234);
SetIndexBuffer(1, DownArrow);
SetIndexLabel(1, "RSI Sell");
return(INIT_SUCCEEDED);
}
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[])
{
int limit;
if(prev_calculated == 0) {
limit = rates_total - RSI_Period - 2;
} else {
limit = rates_total - prev_calculated;
}
for(int i = limit; i >= 0; i--) {
UpArrow[i] = EMPTY_VALUE;
DownArrow[i] = EMPTY_VALUE;
double rsiVal = iRSI(NULL, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE, i);
if(rsiVal < OverSold) {
UpArrow[i] = Low[i] - 10 * Point;
} else if(rsiVal > OverBought) {
DownArrow[i] = High[i] + 10 * Point;
}
}
return(rates_total);
}
externで宣言したパラメータはMT4のインジケーター設定ダイアログから変更できます。RSI_Period、OverBought、OverSoldをexternにしておくことで、コードを再コンパイルせずにパラメータを変えて検証できます。バックテスト時のパラメータ最適化でも使いやすくなります。
iRSI関数の引数は順番に:通貨ペア(NULLで現在ペア)・時間足・計算期間・価格タイプ(PRICE_CLOSEで終値)・バーインデックスです。他のテクニカル指標も同様の構造で呼び出せます。ストキャスティクスならiStochastic、ボリンジャーバンドならiBandsを使います。
参考として、このRSIアロー(USDJPY 5分足・2023年通年・RSI期間14・閾値70/30)でシグナル後5本以内の方向一致率を確認したところ、買いシグナルで約56%、売りシグナルで約54%でした。単体では微妙な数値ですが、移動平均線の向きでフィルタリングすると方向一致率が5〜8ポイント改善するケースが多いです。アロー可視化→目視確認→フィルター条件の追加というサイクルでロジックを磨いていくのが実践的なアプローチです。アローとバックテストの関係についてはMT4にアロー(矢印)の出し方がわかればバックテストは簡単も参照してください。
アロー位置のPointオフセット調整テクニック
アローをロウソク足の高値や安値ちょうどに配置すると足に重なって見づらくなります。Pointを使ったオフセット計算でアロー位置を適切にずらします。このオフセット量の設定は見た目に直接影響するので、自分のチャート設定に合わせて調整しましょう。
// 上向きアロー:安値より少し下に配置 UpArrow[i] = Low[i] - 10 * Point; // 下向きアロー:高値より少し上に配置 DownArrow[i] = High[i] + 10 * Point;
PointはMQL4の組み込み変数で通貨ペアの最小価格単位を表します。USDJPYの場合、5桁ブローカーでは0.001(1Point = 0.1pips)、4桁ブローカーでは0.01(1Point = 1pip)になります。10 * Pointは5桁ブローカーなら1pip分のオフセットです。
ブローカーの桁数の違いを自動対応したい場合はDigits変数で条件分岐させます。
// 桁数に関わらず約2pip分のオフセットを確保
double offset;
if(Digits == 5 || Digits == 3) {
offset = 20 * Point; // 5桁/3桁ブローカー
} else {
offset = 2 * Point; // 4桁/2桁ブローカー
}
UpArrow[i] = Low[i] - offset;
DownArrow[i] = High[i] + offset;
オフセット量の目安はシグナルの頻度によって変わります。シグナルが多い(M1〜M5)ならオフセットを小さめ(5〜10 Point相当)にするとチャートが煩雑になりにくく、シグナルが少ない(H4〜D1)なら大きめ(20〜30 Point相当)にして視認性を高めます。実際のチャートを見ながら何ポイントが自分にとって最も見やすいか調整してみてください。
5桁ブローカー(XM等)と4桁ブローカーではPointの値が10倍異なる。固定の10 * Pointを使うとブローカーによって見た目が大幅に変わる。Digits変数で条件分岐させるのが安全。
よくあるコンパイルエラーと実行時エラーの解決法
MQL4でアロー実装をする際によく遭遇するエラーのパターンとその解決法をまとめます。同じエラーで何度も詰まらないよう、原因と対処を覚えておきましょう。
エラー1:「Too many indicator buffers used」または「Array out of range」が実行時に発生する。
原因:#property indicator_buffersの宣言数と実際にSetIndexBufferに登録したバッファ数が一致していない。宣言数より多くSetIndexBufferを呼び出すとこのエラーになる。
解決:indicator_buffersの数値を実際のSetIndexBuffer呼び出し数に合わせる。UpArrow/DownArrowの2バッファならindicator_buffers 2。
エラー2:コンパイルは成功するがチャートにアローが全く表示されない。
原因:①SetIndexBuffer呼び出しを忘れている、②SetIndexBufferに渡す配列名がグローバル変数の名前と違う(タイポ)、③OnCalculate内の条件が一度も真にならない。
解決:まずOnInitの3関数の呼び出しを確認。次にOnCalculate内の条件を一時的にif(true)に変えてアローが出るかテスト。
エラー3:アローが表示されるが位置がおかしい(高値/安値と逆になっている等)。
原因:UpArrow/DownArrowへの代入先を入れ違えている。上向きアローにHigh[i]を、下向きアローにLow[i]を誤って設定しているケース。
解決:「上向きアロー=安値より下(Low[i] – offset)」「下向きアロー=高値より上(High[i] + offset)」という対応を確認する。
エラー4:アローが出るが古いバーのアローが消えない。
原因:OnCalculateのforループ先頭でEMPTY_VALUE初期化を書いていない。前のティックで設定した値が残り続ける。
解決:ループの最初の行にUpArrow[i] = EMPTY_VALUE; DownArrow[i] = EMPTY_VALUE;を追加する。
MetaEditorのデバッグ機能として、下部のエラーウィンドウに表示されるメッセージの行番号をクリックするとエラー箇所に直接ジャンプできます。コンパイルエラーはほぼ全てこれで特定できます。カスタムインジケーター開発全体の流れについてはカスタムインジケーターの作成から導入まで基本的な部分を理解するも参考にしてください。
アロー実装のまとめとカスタマイズの方向性
MQL4でアローを表示する実装の要点を整理します。全体の流れは:①#property indicator_buffers Nでバッファ数宣言→②グローバルdouble配列を宣言→③OnInitでSetIndexStyle・SetIndexArrow・SetIndexBufferをバッファ数分呼び出す→④OnCalculateでprev_calculatedを使ってlimitを計算→⑤forループ内でEMPTY_VALUE初期化→条件判定→座標値代入、という5ステップです。この構造を一度体に染み込ませれば、どんな条件のアロー実装も応用できます。
カスタマイズの方向性は主に3つあります。
方向性1・条件の複合化:RSI単体ではなく「RSI売られすぎ かつ 移動平均線が上向き」のような複合条件にする。フィルターを追加するたびシグナル数は減りますが精度は上がります。バックテストで勝率と期待値を数値で確認しながら絞り込んでいきましょう。シグナルが多すぎる場合は条件を厳しく、少なすぎる場合は緩めるという試行錯誤が実践的なアプローチです。
方向性2・マルチタイムフレーム化:OnCalculate内でiClose(NULL, PERIOD_H4, i)のように上位足のデータを参照し、上位足のトレンド方向と一致したシグナルのみアローを出す実装。ノイズを大幅に削減できます。ただし上位足を参照すると再描画の問題が発生しやすいので、PERIOD_CURRENT以外の時間足を参照する際は確定足(バーインデックス1以降)を使うことを徹底してください。
方向性3・EAへの転用:アロー表示で条件の動作確認ができたら、同じロジックをEAのOnTick内に移植します。EAからインジケーターバッファをiCustomで参照してシグナルを取る方法もありますが、直接OnTick内に条件を書く方がレイテンシと処理の明確さの面で有利です。アロー→目視確認→EA化というサイクルで、検証済みのロジックだけを自動売買に組み込んでいきましょう。


コメント