- 1 1. MQL5 CopyBufferとは何か
- 2 2. CopyBufferの基本構文と引数
- 3 3. CopyBufferの基本的な使い方(シンプル例)
- 4 4. CopyBufferの実践例(RSI・MACD)
- 5 4.3 iMACDの値を取得する例(複数バッファ)
- 6 5. CopyBufferのエラー原因と対処法
- 7 6. CopyBufferの最適化と実務テクニック
- 8 7. CopyBufferと関連関数の関係
- 9 8. FAQ(よくある質問と解決方針)
- 10 9. まとめ(実務で迷わないための要点整理)
1. MQL5 CopyBufferとは何か
1.1 CopyBufferの基本概念
MQL5のCopyBufferは、インジケーターが内部で計算している値(バッファ)を取得するための関数です。
ここでいう「バッファ」とは、インジケーターが各バーごとに保持している数値配列のことを指します。
例えば以下のような指標は、すべて内部にバッファを持っています。
- 移動平均(iMA)
- RSI(iRSI)
- MACD(iMACD)
これらの値は、チャート上に表示されるだけでなく、プログラムから取得して売買ロジックに利用することができます。そのために使うのがCopyBufferです。
重要なポイントは以下です。
- MQL5ではインジケーターはhandle(ハンドル:識別子)で管理される
- CopyBufferは、そのhandleから数値データをコピーする関数
- 戻り値は「取得できたデータ数」
1.2 CopyBufferが必要になる理由
EA(自動売買プログラム)では、価格データだけでなくテクニカル指標を使った判断がほぼ必須になります。
例えば以下のようなロジックです。
- 移動平均クロスでエントリー
- RSIが70以上なら売り、30以下なら買い
- MACDのシグナル交差で判断
これらを実装するためには、インジケーターの値を取得する処理が必要になります。
基本的な流れは以下です。
// 1. インジケーターのhandleを取得
int handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
// 2. 配列を用意
double buffer[];
// 3. データを取得
CopyBuffer(handle, 0, 0, 3, buffer);
このように、
「インジケーター生成 → CopyBufferで値取得 → 条件判定」
という構造が、MQL5のEA開発の基本パターンになります。
1.3 対象読者のつまずきポイント
CopyBufferは非常に重要な関数ですが、初心者が最もつまずきやすいポイントでもあります。
特に以下の点で混乱が発生します。
■ よくある混乱①:handleの意味が分からない
- MQL4では直接値を取得できたが、MQL5ではhandle経由になる
- handleは「インジケーターのインスタンスID」と考えると理解しやすい
■ よくある混乱②:buffer番号の意味が分からない
- インジケーターによっては複数のラインを持つ
- 例:MACD
- 0 → メインライン
- 1 → シグナルライン
■ よくある混乱③:値が取得できない
主な原因は以下です。
- handleが正しく生成されていない
- CopyBufferの戻り値を確認していない
- インジケーターの計算がまだ完了していない(特にOnInit直後)
- 配列サイズが不足している
■ よくある失敗(実務で多い)
- CopyBufferを毎Tick無駄に呼びすぎる(パフォーマンス低下)
- ArraySetAsSeriesを設定していない(インデックス逆転バグ)
- start_posの意味を誤解している
CopyBufferは単なる関数ではなく、MQL5における「データ取得レイヤーの中核」です。
ここを正しく理解すると、EA開発の実装速度と安定性が大きく向上します。
2. CopyBufferの基本構文と引数
2.1 基本構文
CopyBufferは以下の形式で使用します。
int CopyBuffer(
int indicator_handle,
int buffer_num,
int start_pos,
int count,
double buffer[]
);
戻り値は「取得できたデータ数」です。
正常に取得できれば count と同じ値が返ります。
2.2 各引数の詳細解説
■ indicator_handle(インジケーターハンドル)
- iMA / iRSI / iCustomなどで取得した値
- インジケーターを識別するID
int handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
注意点:
INVALID_HANDLEの場合はエラー- OnInitで作るのが基本
■ buffer_num(バッファ番号)
- どのラインの値を取得するか
例:
| インジケーター | buffer_num |
|---|---|
| iMA | 0 |
| iRSI | 0 |
| iMACD | 0(メイン) / 1(シグナル) |
例:
CopyBuffer(handle, 0, 0, 3, buffer);
注意点:
- インジケーターごとに意味が異なる
- 誤ると「値がズレる」ので要注意
■ start_pos(取得開始位置)
- どのバーから取得するか(0が最新)
| 値 | 意味 |
|---|---|
| 0 | 現在バー |
| 1 | 1本前 |
| 2 | 2本前 |
例:
CopyBuffer(handle, 0, 0, 1, buffer); // 最新1本
CopyBuffer(handle, 0, 1, 1, buffer); // 1本前
重要:
- 0は未確定バー(形成中)の可能性あり
- 確定値を使うなら1を使うことが多い
■ count(取得数)
- 取得するデータの個数
CopyBuffer(handle, 0, 0, 3, buffer);
この場合:
buffer[0] → 現在
buffer[1] → 1本前
buffer[2] → 2本前
注意点:
- 配列サイズ以上を指定するとエラー
- 必要最小限にするのが基本
■ buffer[](格納先配列)
- 結果を受け取る配列
double buffer[];
ArrayResize(buffer, 3);
または
double buffer[3];
2.3 よくあるミス
CopyBufferで最も多い失敗パターンを整理します。
■ ミス①:配列サイズ未確保
double buffer[];
CopyBuffer(handle, 0, 0, 3, buffer); // NG
対策:
ArrayResize(buffer, 3);
■ ミス②:戻り値をチェックしていない
CopyBuffer(handle, 0, 0, 3, buffer);
これだけでは不十分です。
対策:
int copied = CopyBuffer(handle, 0, 0, 3, buffer);
if(copied <= 0)
{
Print("CopyBuffer error: ", GetLastError());
}
■ ミス③:start_posの誤解
- 0を「確定値」と思って使う → バグの原因
- 特にバックテストと実運用で差が出る
■ ミス④:buffer_numの誤指定
- MACDなどでラインがズレる
- 意図しない値になる
■ ミス⑤:handleの生成失敗
if(handle == INVALID_HANDLE)
{
Print("handle error");
}
実務での最低チェックテンプレ
double buffer[];
ArrayResize(buffer, 3);
int copied = CopyBuffer(handle, 0, 0, 3, buffer);
if(copied != 3)
{
Print("CopyBuffer failed: ", GetLastError());
return;
}
CopyBufferは「引数の理解」が8割です。
ここを曖昧にすると、EAのロジック全体が崩れます。
3. CopyBufferの基本的な使い方(シンプル例)
3.1 iMA(移動平均)で値を取得する最小構成
まずは最もシンプルな例として、移動平均(iMA)の値を取得します。
「CopyBufferの動き」を理解するには、このパターンが最短です。
// グローバル変数
int ma_handle;
// 初期化
int OnInit()
{
ma_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
if(ma_handle == INVALID_HANDLE)
{
Print("iMA handle error");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
// 毎Tick処理
void OnTick()
{
double ma_buffer[];
ArrayResize(ma_buffer, 1);
int copied = CopyBuffer(ma_handle, 0, 1, 1, ma_buffer);
if(copied <= 0)
{
Print("CopyBuffer error: ", GetLastError());
return;
}
double ma_value = ma_buffer[0];
Print("MA value: ", ma_value);
}
3.2 処理の流れ(重要)
上記コードは、以下の流れで動いています。
- OnInitでインジケーター生成(handle取得)
- OnTickでCopyBufferを実行
- 配列に値を格納
- 値をロジックで使用
特に重要なのは以下です。
- handleは毎回作らない(OnInitで1回)
- CopyBufferは必要なときだけ呼ぶ
- 戻り値チェックは必須
3.3 start_pos=1を使う理由
CopyBuffer(ma_handle, 0, 1, 1, ma_buffer);
ここで 1 を使っている理由は非常に重要です。
| 値 | 状態 |
|---|---|
| 0 | 未確定(現在バー) |
| 1 | 確定済み(1本前) |
実務では基本的に:
- シグナル判定 → 1を使う
- 可視化・参考 → 0でもOK
理由:
- 0はTickごとに変化するため、シグナルがブレる
- バックテストと実運用で差が出る原因になる
3.4 よくある失敗(ここが最重要)
■ 失敗①:OnTickでhandleを作る
int handle = iMA(...); // NG
問題:
- 毎Tickインジケーター生成 → パフォーマンス劣化
- メモリリークの原因
■ 失敗②:ArrayResizeを忘れる
double buffer[];
CopyBuffer(...); // NG
必ず:
ArrayResize(buffer, 1);
■ 失敗③:戻り値を無視する
CopyBufferは失敗することがあります。
- データ未ロード
- handle不正
- 通信遅延
必ずチェック:
if(copied <= 0)
■ 失敗④:ArraySetAsSeriesを理解していない
通常の配列:
buffer[0] → 古いデータ
Series配列:
ArraySetAsSeries(buffer, true);
buffer[0] → 最新データ
※ CopyBufferは基本的に「Series形式」で扱うのが安全
3.5 実務向け最適テンプレ
以下が実務で使われる最小構成です。
double buffer[];
ArrayResize(buffer, 2);
ArraySetAsSeries(buffer, true);
int copied = CopyBuffer(ma_handle, 0, 1, 2, buffer);
if(copied != 2)
{
Print("CopyBuffer failed: ", GetLastError());
return;
}
double current = buffer[0];
double prev = buffer[1];
これで:
- 最新確定値
- 1本前
の比較が可能になります。
3.6 この段階で理解すべき本質
CopyBufferは以下の役割です。
- インジケーターの「内部配列」をコピーする
- 配列操作でロジックを組む
- EAの判断材料を提供する
つまり:
👉 CopyBuffer = データ取得の入口
ここが不安定だと、すべてのロジックが崩れます。
4. CopyBufferの実践例(RSI・MACD)
4.1 iRSIの値を取得する例
RSIは単一バッファ(buffer_num=0)のため、CopyBufferの基本形をそのまま適用できます。
// グローバル
int rsi_handle;
// 初期化
int OnInit()
{
rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
if(rsi_handle == INVALID_HANDLE)
{
Print("RSI handle error");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
// Tick処理
void OnTick()
{
double rsi_buffer[];
ArrayResize(rsi_buffer, 2);
ArraySetAsSeries(rsi_buffer, true);
int copied = CopyBuffer(rsi_handle, 0, 1, 2, rsi_buffer);
if(copied != 2)
{
Print("RSI CopyBuffer error: ", GetLastError());
return;
}
double rsi_current = rsi_buffer[0];
double rsi_prev = rsi_buffer[1];
// シンプルな条件例
if(rsi_current > 70)
{
Print("Overbought");
}
else if(rsi_current < 30)
{
Print("Oversold");
}
}
4.2 RSIでの注意点
■ つまずき①:0番を使ってしまう
CopyBuffer(rsi_handle, 0, 0, 1, buffer); // NG(ブレる)
→ 必ず start_pos=1 を使う
■ つまずき②:期間変更の影響
- RSI期間(14など)によって値の性質が変わる
- ロジックに直接影響する
👉 パラメータは外部入力にするのが基本
■ つまずき③:初期バー不足
- 計算に必要なバー数が足りないと取得できない
- 特にテスト初期で発生
対策:
if(Bars(_Symbol, PERIOD_CURRENT) < 50) return;
4.3 iMACDの値を取得する例(複数バッファ)
MACDは複数バッファを持つ代表例です。
| buffer_num | 内容 |
|---|---|
| 0 | メインライン |
| 1 | シグナルライン |
int macd_handle;
int OnInit()
{
macd_handle = iMACD(_Symbol, PERIOD_CURRENT, 12, 26, 9, PRICE_CLOSE);
if(macd_handle == INVALID_HANDLE)
{
Print("MACD handle error");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnTick()
{
double macd_main[];
double macd_signal[];
ArrayResize(macd_main, 2);
ArrayResize(macd_signal, 2);
ArraySetAsSeries(macd_main, true);
ArraySetAsSeries(macd_signal, true);
int copied_main = CopyBuffer(macd_handle, 0, 1, 2, macd_main);
int copied_signal = CopyBuffer(macd_handle, 1, 1, 2, macd_signal);
if(copied_main != 2 || copied_signal != 2)
{
Print("MACD CopyBuffer error: ", GetLastError());
return;
}
double main_current = macd_main[0];
double main_prev = macd_main[1];
double signal_current = macd_signal[0];
double signal_prev = macd_signal[1];
// クロス判定
if(main_prev < signal_prev && main_current > signal_current)
{
Print("Golden Cross");
}
if(main_prev > signal_prev && main_current < signal_current)
{
Print("Dead Cross");
}
}
4.4 MACDでの重要ポイント
■ buffer_numの理解が最重要
- 0と1を間違えるとロジックが崩壊
- 見た目と一致しない原因になる
■ 同時取得のタイミング
- mainとsignalは同じタイミングで取得する
- バラバラにするとズレる可能性あり
■ 配列の同期
ArraySetAsSeries(..., true);
これを忘れると:
- indexの意味が逆転
- クロス判定が誤作動
4.5 実務での最適パターン
複数バッファの場合の定型パターン:
double buf1[], buf2[];
ArrayResize(buf1, 2);
ArrayResize(buf2, 2);
ArraySetAsSeries(buf1, true);
ArraySetAsSeries(buf2, true);
if(CopyBuffer(handle, 0, 1, 2, buf1) != 2) return;
if(CopyBuffer(handle, 1, 1, 2, buf2) != 2) return;
4.6 この章の本質
- 単一バッファ → RSIのようにシンプル
- 複数バッファ → MACDのように構造理解が必要
そして最重要は:
👉 「buffer_numの意味」と「配列の扱い」
ここを間違えると、正しく動いているように見えて誤動作するため、最も危険な領域です。
5. CopyBufferのエラー原因と対処法
5.1 CopyBufferが失敗する主な原因
CopyBufferは安定して動く関数ですが、実務では一定確率で失敗します。
重要なのは「なぜ失敗するか」を構造的に理解することです。
主な原因は以下の3系統に分かれます。
- データ未準備(ヒストリ未ロード・計算未完了)
- handle不正(生成失敗・解放済み)
- 引数ミス(配列サイズ・buffer番号など)
5.2 代表的なエラー①:戻り値が0または-1
int copied = CopyBuffer(handle, 0, 1, 2, buffer);
if(copied <= 0)
{
Print("Error: ", GetLastError());
}
■ 原因
- データがまだ存在しない
- インジケーターの計算が終わっていない
- チャートの履歴が不足
■ 対処法
if(Bars(_Symbol, PERIOD_CURRENT) < 100)
{
return;
}
または:
if(copied != count)
{
return;
}
5.3 代表的なエラー②:ERR_INDICATOR_DATA_NOT_FOUND
■ 原因
- 指定したバーにデータが存在しない
- start_posが大きすぎる
例:
CopyBuffer(handle, 0, 10000, 1, buffer); // NG
■ 対処法
- 取得範囲を現実的な値にする
- Barsでチェック
int bars = Bars(_Symbol, PERIOD_CURRENT);
if(bars < 50) return;
5.4 代表的なエラー③:INVALID_HANDLE
if(handle == INVALID_HANDLE)
■ 原因
- iMA / iRSIの生成失敗
- パラメータ不正
- シンボルや時間足の問題
■ 対処法
if(handle == INVALID_HANDLE)
{
Print("Handle creation failed: ", GetLastError());
return(INIT_FAILED);
}
5.5 代表的なエラー④:配列関連の問題
■ ケース1:サイズ不足
double buffer[];
CopyBuffer(...); // NG
→ 必ずArrayResize
■ ケース2:Series設定ミス
buffer[0] // どの位置か分からない
対策:
ArraySetAsSeries(buffer, true);
5.6 実務で最も多い「見えないバグ」
■ ケース:動いているが結果が間違っている
原因:
- buffer_numミス
- start_pos=0使用
- Series未設定
これが最も危険です。
👉 エラーが出ないため発見が遅れる
5.7 安全な実装テンプレ(実務推奨)
double buffer[];
ArrayResize(buffer, 2);
ArraySetAsSeries(buffer, true);
// データ数チェック
if(Bars(_Symbol, PERIOD_CURRENT) < 100)
{
return;
}
// CopyBuffer実行
int copied = CopyBuffer(handle, 0, 1, 2, buffer);
// 成功チェック
if(copied != 2)
{
Print("CopyBuffer failed: ", GetLastError());
return;
}
// 使用
double current = buffer[0];
double prev = buffer[1];
5.8 デバッグの基本戦略
CopyBufferで問題が出た場合、以下の順で確認します。
- handleが正常か
- Barsが十分か
- CopyBufferの戻り値
- GetLastError
- bufferの中身をPrint
Print("value: ", buffer[0]);
5.9 環境依存の注意点
以下は環境によって挙動が変わることがあります。
- ブローカーのデータ提供範囲
- シンボルのヒストリ
- VPSの通信状況
- ストラテジーテスターの設定
👉 「同じコードでも結果が違う」原因になるため注意
この章の本質
CopyBufferのエラーは偶発ではなく、構造的に発生します。
- データ未準備
- 引数ミス
- 状態管理不足
👉 この3つを潰せば、ほぼ安定します
6. CopyBufferの最適化と実務テクニック
6.1 CopyBufferは最小回数で呼ぶ
CopyBufferは軽量な関数ではありません。
毎Tick無制限に呼び続けると、CPU負荷増加・遅延・バックテスト速度低下につながります。
■ NGパターン
void OnTick()
{
CopyBuffer(handle, 0, 1, 1, buffer); // 毎Tick実行
}
■ 推奨パターン(新バー判定)
datetime last_time = 0;
void OnTick()
{
datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
if(current_time == last_time)
return;
last_time = current_time;
// 新バー時のみ実行
CopyBuffer(handle, 0, 1, 1, buffer);
}
👉 新バー単位で処理することで負荷を大幅削減
6.2 必要最小限のデータだけ取得する
■ NGパターン
CopyBuffer(handle, 0, 0, 100, buffer);
■ 推奨
CopyBuffer(handle, 0, 1, 2, buffer);
理由:
- 多くのロジックは「現在+1本前」で十分
- 無駄なデータ取得はパフォーマンス悪化
6.3 handleは再生成しない
■ NG
void OnTick()
{
int handle = iMA(...); // 毎回生成
}
■ 正解
int handle;
int OnInit()
{
handle = iMA(...);
}
👉 handleは状態として保持する
6.4 複数インジケーターの管理
実務では複数の指標を同時に使います。
■ 推奨構造
int ma_handle;
int rsi_handle;
int macd_handle;
■ 初期化でまとめて生成
ma_handle = iMA(...);
rsi_handle = iRSI(...);
macd_handle = iMACD(...);
■ OnTickでまとめて取得
CopyBuffer(ma_handle, 0, 1, 2, ma_buf);
CopyBuffer(rsi_handle, 0, 1, 2, rsi_buf);
CopyBuffer(macd_handle, 0, 1, 2, macd_buf);
👉 構造化することで保守性が向上
6.5 キャッシュ戦略(重要)
同じ値を何度も取得するのは非効率です。
■ 改善例
double ma_value;
void UpdateIndicators()
{
double buffer[];
ArrayResize(buffer, 1);
if(CopyBuffer(ma_handle, 0, 1, 1, buffer) > 0)
{
ma_value = buffer[0];
}
}
👉 値を変数に保持して使い回す
6.6 ArraySetAsSeriesは常に統一
■ 推奨ルール
ArraySetAsSeries(buffer, true);
理由:
- indexの意味を統一できる
- ロジックのバグを防ぐ
👉 「常にtrue」運用が最も安全
6.7 ストラテジーテスター最適化
バックテストではCopyBufferの使い方が結果に影響します。
■ 注意点
- start_pos=0 → リペイントの原因
- 過剰なCopyBuffer → テスト遅延
■ 推奨
- start_pos=1固定
- 新バー判定でのみ取得
6.8 実務での最終テンプレ
double buffer[];
ArrayResize(buffer, 2);
ArraySetAsSeries(buffer, true);
// 新バー判定
static datetime last_time = 0;
datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
if(current_time == last_time)
return;
last_time = current_time;
// データ取得
if(CopyBuffer(handle, 0, 1, 2, buffer) != 2)
{
Print("CopyBuffer error: ", GetLastError());
return;
}
// 使用
double current = buffer[0];
double prev = buffer[1];
6.9 この章の本質
CopyBufferの最適化は以下の3点に集約されます。
- 呼び出し回数を減らす
- 取得データを最小化する
- 状態(handle・値)を管理する
👉 これにより
- パフォーマンス向上
- バグ削減
- 実運用安定性向上
が同時に達成されます。
7. CopyBufferと関連関数の関係
7.1 CopyBufferは単独では使わない
CopyBufferは重要な関数ですが、これ単体では完結しません。
実際のMQL5開発では、以下の流れの一部として使います。
- インジケーターを作成する
- handleを取得する
- CopyBufferで値を取り出す
- 条件判定に使う
つまり、CopyBufferは「値を取り出す担当」であり、前段には必ず「インジケーター生成」があります。
典型的な流れは次の通りです。
int ma_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
double ma_buffer[];
ArrayResize(ma_buffer, 2);
ArraySetAsSeries(ma_buffer, true);
if(CopyBuffer(ma_handle, 0, 1, 2, ma_buffer) == 2)
{
double ma_current = ma_buffer[0];
double ma_prev = ma_buffer[1];
}
この構造を理解しておくと、CopyBufferを「魔法の関数」のように誤解せずに済みます。
7.2 iMA・iRSI・iMACD・iCustomとの関係
CopyBufferと最も密接に関係するのは、handleを返す関数群です。
代表例は以下です。
iMA:移動平均iRSI:RSIiMACD:MACDiCustom:カスタムインジケーター
これらはすべて、インジケーターそのものの値を直接返すのではなく、インジケーターのhandleを返します。
そのため、MQL5では次の2段階が基本になります。
■ 手順
-
iMAなどでhandleを取得する
-
CopyBufferで必要な値を配列へコピーする
たとえばiRSIなら次のようになります。
int rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
double rsi_buffer[];
ArrayResize(rsi_buffer, 1);
ArraySetAsSeries(rsi_buffer, true);
if(CopyBuffer(rsi_handle, 0, 1, 1, rsi_buffer) == 1)
{
double rsi_value = rsi_buffer[0];
}
iCustomでも考え方は同じです。
int custom_handle = iCustom(_Symbol, PERIOD_CURRENT, "MyIndicator");
double custom_buffer[];
ArrayResize(custom_buffer, 1);
ArraySetAsSeries(custom_buffer, true);
if(CopyBuffer(custom_handle, 0, 1, 1, custom_buffer) == 1)
{
double custom_value = custom_buffer[0];
}
■ よくある誤解
iMAを呼べばそのまま値が返ると思ってしまうiCustomは特別で、CopyBuffer不要だと思ってしまう
どちらも誤りです。
handle型のインジケーターは、原則としてCopyBufferで値を取得する、と覚えるのが安全です。
7.3 CopyRates・CopyTimeとの違い
CopyBufferと名前が似ている関数に、CopyRates や CopyTime があります。
ここを混同すると、設計が崩れます。
■ CopyBuffer
- 対象:インジケーターの計算結果
- 取得先:インジケーターバッファ
- 型:通常は
double[]
■ CopyRates
- 対象:価格データ一式
- 取得先:OHLC(始値・高値・安値・終値)や出来高
- 型:
MqlRates[]
MqlRates rates[];
ArrayResize(rates, 2);
int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 2, rates);
■ CopyTime
- 対象:時間データ
- 取得先:各バーの時刻
- 型:
datetime[]
datetime times[];
ArrayResize(times, 2);
int copied = CopyTime(_Symbol, PERIOD_CURRENT, 0, 2, times);
■ 使い分けの基準
- 価格そのものが欲しい →
CopyRates - 時刻が欲しい →
CopyTime - RSIやMAなどの指標値が欲しい →
CopyBuffer
この3つは用途が明確に異なります。
CopyBufferは価格データ取得関数ではなく、指標値取得関数です。
7.4 BarsCalculatedとの関係
CopyBufferの前に確認しておくと便利なのが BarsCalculated です。
これは、インジケーターが何本分計算済みかを確認する関数です。
int calculated = BarsCalculated(ma_handle);
if(calculated < 20)
{
return;
}
これは特に次の場面で有効です。
- OnInit直後
- カスタムインジケーター利用時
- マルチタイムフレーム処理時
- テスター初期バー不足時
■ よくある失敗
- CopyBufferだけ実行して失敗理由が分からない
- 実際は「まだ計算されていないだけ」
そのため、安定性を重視するなら次のような順番が有効です。
- handle生成
BarsCalculated(handle)を確認- 十分なら
CopyBuffer
7.5 IndicatorReleaseとの関係
生成したhandleは、不要になったら解放するのが原則です。
そのために使うのが IndicatorRelease です。
void OnDeinit(const int reason)
{
if(ma_handle != INVALID_HANDLE)
{
IndicatorRelease(ma_handle);
}
}
■ なぜ必要か
- 不要なインジケーターを保持し続けないため
- リソース管理を明確にするため
- 長時間稼働EAでの安定性向上のため
■ 注意点
- 解放後のhandleをCopyBufferで使うと失敗する
- 再初期化時にhandleを作り直す必要がある
7.6 実務での位置付け
実務で見ると、CopyBufferは次のような層に位置します。
- 価格データ層:CopyRates / CopyTime
- 指標データ層:CopyBuffer
- 売買判断層:シグナル判定
- 執行層:OrderSend など
この分離ができているEAは強いです。
逆に、すべてをOnTickにベタ書きすると、以下の問題が起きやすくなります。
- バグ発見が難しい
- 指標差し替えが面倒
- 最適化しにくい
- 再利用性が低い
そのため、CopyBufferは単なる取得関数ではなく、EA設計における「指標データ取得モジュールの中核」として扱うのが実務的です。
7.7 この章で押さえるべき結論
CopyBufferの周辺関数を整理すると、役割は次のように分かれます。
iMA/iRSI/iMACD/iCustom
→ handleを作るBarsCalculated
→ 計算済み本数を確認するCopyBuffer
→ 指標値を取り出すIndicatorRelease
→ handleを解放するCopyRates/CopyTime
→ 価格や時間を取得する
この関係が整理できると、CopyBufferだけでなく、MQL5のデータ取得全体の構造が見えてきます。
8. FAQ(よくある質問と解決方針)
8.1 CopyBufferとは何ですか?
回答方針:
インジケーターの計算結果(バッファ)を配列として取得する関数であることを説明する。
EAでテクニカル指標を使うための必須機能である点も補足する。
8.2 CopyBufferで値が取得できないのはなぜですか?
回答方針:
主な原因を3つに整理する。
- データ未ロード(Bars不足)
- handle不正(INVALID_HANDLE)
- CopyBuffer戻り値未確認
対処として「戻り値チェック」と「Bars確認」を提示する。
8.3 start_posは0と1どちらを使うべきですか?
回答方針:
原則として「1(確定バー)」を使う理由を説明する。
- 0は未確定で変動する
- バックテストと実運用のズレ原因になる
例外として、リアルタイム監視用途では0も可と補足する。
8.4 buffer_numとは何ですか?
回答方針:
インジケーター内のライン番号であることを説明する。
- RSI → 0のみ
- MACD → 0(メイン)、1(シグナル)
誤るとロジックが崩れるため、最重要ポイントであることを強調する。
8.5 ArraySetAsSeriesは必要ですか?
回答方針:
実務では「true固定」を推奨する理由を説明する。
- buffer[0]=最新データになる
- インデックスの混乱を防ぐ
設定しない場合のバグリスクも明示する。
8.6 CopyBufferは毎Tick呼んでも問題ないですか?
回答方針:
技術的には可能だが非推奨であることを説明する。
- パフォーマンス低下
- VPS負荷増加
対策として「新バー判定での実行」を提示する。
8.7 CopyBufferとCopyRatesの違いは何ですか?
回答方針:
用途の違いを明確にする。
- CopyBuffer → インジケーター値
- CopyRates → 価格データ(OHLC)
混同すると設計ミスになる点を補足する。
8.8 iCustomでもCopyBufferは必要ですか?
回答方針:
必要であることを明確にする。
- iCustomはhandleを返すだけ
- 値取得にはCopyBufferが必須
初心者の誤解ポイントとして強調する。
8.9 CopyBufferの最小構成は何ですか?
回答方針:
実務で使える最小テンプレを提示する。
double buffer[];
ArrayResize(buffer, 1);
ArraySetAsSeries(buffer, true);
if(CopyBuffer(handle, 0, 1, 1, buffer) == 1)
{
double value = buffer[0];
}
「これを基準に拡張すればよい」という位置づけで説明する。
9. まとめ(実務で迷わないための要点整理)
9.1 CopyBufferの役割を一言で
CopyBufferは、インジケーターの値を取得するための唯一の入口です。
MQL5では「指標=handle管理」が前提のため、CopyBufferを理解しないとEAは成立しません。
9.2 実務で守るべきルール(最重要)
以下のルールを守るだけで、ほとんどのバグは防げます。
■ ルール①:handleはOnInitで作る
int handle;
int OnInit()
{
handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
}
■ ルール②:start_posは基本1
CopyBuffer(handle, 0, 1, 2, buffer);
理由:
- 0は未確定で不安定
- 実運用とテストの乖離を防ぐ
■ ルール③:ArraySetAsSeriesはtrue固定
ArraySetAsSeries(buffer, true);
■ ルール④:戻り値は必ずチェック
if(CopyBuffer(handle, 0, 1, 2, buffer) != 2)
{
return;
}
■ ルール⑤:新バーでのみ実行
if(current_time == last_time) return;
9.3 よくある失敗と回避
■ 見た目正常だがロジックが間違っている
原因:
- buffer_numミス
- indexの逆転
- start_pos=0
👉 最も危険なバグ
■ 値が取得できない
原因:
- Bars不足
- handleエラー
- CopyBuffer失敗
👉 戻り値+GetLastErrorで必ず検証
■ EAが重い・遅い
原因:
- CopyBuffer多用
- 無駄なデータ取得
- 毎Tick実行
👉 新バー処理+最小データ取得で改善
9.4 CopyBufferの構造的理解
CopyBufferは以下の構造の一部です。
- handle生成(iMA / iRSI / iCustom)
- データ取得(CopyBuffer)
- 判定ロジック
- 発注処理
👉 この「分離」ができると、EAの品質が一段上がります。
9.5 最終テンプレ(これだけ覚えればOK)
double buffer[];
ArrayResize(buffer, 2);
ArraySetAsSeries(buffer, true);
if(Bars(_Symbol, PERIOD_CURRENT) < 100)
return;
if(CopyBuffer(handle, 0, 1, 2, buffer) != 2)
return;
double current = buffer[0];
double prev = buffer[1];
9.6 本記事の本質
CopyBufferで重要なのは以下の3点です。
- 構文ではなく「使い方」
- 値ではなく「配列の扱い」
- 関数ではなく「設計」
これを理解できれば、
- RSI・MACD・ボリンジャーバンド
- カスタムインジケーター
- マルチタイムフレーム
すべて同じ構造で扱えるようになります。