MQL5で`iMA()`や`iRSI()`を使うとき、「これはハンドルを返すだけの関数」
と説明を受けて、そういうものだと覚えて使っている人は多いのではないでしょうか。
実はこの`iMA()`・`iRSI()`といった関数は、もっと汎用的な`IndicatorCreate()`という関数を、
あらかじめ決まった種類のインジケーター専用に簡略化したラッパー(下請け)にすぎません。
`IndicatorCreate()`を直接使えるようになると、
`iMA()`のような標準インジケーター専用の関数が用意されていない場面、
つまり**自分で作ったカスタムインジケーター(.ex5ファイル)のハンドルを、
他のEAやインジケーターから取得したいとき**にも対応できます。
MQL4で言う`iCustom()`に相当する処理です。
この記事では、`IndicatorCreate()`で標準インジケーターと自作インジケーターの両方のハンドルを取得する方法と、
対になる`IndicatorRelease()`を呼び忘れた場合に実際どういう不具合が起きるのかを、
動くコードとあわせて解説します。
IndicatorCreate関数の書式
int IndicatorCreate( string symbol, //通貨ペア(_Symbolで現在のチャート) ENUM_TIMEFRAMES period, //時間足(PERIOD_CURRENTで現在の時間足) ENUM_INDICATOR indicator_type, //インジケーターの種類 int parameters_cnt=0, //パラメーター配列の要素数 const MqlParam& parameters_array[]=NULL //パラメーターの配列 );
戻り値はハンドル(int)で、失敗すると`INVALID_HANDLE`(-1)が返ります。
`iMA()`・`iRSI()`・`iBands()`は、この`IndicatorCreate()`の`indicator_type`にそれぞれ`IND_MA`・`IND_RSI`・`IND_BANDS`をあらかじめ指定し、
パラメーターの並べ方まで隠蔽して使いやすくした専用関数、という位置づけです。
parameters_array引数: MqlParam構造体の配列で渡す
`IndicatorCreate()`はインジケーターの種類ごとにパラメーターの数も型もバラバラなため、
`MqlParam`という共通の構造体の配列にまとめて渡す設計になっています。
struct MqlParam
{
ENUM_DATATYPE type; //値の型(TYPE_INT/TYPE_DOUBLE/TYPE_STRING等)
long integer_value; //整数系の値はここに入れる
double double_value; //double型の値はここに入れる
string string_value; //string型の値、または自作インジケーターのファイル名
};
標準インジケーターの場合、パラメーターの並び順は種類ごとに公式リファレンスで決まっています。
たとえば`IND_MA`(移動平均線)の場合は次の順番です。
- 0番目: 期間(int)
- 1番目: 水平シフト(int)
- 2番目: 平均化の種類、ENUM_MA_METHOD(int)
- 3番目: 適用価格、ENUM_APPLIED_PRICE(int)
MqlParam pars[4]; pars[0].type = TYPE_INT; pars[0].integer_value = 20; //期間 pars[1].type = TYPE_INT; pars[1].integer_value = 0; //水平シフト pars[2].type = TYPE_INT; pars[2].integer_value = MODE_SMA; //平均化の種類 pars[3].type = TYPE_INT; pars[3].integer_value = PRICE_CLOSE;//適用価格 int maHandle = IndicatorCreate(_Symbol, PERIOD_CURRENT, IND_MA, 4, pars);
`iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE)`と書いた場合、
内部的にはこれと同じ処理が行われている、とイメージすると理解しやすくなります。
IND_CUSTOM: 自作インジケーターのハンドルを取得する
自作(カスタム)インジケーターのハンドルが欲しい場合は、`indicator_type`に`IND_CUSTOM`を指定します。
この場合、`MqlParam`配列の**0番目には必ず「呼び出したい自作インジケーターのファイル名(拡張子.ex5は付けない)」
を文字列型で入れる**という決まりがあります。
1番目以降には、その自作インジケーターが持つinput変数を、
定義した順番どおりに詰めていきます。
//例: 「MyArrowIndicator.ex5」(input int Period=14を持つ想定)を呼び出す場合の書き方
//ファイル名・パラメーターの内容は、自分が作ったインジケーターに合わせて書き換える
MqlParam customPars[2];
customPars[0].type = TYPE_STRING;
customPars[0].string_value = "MyArrowIndicator"; //拡張子(.ex5)は書かない
customPars[1].type = TYPE_INT;
customPars[1].integer_value = 14; //自作インジケーターの1番目のinput変数に対応する値
int customHandle = IndicatorCreate(_Symbol, PERIOD_CURRENT, IND_CUSTOM, 2, customPars);
if(customHandle == INVALID_HANDLE)
Print("自作インジケーターのハンドル取得に失敗しました。エラーコード=", GetLastError());
これがMQL4の`iCustom(Symbol(), 0, “MyArrowIndicator”, 14, 0, 0)`に相当する処理です。
MQL4では自作インジケーターの名前とパラメーターを`iCustom()`の引数に直接並べるだけでしたが、
MQL5では`MqlParam`構造体を経由するぶん一手間増えている、
と考えると整理しやすくなります。
IndicatorRelease関数: 呼び忘れると何が起きるか
bool IndicatorRelease( int indicator_handle //解放するインジケーターハンドル );
戻り値は解放の成否(bool)です。
`IndicatorCreate()`で取得したハンドルは、
インジケーターやEAが終了する`OnDeinit()`の中で必ず`IndicatorRelease()`を呼んで解放するのが基本です。
呼び忘れても、多くの場合は1回動かしただけではエラーになりません。
問題が表面化するのは、**`OnInit()`が繰り返し呼ばれる場面**です。
チャート上でインジケーターのパラメーターを何度も変更して調整したり、
時間足を切り替えたりするたびに`OnInit()`は再実行されますが、
そのたびに新しいハンドルが`IndicatorCreate()`で作られ、
古いハンドルを解放していなければハンドルは増え続けます。
ターミナルが保持できるインジケーターハンドルの数には上限があり、
上限に達すると、それ以降の`IndicatorCreate()`(`iMA()`等のラッパー関数を含む)が`INVALID_HANDLE`を返すようになります。
パラメーター調整を繰り返しているうちに、突然`CopyBuffer()`が失敗し始めたら、
この状態を疑ってください。
実際に動くコード全体(IndicatorCreateでIND_MAを直接呼び出す)
`iMA()`ではなく`IndicatorCreate(IND_MA)`を直接使って移動平均線の値を取得し、
チャート左上にコメント表示する、実際にMetaEditorでコンパイルできるインジケーターです。
`OnDeinit()`でハンドルを確実に解放している点も確認してください。
//+------------------------------------------------------------------+
//| IndicatorCreateDemo.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
input int MAPeriod = 20;//移動平均線の期間
//このインジケーターはチャートに線を描かず、値をComment()で表示するだけなので、
//バッファ・プロットともに0個で問題ありません(線を描く場合は1以上が必要です)。
int maHandle = INVALID_HANDLE;//IndicatorCreateで作るハンドル
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//iMA()の内部でもこれと同じ処理が行われている(iMA()はIndicatorCreateのラッパー)
MqlParam pars[4];
pars[0].type = TYPE_INT; pars[0].integer_value = MAPeriod; //期間
pars[1].type = TYPE_INT; pars[1].integer_value = 0; //水平シフト
pars[2].type = TYPE_INT; pars[2].integer_value = MODE_SMA; //平均化の種類
pars[3].type = TYPE_INT; pars[3].integer_value = PRICE_CLOSE; //適用価格
maHandle = IndicatorCreate(_Symbol, PERIOD_CURRENT, IND_MA, 4, pars);
if(maHandle == INVALID_HANDLE)
{
Print("IndicatorCreate(IND_MA)に失敗しました。エラーコード=", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//ハンドルを解放する。呼び忘れると、パラメーター変更のたびにOnInit()が
//再実行される際に古いハンドルが解放されないまま残り続ける。
if(maHandle != INVALID_HANDLE)
IndicatorRelease(maHandle);
Comment("");
}
//+------------------------------------------------------------------+
//| 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[])
{
double maValue[];
ArraySetAsSeries(maValue, true);
if(CopyBuffer(maHandle, 0, 0, 1, maValue) <= 0)
{
Print("CopyBufferに失敗しました。エラーコード=", GetLastError());
return(0);
}
Comment("IndicatorCreate(IND_MA)で取得した", MAPeriod, "期間移動平均線: ", maValue[0]);
return(rates_total);
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MetaTrader 5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「インジケーター」を選び、名前を「IndicatorCreateDemo」にして作成する(中身は空のテンプレートで構いません)
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- MT5の「ナビゲーター」パネルからインジケーターを探し、チャートへドラッグ&ドロップする
- チャート左上に「IndicatorCreate(IND_MA)で取得した20期間移動平均線: ○○」と表示されれば成功です
- 自作インジケーターで試す場合は、上のコードの`OnInit()`内を「IND_CUSTOM: 自作インジケーターのハンドルを取得する」節のコードに置き換え、ファイル名とパラメーターを自分のインジケーターに合わせてください
コピペしてコンパイルが通らない・ハンドル取得に失敗するときに確認すること
- `parameters_cnt`(第4引数)と、実際に用意した`MqlParam`配列の要素数が一致しているか(足りないとその種類のインジケーターの規定パラメーター数を満たせずエラーになる)
- IND_CUSTOMを使う場合、0番目のファイル名に`.ex5`拡張子を付けてしまっていないか(拡張子なしが正しい書き方)
- IND_CUSTOMで指定した自作インジケーターの.ex5ファイルが、実行環境の「MQL5/Indicators」フォルダに実際にコンパイル・配置されているか(存在しない・コンパイルエラーが残っているファイルを指定すると`INVALID_HANDLE`が返る)
- `MqlParam`の`type`フィールドと、実際に値を入れたフィールド(`integer_value`/`double_value`/`string_value`)が一致しているか(例えばdouble型のパラメーターのつもりで`integer_value`に値を入れると、小数部分が失われるなど意図と違う値になる)
- パラメーター調整を繰り返しているうちに突然ハンドル取得が失敗し始めた場合、`IndicatorRelease()`の呼び忘れによるハンドルリークが蓄積している可能性がある(EAやインジケーターをチャートから一度削除して再度適用すると回復することが多い)
まとめ
`IndicatorCreate()`は、`iMA()`や`iRSI()`といった専用関数の元になっている汎用関数で、
`indicator_type`に`IND_CUSTOM`を指定し`MqlParam`配列でファイル名とパラメーターを渡せば、
自作インジケーターのハンドルもMQL4の`iCustom()`と同じ感覚で取得できます。
対になる`IndicatorRelease()`は、1回動かすだけなら省略しても表面化しにくいバグですが、
パラメーター調整や時間足切り替えで`OnInit()`が繰り返される実務の場面では、
呼び忘れがハンドルリークとして確実に蓄積します。
`OnDeinit()`での解放を、`CopyBuffer()`とセットの必須作業として習慣にしてください。
コメント