- 1 1. iCustomとは何か(MQL5での役割)
- 2 2. iCustomの基本構文とパラメータ
- 3 3. CopyBuffer()でインジケーターの値を取得する方法
- 4 4. iCustomで複数パラメータのインジケーターを呼ぶ方法
- 5 5. iCustomでインジケーターバッファを取得する方法
- 6 6. iCustomで発生しやすい典型エラーと対処法
- 7 7. iCustom使用時の重要な注意点
- 8 8. iCustomの応用(EA設計での使い方)
- 9 9. FAQ:iCustomに関するよくある質問
1. iCustomとは何か(MQL5での役割)
iCustom は、MQL5でカスタムインジケーターをEAやスクリプトから呼び出すための関数です。
MetaTrader 5(MT5)では、インジケーターは通常チャート上に表示して使用しますが、EA(自動売買プログラム)からも同じインジケーターの計算結果を取得できます。そのために使われるのが iCustom です。
EA開発では次のようなケースがよくあります。
- 自作インジケーターのシグナルをEAで売買判断に使う
- 外部インジケーター(配布・購入したもの)をEAに組み込む
- 複雑なロジックをインジケーター側に分離する
このようなとき、EAからインジケーターを直接参照する仕組みとして iCustom を利用します。
ただし 重要なポイントがあります。
iCustom は インジケーターの値を直接返す関数ではありません。
代わりに インジケーターハンドル(indicator handle) を返します。
インジケーターハンドルとは、
「このインジケーターの計算結果にアクセスするための識別番号」のようなものです。
その後、このハンドルを使って CopyBuffer() 関数で実際のインジケーター値を取得します。
つまり、処理の流れは次のようになります。
iCustomでインジケーターハンドルを取得CopyBuffer()でインジケーターバッファ(計算結果)を取得
EAでインジケーターを使う場合、この 2段階構造を理解することが重要です。
1.1 iCustomの概要
MQL5では、インジケーターを呼び出す方法は大きく2種類あります。
| 方法 | 内容 |
|---|---|
| 標準インジケーター関数 | iMA, iRSI, iMACD など |
| カスタムインジケーター | iCustom |
標準インジケーターは専用関数がありますが、
自作または外部インジケーターは iCustom を使う必要があります。
例として、次のようなファイルがあるとします。
MQL5/Indicators/MyIndicator.ex5
このインジケーターをEAから呼び出す場合、基本的には次のようなコードになります。
int handle;
handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator"
);
このコードは次の意味を持ちます。
- 現在の通貨ペア (
_Symbol) - 現在の時間足 (
PERIOD_CURRENT) MyIndicatorというインジケーター
この条件でインジケーターをロードし、
その ハンドル(識別番号) を handle 変数に保存します。
1.2 iCustomが使われる典型的なケース
実際のEA開発では、iCustom は次のような用途で使用されます。
自作インジケーターをEAで利用する
多くのEA開発者は、
- シグナル生成 → インジケーター
- 売買処理 → EA
という形で役割を分けます。
理由は次の通りです。
- デバッグしやすい
- チャートで可視化できる
- ロジックの再利用が可能
この構造にすると、iCustom でインジケーターの値を取得して売買判断を行うことになります。
外部インジケーターをEAに組み込む
配布されているインジケーターをEAで使うケースも多くあります。
例
- トレンド判定インジケーター
- シグナル矢印インジケーター
- ボラティリティ指標
この場合、インジケーターの バッファ番号(buffer index) を確認して、EAから値を取得します。
複雑なロジックを分離する
EAにすべてのロジックを書くと、コードが非常に長くなります。
そこで次のような設計がよく使われます。
インジケーター
↓
シグナル計算
↓
EA
↓
売買処理
この構造では iCustomがシグナル連携の役割になります。
1.3 iCustomを理解する前に知っておくべきMQL5の基礎
iCustom を正しく使うためには、次の3つの概念を理解する必要があります。
インジケーターハンドル
MQL5では、インジケーターは ハンドル方式で管理されます。
ハンドルとは
インジケーターインスタンスを識別する整数ID
です。
例
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
この handle を使ってデータを取得します。
インジケーターバッファ
インジケーターの計算結果は バッファ(配列) に保存されます。
例
buffer 0 → メインライン
buffer 1 → シグナルライン
EAはこのバッファから値を取得します。
CopyBuffer()
CopyBuffer() は
インジケーターバッファから値を取得する関数です。
基本形
CopyBuffer(handle, buffer_index, start_pos, count, array);
この関数で、インジケーターの最新値や過去データを取得できます。
つまずきやすいポイント(重要)
初心者が最もよく間違える点は次の3つです。
① iCustomは値を返さない
誤解例
double value = iCustom(...);
これは 間違いです。
iCustom は ハンドルを返すだけです。
② 毎Tick iCustom を呼んでしまう
次のコードはよくある失敗です。
void OnTick()
{
int handle = iCustom(...);
}
これは 非常に非効率です。
通常は
OnInit() → ハンドル作成
OnTick() → CopyBuffer
という構造にします。
③ インジケーターのパス間違い
インジケーターは次のフォルダに置く必要があります。
MQL5/Indicators
サブフォルダの場合
Indicators/MyFolder/Indicator
のようにパス指定が必要になります。
2. iCustomの基本構文とパラメータ
iCustom は、MQL5で カスタムインジケーターをロードし、そのハンドル(識別ID)を取得する関数です。
EAやスクリプトからインジケーターの計算結果を利用する場合、まず iCustom を使ってインジケーターを生成します。
基本構文は次の通りです。
int iCustom(
string symbol,
ENUM_TIMEFRAMES period,
string name,
...
);
戻り値は インジケーターハンドル(整数)です。
このハンドルを使い、後から CopyBuffer() でインジケーター値を取得します。
もしインジケーターのロードに失敗した場合、戻り値は INVALID_HANDLE になります。
2.1 iCustomの基本コード例
最もシンプルな例を示します。
int handle;
handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator"
);
このコードは次の意味を持ちます。
| 引数 | 内容 |
|---|---|
_Symbol |
現在の通貨ペア |
PERIOD_CURRENT |
現在の時間足 |
"MyIndicator" |
インジケーターファイル |
ここで指定する "MyIndicator" は
次のファイルを指します。
MQL5/Indicators/MyIndicator.ex5
拡張子 .ex5 は不要です。
2.2 symbol(通貨ペア)
1つ目の引数は 通貨ペア(シンボル)です。
例
_Symbol
または
"EURUSD"
一般的には 現在のチャートと同じシンボルを使うため _Symbol が推奨されます。
例
iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
2.3 period(時間足)
2つ目の引数は 時間足(タイムフレーム)です。
例
PERIOD_M1
PERIOD_M5
PERIOD_H1
PERIOD_D1
現在のチャート時間足を使う場合は
PERIOD_CURRENT
を指定します。
例
iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
別の時間足のインジケーターを参照することも可能です。
例
iCustom(_Symbol, PERIOD_H1, "MyIndicator");
これは H1のインジケーターをEAから参照する意味になります。
2.4 name(インジケーターファイル)
3つ目の引数は インジケーター名(ファイル名)です。
例
"MyIndicator"
これは次のファイルを指します。
MQL5/Indicators/MyIndicator.ex5
サブフォルダにある場合は パス指定します。
例
"MyFolder/MyIndicator"
フォルダ構造
MQL5
└ Indicators
└ MyFolder
└ MyIndicator.ex5
2.5 インジケーターパラメータの指定
多くのカスタムインジケーターは 入力パラメータ(input変数)を持っています。
例(インジケーター側)
input int Period = 14;
input double Multiplier = 2.0;
この場合、iCustom でパラメータを指定できます。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
14,
2.0
);
順序は インジケーターの input 定義順と同じです。
これは非常に重要です。
つまずきやすいポイント(重要)
iCustom のパラメータ指定では、初心者がよくミスをします。
① input順序を間違える
例
インジケーター
input int Period;
input double Multiplier;
EA
iCustom(..., 2.0, 14);
これは 順序が逆なのでエラーや誤動作になります。
② インジケーター名の指定ミス
よくあるミス
"MyIndicator.ex5"
拡張子は 通常不要です。
正しい例
"MyIndicator"
③ インジケーターが存在しない
もしファイルが見つからない場合
INVALID_HANDLE
が返されます。
そのため 必ずチェックすることが重要です。
例
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
if(handle == INVALID_HANDLE)
{
Print("Indicator load failed");
}
④ 毎Tickでインジケーターを生成する
初心者に非常に多いミスです。
誤った例
void OnTick()
{
int handle = iCustom(...);
}
これは 毎Tickインジケーターを作るため極端に重くなります。
正しい構造
OnInit()
↓
iCustom
OnTick()
↓
CopyBuffer
iCustom使用の基本フロー
EAでは通常、次の流れになります。
OnInit()
↓
iCustomでインジケーター生成
↓
OnTick()
↓
CopyBufferで値取得
↓
売買判断
この構造を理解しておくと、EA開発が大幅にスムーズになります。
3. CopyBuffer()でインジケーターの値を取得する方法
iCustom で取得したハンドルだけでは、インジケーターの値はまだ取得できません。
実際の計算結果(ライン値・シグナルなど)を取得するには CopyBuffer() を使用します。
CopyBuffer() は、インジケーターのバッファ(計算結果配列)からデータをコピーする関数です。
EAでは次の流れになります。
iCustom → インジケーターハンドル取得
CopyBuffer → インジケーター値取得
この2段階構造が MQL5でインジケーターを扱う基本パターンです。
3.1 CopyBuffer()の基本構文
基本構文は次の通りです。
int CopyBuffer(
int indicator_handle,
int buffer_num,
int start_pos,
int count,
double buffer[]
);
各パラメータの意味は次の通りです。
| 引数 | 内容 |
|---|---|
| indicator_handle | iCustom で取得したハンドル |
| buffer_num | バッファ番号 |
| start_pos | 取得開始位置 |
| count | 取得するデータ数 |
| buffer[] | 値を格納する配列 |
戻り値は コピーできたデータ数です。
3.2 最も基本的なコード例
インジケーターの 最新値を1つ取得する例です。
int handle;
double value[];
handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
ArraySetAsSeries(value,true);
CopyBuffer(handle,0,0,1,value);
double latest = value[0];
処理内容は次の通りです。
iCustomでインジケーターハンドル取得CopyBufferでバッファ0の値取得value[0]が最新値
3.3 バッファ番号とは
インジケーターは 複数のバッファ(ライン)を持つことがあります。
例
| バッファ番号 | 内容 |
|---|---|
| 0 | メインライン |
| 1 | シグナルライン |
| 2 | ヒストグラム |
例えば MACDタイプのインジケーターでは
buffer 0 → MACD
buffer 1 → signal
buffer 2 → histogram
のようになっている場合があります。
EAから値を取得する場合は、
対象バッファ番号を正しく指定する必要があります。
例
CopyBuffer(handle,0,0,1,value);
3.4 過去データを取得する
start_pos を変更すると 過去データを取得できます。
例
CopyBuffer(handle,0,0,3,value);
取得結果
| index | 内容 |
|---|---|
| value[0] | 最新バー |
| value[1] | 1本前 |
| value[2] | 2本前 |
これは EAでシグナル判定する際によく使われます。
例
if(value[0] > value[1])
{
Print("上昇トレンド");
}
3.5 実践的なEAコード例
EAでは通常、次の構造になります。
int handle;
double buffer[];
int OnInit()
{
handle = iCustom(_Symbol,PERIOD_CURRENT,"MyIndicator");
if(handle == INVALID_HANDLE)
{
Print("Indicator load failed");
return(INIT_FAILED);
}
ArraySetAsSeries(buffer,true);
return(INIT_SUCCEEDED);
}
void OnTick()
{
if(CopyBuffer(handle,0,0,2,buffer) <= 0)
return;
if(buffer[0] > buffer[1])
{
Print("Signal detected");
}
}
このコードのポイント
OnInit()でインジケーター生成OnTick()で値取得- バッファ比較でシグナル判定
これは 多くのEAの基本構造です。
つまずきやすいポイント(重要)
CopyBuffer() は初心者が最もミスしやすい部分です。
① 配列をSeriesにしていない
MQLでは通常、配列は
0 → 古いデータ
ですが、時系列データでは
0 → 最新バー
の方が使いやすいです。
そのため次を必ず設定します。
ArraySetAsSeries(buffer,true);
これを忘れると データ順序が逆になります。
② バッファ番号が間違っている
インジケーターによって
バッファ構造が異なります。
確認方法
- インジケーターソースコード
SetIndexBuffer()の定義
③ データ取得に失敗している
CopyBuffer() が失敗すると 0や負数を返します。
そのため必ずチェックします。
if(CopyBuffer(handle,0,0,1,buffer) <= 0)
return;
④ インジケーター計算がまだ終わっていない
チャートロード直後などは
インジケーターがまだ計算されていない場合があります。
この場合
CopyBuffer → 失敗
になることがあります。
対策
- 次のTickで再取得
BarsCalculated()を使う
CopyBuffer()を使うときの基本設計
EAで最も安定する設計は次です。
OnInit
↓
iCustom
OnTick
↓
CopyBuffer
↓
シグナル判定
↓
注文処理
この構造を守ると パフォーマンスと安定性が高くなります。
4. iCustomで複数パラメータのインジケーターを呼ぶ方法
カスタムインジケーターの多くは、input で定義された設定値を持っています。
たとえば、期間、しきい値、シフト、本数、係数などです。
このようなインジケーターを iCustom で呼ぶ場合は、インジケーター側の input 定義順に、値をそのまま後ろへ並べて渡す必要があります。
ここが iCustom の最重要ポイントの1つです。
iCustom の基本形は次の通りです。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
パラメータ1,
パラメータ2,
パラメータ3
);
つまり、3番目の "MyIndicator" 以降に並ぶ値は、インジケーターの input 変数に順番通り対応します。
4.1 インジケーター側の input とEA側の引数は1対1で対応する
まず、インジケーター側の例を見ます。
input int FastPeriod = 12;
input int SlowPeriod = 26;
input int SignalPeriod = 9;
この場合、EA側では次のように書きます。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
12,
26,
9
);
対応関係は次の通りです。
- 1番目の
12→FastPeriod - 2番目の
26→SlowPeriod - 3番目の
9→SignalPeriod
ここで重要なのは、変数名ではなく順番で対応するという点です。
EA側で「これはFastPeriod用」「これはSignalPeriod用」と明示する仕組みはありません。
そのため、順番を1つでも間違えると、コンパイルが通っても動作が狂うことがあります。
4.2 典型例:複数パラメータを持つインジケーターを呼ぶ
たとえば、次のようなカスタムインジケーターがあるとします。
input int MAPeriod = 20;
input int MAShift = 0;
input ENUM_MA_METHOD MAMethod = MODE_SMA;
input ENUM_APPLIED_PRICE AppliedPrice = PRICE_CLOSE;
この場合の iCustom は次のようになります。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyMAIndicator",
20,
0,
MODE_SMA,
PRICE_CLOSE
);
この書き方なら、インジケーター側の設定と一致します。
実務上は、EA側でも変数化しておく方が安全です。
input int InpMAPeriod = 20;
input int InpMAShift = 0;
input ENUM_MA_METHOD InpMAMethod = MODE_SMA;
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyMAIndicator",
InpMAPeriod,
InpMAShift,
InpMAMethod,
InpAppliedPrice
);
この形にすると、あとで設定を変えやすく、何の値を渡しているかも読みやすくなります。
4.3 パラメータの型も一致させる
順番だけでなく、型も一致させる必要があります。
代表例
intdoubleboolstring- 列挙型(
ENUM_...)
たとえば、インジケーター側が次のようになっているとします。
input int Period = 14;
input double Threshold = 1.5;
input bool UseFilter = true;
EA側では次のように渡します。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
14,
1.5,
true
);
このとき、型が大きくずれると意図しない動作になります。
特に int と double、bool の扱いは雑に書くと読みづらくなります。
4.4 一部だけ変えたい場合も、前のパラメータは省略できない
iCustom では、途中の引数だけ飛ばして後ろだけ指定することは基本的にできません。
たとえば、インジケーターが次の順序だったとします。
input int Period = 14;
input double Multiplier = 2.0;
input bool UseFilter = true;
このとき、UseFilter だけ変えたいからといって、次のようには書けません。
// 誤りの例
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator", true);
この true は3番目の引数ではなく、1番目の input に渡されたものとして解釈される可能性があります。
そのため、後ろの設定だけ変えたい場合でも、前の設定を順番通りすべて書くのが原則です。
正しくは次です。
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
14,
2.0,
false
);
4.5 どのパラメータ順かわからないときの確認方法
外部配布のインジケーターや過去に作ったインジケーターでは、入力順が曖昧になることがあります。
その場合は、次の順で確認します。
- インジケーターのソースコードを開く
inputまたはinput相当の設定群を見る- 上から順番に並べる
- その順番のまま
iCustomに書く
確認対象の例
input int Period = 14;
input double Multiplier = 2.0;
input color LineColor = clrRed;
対応する iCustom
int handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
14,
2.0,
clrRed
);
ソースコードがない場合は、プロパティ画面の並びや配布説明を確認する必要があります。
ただし、環境や配布形式によっては正確に把握しづらいことがあります。
4.6 実務では「EA側の入力」と「iCustom引数」をそろえると管理しやすい
保守性を上げるには、EA側にも同じ意味の input を持たせて、そのまま iCustom に流す構成が有効です。
input int InpPeriod = 14;
input double InpMultiplier = 2.0;
input bool InpUseFilter = true;
int handle;
int OnInit()
{
handle = iCustom(
_Symbol,
PERIOD_CURRENT,
"MyIndicator",
InpPeriod,
InpMultiplier,
InpUseFilter
);
if(handle == INVALID_HANDLE)
{
Print("iCustom failed");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
この構造の利点は次の通りです。
- EAの設定画面から調整できる
- 何を渡しているか明確
- バックテスト時に条件変更しやすい
- インジケーター側とEA側の対応を追いやすい
つまずきやすい点・注意点・よくある失敗
1. 引数の順番を間違える
最も多い失敗です。
特に int が複数続くと、コンパイルは通っても設定が入れ替わります。
2. 型が近いので間違いに気づきにくい
int と bool、int と列挙型は見た目では判別しづらいことがあります。
コード上で変数名を明示した方が安全です。
3. デフォルト値があるから省略できると誤解する
インジケーター側にデフォルト値があっても、iCustom 側で途中の引数だけ省略するのは危険です。
省略可否は書き方や定義に依存するため、初心者は 必要なものを順番通り全部書く 方が安全です。
4. インジケーター更新後に引数順が変わる
インジケーター側で input の順番を入れ替えると、EA側の iCustom も修正が必要です。
ここを放置すると、原因不明のシグナル異常が起きます。
5. サブフォルダ配置時に名前だけ合っていても動かない
引数が正しくても、ファイルパスが違えば INVALID_HANDLE になります。
name と配置場所の両方を確認してください。
5. iCustomでインジケーターバッファを取得する方法
iCustom を使う目的は、最終的には カスタムインジケーターの計算結果をEA側で読むことです。
その実データは インジケーターバッファ に入っており、MQL5では通常 CopyBuffer() で取得します。
ここで重要なのは、「どのバッファに、何の値が入っているか」を正しく把握することです。
iCustom 自体はバッファの意味を教えてくれません。EA側が、対象インジケーターの設計を理解している必要があります。
5.1 バッファ番号の基本
インジケーターは、内部で1本以上のバッファを持ちます。
たとえば次のような構成があります。
- バッファ0:メインライン
- バッファ1:シグナルライン
- バッファ2:矢印シグナル
- バッファ3:補助判定値
EA側では、この 番号指定 でデータを取得します。
CopyBuffer(handle, 0, 0, 1, values);
この例では、バッファ0 の最新値を1個取得します。
つまり、CopyBuffer の第2引数 0 がバッファ番号です。
この番号を誤ると、取得自体は成功しても 違う意味の値を読んでしまう ため、売買ロジックが壊れます。
5.2 バッファ番号の確認方法
最も確実なのは、インジケーターのソースコードを見ることです。
MQL5のカスタムインジケーターでは、通常 SetIndexBuffer() でバッファを割り当てます。
例
double MainBuffer[];
double SignalBuffer[];
int OnInit()
{
SetIndexBuffer(0, MainBuffer, INDICATOR_DATA);
SetIndexBuffer(1, SignalBuffer, INDICATOR_DATA);
return(INIT_SUCCEEDED);
}
この場合の対応は明確です。
0→MainBuffer1→SignalBuffer
EA側では次のように使います。
double main_val[];
double signal_val[];
ArraySetAsSeries(main_val, true);
ArraySetAsSeries(signal_val, true);
CopyBuffer(handle, 0, 0, 1, main_val);
CopyBuffer(handle, 1, 0, 1, signal_val);
ソースコードがない場合は、次を確認します。
- 配布元の説明文
- パラメータ説明
- チャート表示との対応
- テスト出力して値の意味を検証
ただし、ソースが見えない外部インジケーターでは、完全には把握しづらい場合があります。
5.3 最新値を取得する基本形
最もよく使うのは、最新バーの値を1つだけ取る方法です。
double val[];
ArraySetAsSeries(val, true);
if(CopyBuffer(handle, 0, 0, 1, val) <= 0)
{
Print("CopyBuffer failed");
return;
}
double current_value = val[0];
このコードの意味は次の通りです。
handle:対象インジケーター0:バッファ00:最新位置1:1個だけ取得val[0]:最新値
EAで現在シグナルを判定するだけなら、この形で十分なことが多いです。
5.4 過去データを複数本取得する
クロス判定やシグナル変化を見る場合は、過去バーもまとめて取得します。
double vals[];
ArraySetAsSeries(vals, true);
if(CopyBuffer(handle, 0, 0, 3, vals) <= 0)
{
Print("CopyBuffer failed");
return;
}
取得結果のイメージ
vals[0]:最新バーvals[1]:1本前vals[2]:2本前
たとえば、メインラインの上抜け判定なら次のように使えます。
if(vals[0] > vals[1] && vals[1] <= vals[2])
{
Print("上昇方向への変化の可能性");
}
もちろん、実際の売買条件はインジケーター仕様に合わせて設計してください。
5.5 矢印系・シグナル系バッファの注意点
カスタムインジケーターには、値があるバーだけ数値を入れ、それ以外は空値にするものがあります。
特に矢印表示型で多いです。
例
- 買いシグナル時だけ価格を格納
- それ以外のバーは
EMPTY_VALUE
この場合、EA側では単純に「値が0でない」ではなく、空値判定が必要です。
double sig[];
ArraySetAsSeries(sig, true);
if(CopyBuffer(handle, 1, 0, 1, sig) > 0)
{
if(sig[0] != EMPTY_VALUE)
{
Print("シグナルあり");
}
}
ここを誤ると、
シグナルなしバーを誤って有効判定することがあります。
つまずきやすい点・注意点・よくある失敗
1. バッファ番号を推測で決める
最も危険です。
見た目が2本線でも、内部では別バッファが追加されていることがあります。
2. ArraySetAsSeries(true) を忘れる
忘れると配列の向きが逆になり、[0] を最新値として扱えなくなります。
3. CopyBuffer() の戻り値を確認しない
取得失敗時も、そのまま古い配列値を読んで誤判定することがあります。
4. EMPTY_VALUE を考慮しない
矢印系・非連続表示系のインジケーターでは必須です。
5. インジケーターの描画用バッファと判定用バッファを混同する
見た目の線と、内部ロジック用数値が別バッファになっていることがあります。
6. iCustomで発生しやすい典型エラーと対処法
iCustom は構文自体は短いですが、実際の開発では 「ハンドル取得失敗」「データ未計算」「バッファ取得失敗」 の3系統で止まりやすいです。
また、MQL5では iCustom の後に BarsCalculated で計算完了を確認し、CopyBuffer で値取得し、不要になったハンドルは IndicatorRelease で解放する、という流れが基本です。これはMQL5のインジケーター利用全体の標準的な扱い方です。
エラー対処では、まず次の順番で切り分けると効率が良いです。
iCustomの戻り値がINVALID_HANDLEか確認する- パス・ファイル名・引数順が正しいか確認する
BarsCalculated(handle)で計算が完了しているか確認するCopyBuffer()の戻り値を確認する- ログに出ているエラー内容を読む
この順番で見ると、原因の切り分けがかなり速くなります。
6.1 INVALID_HANDLE になる
最も多い失敗です。
iCustom が失敗すると、ハンドルは有効な整数ではなく INVALID_HANDLE になります。MQL5の書籍でも、iCustom で複数ハンドルを作成した直後に INVALID_HANDLE を判定し、失敗時は INIT_FAILED を返す流れが示されています。
基本形は次です。
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
if(handle == INVALID_HANDLE)
{
Print("iCustom failed");
return(INIT_FAILED);
}
主な原因は次の通りです。
- インジケーターファイル名が違う
- サブフォルダを含むパス指定が違う
- 渡しているパラメータ数が合っていない
- パラメータ順が
input定義順と一致していない - リソース化したインジケーターの参照方法が不正
特に 引数順ミス は厄介です。
MQL5では、iCustom に渡す追加引数はインジケーターの input 定義順に対応します。途中の不要な引数を勝手に飛ばすと、後ろの値が別パラメータとして解釈されます。書籍でも、途中の引数を省略すると後続のハンドルが別用途のパラメータとして解釈され、意図と異なる動作になる例が示されています。
6.2 cannot load custom indicator が出る
これは ロード対象の場所や指定名が噛み合っていない ときに出やすいエラーです。
MQL5の書籍では、リソースとして埋め込まれたインジケーターを誤った指定で呼び出した結果、cannot load custom indicator ... [4802] が出る例が示されています。
典型原因は次です。
- ファイル名は合っているが、サブフォルダ指定が間違っている
- インジケーターを移動したのに
nameを直していない - リソース埋め込み時の参照ルールを誤解している
通常のケースでは、まず次を確認します。
MQL5/Indicators配下にあるか- サブフォルダ込みで
"FolderName/MyIndicator"の形で指定しているか - 拡張子を含めない通常指定で呼んでいるか
- EA側とインジケーター側でビルド済みファイルが存在するか
実務では、ファイル移動後やフォルダ整理後にこのエラーが増えます。
名前ではなく相対配置がズレている ケースが多いです。
6.3 CopyBuffer() が失敗する
iCustom が成功しても、すぐに CopyBuffer() が成功するとは限りません。
MQL5では、インジケーターの計算バー数を BarsCalculated(handle) で確認してから処理を進める例が示されています。対象インジケーターの計算がまだ終わっていない場合、処理を保留して前回計算値を返す構造です。
つまり、次の状況が起こります。
- ハンドル取得は成功
- しかし計算はまだ終わっていない
- その時点で
CopyBufferを呼ぶ - 値取得に失敗する
対策は次です。
if(BarsCalculated(handle) <= 0)
return;
または、より実務的には次のように書きます。
double vals[];
ArraySetAsSeries(vals, true);
if(BarsCalculated(handle) < 2)
return;
if(CopyBuffer(handle, 0, 0, 2, vals) <= 0)
return;
主な原因は次です。
- インジケーター計算が未完了
- バッファ番号の指定ミス
- 取得本数
countが不適切 - 配列方向の扱いミス
- 対象時間足やシンボルの履歴不足
特に、初回ロード直後 と マルチタイムフレーム では失敗率が上がりやすいです。
6.4 array out of range が出る
これは iCustom 自体というより、CopyBuffer の結果を格納した配列の扱いミスで起こることが多いです。
たとえば1本しか取得していないのに buffer[1] を読む、取得失敗時に配列を読んでしまう、などです。
誤り例
double vals[];
ArraySetAsSeries(vals, true);
CopyBuffer(handle, 0, 0, 1, vals);
double prev = vals[1];
このコードは、1個しか取得していないのに vals[1] を読んでいます。
そのため範囲外アクセスになります。
正しくは次です。
double vals[];
ArraySetAsSeries(vals, true);
if(CopyBuffer(handle, 0, 0, 2, vals) <= 0)
return;
double current = vals[0];
double previous = vals[1];
対策の基本は次の3点です。
CopyBuffer()の戻り値を必ず確認する- 読む添字に見合う本数を取得する
- 配列方向を
ArraySetAsSeries(..., true)で明示する
6.5 ハンドルを毎Tick作って重くなる
エラー表示が出ないので見落としやすいですが、実務上はかなり多い失敗です。
OnTick() の中で毎回 iCustom を呼ぶと、不要なインスタンス生成が増え、パフォーマンス低下や挙動不安定の原因になります。
誤り例
void OnTick()
{
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
}
基本は次です。
OnInit()でハンドル生成OnTick()でCopyBuffer()のみ実行- 不要になったら
IndicatorRelease()で解放
MQL5の書籍でも、インジケーターハンドル管理の章として IndicatorRelease が独立して扱われています。
終了時の例
void OnDeinit(const int reason)
{
if(handle != INVALID_HANDLE)
IndicatorRelease(handle);
}
つまずきやすい点・注意点・よくある失敗
1. INVALID_HANDLE を確認せず先へ進む
これをやると、以降の CopyBuffer 失敗原因が見えにくくなります。
2. BarsCalculated() を見ずに値取得する
特に初回起動直後は危険です。
3. CopyBuffer() の戻り値を無視する
失敗時に古い配列値を使うと、バグが再現しにくくなります。
4. 引数順のズレを軽視する
コンパイルが通ってもロジックが壊れます。これが一番厄介です。
5. インジケーター名と配置場所を同時に確認していない
ファイル名だけ見ていても解決しないことがあります。
7. iCustom使用時の重要な注意点
iCustom は「動けば終わり」の関数ではありません。
実運用のEAで安定して使うには、生成タイミング・計算完了確認・ハンドル解放・時間足/通貨ペアの整合性を意識する必要があります。
MQL5では、iCustom はインジケーターのハンドルを返し、その後に BarsCalculated() で計算済みバー数を確認し、CopyBuffer() で値取得し、不要になったら IndicatorRelease() で解放する流れが基本です。
この前提を外すと、バックテストでは動いても本番で不安定になることがあります。
7.1 iCustomは OnInit() で生成する
最重要ポイントは、ハンドル生成を毎Tickで行わないことです。
誤り例
void OnTick()
{
int handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
}
この書き方だと、ティックのたびにインジケーター生成処理が走ります。
その結果、次の問題が起こりやすくなります。
- 無駄な負荷が増える
- デバッグしにくくなる
- 複数ハンドル管理が破綻しやすい
- 戻り値チェック漏れで不安定になる
基本形は次です。
int handle = INVALID_HANDLE;
int OnInit()
{
handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
if(handle == INVALID_HANDLE)
{
Print("iCustom failed");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
そして OnTick() では 値取得だけを行います。
void OnTick()
{
double vals[];
ArraySetAsSeries(vals, true);
if(CopyBuffer(handle, 0, 0, 1, vals) <= 0)
return;
double latest = vals[0];
}
この構造が最も安定します。
7.2 計算完了前に CopyBuffer() しない
iCustom 成功直後でも、対象インジケーターの計算が終わっていない場合があります。
MQL5の書籍でも、BarsCalculated(handle) を使って計算済みバー数を確認してから処理する例が示されています。
安全な形は次です。
if(BarsCalculated(handle) <= 0)
return;
過去2本を比較したいなら、最低限その本数を満たしているか見ます。
double vals[];
ArraySetAsSeries(vals, true);
if(BarsCalculated(handle) < 2)
return;
if(CopyBuffer(handle, 0, 0, 2, vals) <= 0)
return;
特に次の状況では注意が必要です。
- EA起動直後
- 通貨ペアや時間足を切り替えた直後
- マルチタイムフレーム参照時
- 履歴データがまだ十分に読み込まれていないとき
7.3 ハンドルは不要時に IndicatorRelease() で解放する
MQL5では、不要になったインジケーターハンドルは IndicatorRelease() で解放できます。
書籍でも、インジケーター管理機能の一部として IndicatorRelease が扱われています。
典型例は OnDeinit() です。
void OnDeinit(const int reason)
{
if(handle != INVALID_HANDLE)
{
IndicatorRelease(handle);
handle = INVALID_HANDLE;
}
}
これを入れておく利点は次の通りです。
- ハンドル再利用事故を避けやすい
- 終了処理が明確になる
- コードの保守性が上がる
なお、短命なスクリプトや単純検証では影響が小さく見えることもありますが、
EAや複数インジケーター運用では 解放前提で設計した方が安全です。
7.4 マルチタイムフレーム・別シンボルは特に慎重に扱う
iCustom は現在チャートだけでなく、別シンボル・別時間足でも呼べます。
これは便利ですが、同時に失敗要因も増えます。
例
int handle_h1 = iCustom(_Symbol, PERIOD_H1, "MyIndicator");
int handle_m15 = iCustom(_Symbol, PERIOD_M15, "MyIndicator");
このときの注意点
CopyBuffer()の値が「現在チャート」と同じ意味になるとは限らない- バーの確定タイミングが時間足ごとに異なる
- 参照先履歴が不足すると失敗しやすい
- 同じロジックでもM1とH1では更新タイミングが全く違う
初心者が最初にやるなら、まずは
_SymbolPERIOD_CURRENT
で固定し、単一条件で安定動作を確認してから拡張する方が安全です。
7.5 外部インジケーターは「見た目」と「内部バッファ」が一致しないことがある
これは実務でかなり重要です。
チャート上で
- 線が見える
- 矢印が出る
- 色が変わる
としても、EAが読むべきバッファが その見た目そのものとは限りません。
たとえば、次のような設計があります。
- 描画用バッファ
- 判定用バッファ
- 補助計算用バッファ
そのため、外部インジケーターを使う場合は次を確認します。
SetIndexBuffer()の対応EMPTY_VALUEの有無- シグナル発生時だけ値が入る仕様か
- 常時ラインが出る仕様か
ここを曖昧にしたままEAへ組み込むと、
「見た目では合っているのに、売買がずれる」 という典型事故になります。
7.6 デバッグ前提でログを入れる
iCustom 周りは、失敗時に無言で終わる設計だと原因追跡が難しくなります。
そのため、最初はログを十分に入れた方が良いです。
例
int copied = CopyBuffer(handle, 0, 0, 2, vals);
if(copied <= 0)
{
Print("CopyBuffer failed");
return;
}
Print("val0=", vals[0], " val1=", vals[1]);
確認すべき項目
handleが有効かBarsCalculated()が十分かCopyBuffer()戻り値はいくつかvals[0]がEMPTY_VALUEではないか- 想定時間足のバー更新と一致しているか
最初にここまで見ておくと、後からの修正コストがかなり下がります。
つまずきやすい点・注意点・よくある失敗
1. OnTick() で毎回 iCustom を呼ぶ
最も多い設計ミスです。
重くなるだけでなく、状態管理も崩れます。
2. BarsCalculated() を見ずに CopyBuffer() する
起動直後や別時間足参照で失敗しやすいです。
3. ハンドル解放を考えない
単発では目立たなくても、EAでは管理が雑になります。
4. チャート表示と内部バッファを同一視する
外部インジケーターで特に危険です。
5. 別時間足データを「現在足の感覚」で扱う
更新タイミングのズレで誤判定しやすくなります。
8. iCustomの応用(EA設計での使い方)
iCustom は単にインジケーター値を読む関数ではなく、EAの設計を分離し、保守しやすくするための接続点として使うと効果が大きくなります。
実務では、売買ロジックをすべて OnTick() に直接書くよりも、「シグナル計算」と「注文処理」を分ける構造の方が再現性・検証性・改修効率で有利です。
基本設計は次の形です。
- カスタムインジケーター:シグナルや判定値を計算する
- EA:
iCustom+CopyBuffer()で値を受け取り、発注・決済・資金管理を行う
この分離により、チャート上でシグナルを可視化しながら、EA側では同じ値を使って自動売買できます。
つまり、目視検証と自動売買ロジックを一致させやすいのが大きな利点です。
8.1 シグナルインジケーターをEAにつなぐ
最も基本的な使い方は、売買サイン専用インジケーターをEAへ接続する方法です。
たとえば、次のような設計です。
- バッファ0:買いシグナル
- バッファ1:売りシグナル
EA側では、買いシグナル用バッファが EMPTY_VALUE でないときだけエントリー判定します。
double buy_sig[];
ArraySetAsSeries(buy_sig, true);
if(CopyBuffer(handle, 0, 0, 1, buy_sig) > 0)
{
if(buy_sig[0] != EMPTY_VALUE)
{
// 買いエントリー候補
}
}
この構造の利点は、売買条件の変更をインジケーター側に集約できることです。
8.2 フィルター専用インジケーターとして使う
iCustom はエントリーそのものだけでなく、フィルター条件にも向いています。
例
- トレンド方向だけ判定するインジケーター
- ボラティリティが一定以上か判定するインジケーター
- 取引禁止時間帯や異常値を判定する補助インジケーター
この場合、EA設計は次のようになります。
メイン条件成立
↓
iCustomの補助バッファ確認
↓
条件通過なら発注
これにより、EA本体を肥大化させずに機能追加しやすくなります。
8.3 複数インジケーターを組み合わせる
実務では、1本のインジケーターだけで売買判断を完結させるよりも、複数条件の合成が一般的です。
例
- インジケーターA:トレンド判定
- インジケーターB:押し目タイミング
- インジケーターC:決済補助
EA側では、それぞれ別ハンドルで管理します。
int trend_handle;
int entry_handle;
int exit_handle;
このとき重要なのは、各インジケーターの役割を明確に分けることです。
何でも1本に詰め込むと、後から検証や修正が難しくなります。
8.4 iCustomを使うEA設計の実務原則
安定しやすい設計原則は次の通りです。
OnInit()でハンドル作成OnTick()では値取得と判定だけ行う- 発注条件と資金管理条件は分離する
- シグナル判定用バッファと描画用バッファを混同しない
- インジケーターの役割を「エントリー」「フィルター」「決済」に分ける
この構造にすると、後で
- シグナルだけ差し替える
- 決済ロジックだけ変える
- フィルター条件だけ追加する
といった改修がしやすくなります。
8.5 よくある失敗
1. シグナル計算と発注処理を同じ場所に書く
コードが肥大化し、検証しにくくなります。
2. インジケーター1本に役割を詰め込みすぎる
どの条件でエントリーしたのか追いにくくなります。
3. 見た目の矢印だけを信用する
EAが読むバッファと、チャート描画の見た目が一致しない場合があります。
4. バックテストでの見た目確認を省く
シグナル値と実際の発注タイミングがずれていても見逃しやすくなります。
9. FAQ:iCustomに関するよくある質問
9.1 iCustomは何を返しますか?
iCustom は **インジケーターハンドル(整数ID)**を返します。
これはインジケーターの計算結果を参照するための識別番号です。
そのため、iCustom の戻り値から直接インジケーター値を取得することはできません。
実際の値は CopyBuffer() を使って取得します。
基本的な流れは次の通りです。
iCustom → ハンドル取得
CopyBuffer → バッファから値取得
9.2 iCustomはどこで呼ぶのが正しいですか?
通常は OnInit() で呼びます。
理由
- インジケーターを毎Tick生成すると負荷が増える
- ハンドル管理が難しくなる
- バックテストと実運用で挙動が変わりやすい
基本構造
OnInit
↓
iCustomでハンドル生成
OnTick
↓
CopyBufferで値取得
9.3 CopyBuffer()が失敗する原因は何ですか?
代表的な原因は次の通りです。
- インジケーター計算がまだ終わっていない
- バッファ番号の指定ミス
- 配列サイズが不足している
ArraySetAsSeries()を設定していない- 履歴データが不足している
対策として、次のチェックを入れるのが一般的です。
if(BarsCalculated(handle) <= 0)
return;
9.4 バッファ番号はどうやって調べますか?
最も確実なのは インジケーターのソースコードを確認する方法です。
MQL5では通常、次の関数でバッファが割り当てられています。
SetIndexBuffer(index, buffer);
ここで指定された index が バッファ番号です。
もしソースコードがない場合は
- 配布元の説明
- テスト出力
- デバッグログ
などで確認する必要があります。
9.5 EMPTY_VALUEとは何ですか?
EMPTY_VALUE は 「値が存在しない」ことを示す特別な値です。
多くのシグナル系インジケーターでは
- シグナルがあるバー → 値あり
- それ以外のバー →
EMPTY_VALUE
という構造になっています。
EAでは次のように判定します。
if(buffer[0] != EMPTY_VALUE)
{
// シグナルあり
}
9.6 iCustomで別時間足のインジケーターは使えますか?
はい、使用できます。
例
iCustom(_Symbol, PERIOD_H1, "MyIndicator");
ただし注意点があります。
- バー更新タイミングが現在チャートと異なる
- 履歴不足で
CopyBufferが失敗する場合がある - マルチタイムフレーム処理の設計が必要
初心者はまず PERIOD_CURRENT で動作確認してから拡張するのが安全です。
9.7 外部インジケーターでも iCustom は使えますか?
使えます。
ただし次の条件があります。
MQL5/Indicatorsフォルダに配置されているinputパラメータ順を正しく指定している- バッファ番号を把握している
また、ソースコードがない場合は 内部バッファ構造を確認しにくいため、テストが重要になります。
9.8 iCustomが INVALID_HANDLE を返すのはなぜですか?
主な原因は次の通りです。
- インジケーター名が違う
- サブフォルダパス指定が間違っている
inputパラメータ数が一致していない- インジケーターがコンパイルされていない
- リソース指定が誤っている
この場合は、まず次のログチェックを行います。
if(handle == INVALID_HANDLE)
{
Print("Indicator load failed");
}