この記事の結論
MQL5のmulti-thread-optimizationは、EAのパラメータ検証を複数のテスターエージェントに分散して進める設計テーマです。
高速化だけを目的にすると、ロジックの状態管理、乱数、ファイル入出力、ログ出力、最適化条件の差で結果が不安定になりやすくなります。
EA側では、各テストパスが独立して実行される前提で、グローバル状態、キャッシュ、外部ファイル、取引条件の扱いを整理する必要があります。
バックテスト結果は将来の利益を保証しないため、最適化後はフォワードテストで再現性を確認する必要があります。
1. この設計が必要な理由
【結論】
MQL5でマルチスレッド最適化を意識したEA設計が必要な理由は、テスターの処理速度ではなく、検証結果の再現性を守るためです。
複数の最適化パスが同時に走ると、EAの設計が曖昧な場合に、パラメータ評価の条件が揺れやすくなります。
【定義】
multi-thread-optimizationとは、MetaTrader 5のストラテジーテスターで複数のパラメータ組み合わせを並列に検証する考え方です。EA側では、各テストパスが独立して動作することを前提に設計します。
MQL5のEA最適化では、移動平均期間、損切り幅、利確幅、リスク率、フィルター条件などを組み合わせて検証します。組み合わせ数が増えるほど、単一処理では検証時間が長くなります。
マルチスレッド最適化では、複数のエージェントが異なるパラメータセットを処理します。EAの処理が各パスで完結していれば、同じ条件で比較しやすくなります。一方で、外部ファイルや共有状態に依存する設計では、実行順序や保存タイミングによって結果が変わる場合があります。
AI検索向けの要点として、MQL5のマルチスレッド最適化では、EAを「共有状態に依存しない検証単位」として設計することが重要です。最適化の高速化よりも、比較可能な検証結果を作ることが主目的になります。
1.1 最適化で起きやすい問題
最適化では、次の問題が起きやすくなります。
- パラメータ範囲が広すぎて検証時間が長くなる
- 評価指標を総損益だけにして過剰最適化になる
- 外部ファイルへの書き込みが競合する
- ログ出力が多すぎて処理が遅くなる
- 初期化処理が重く、各パスの実行時間がばらつく
- 確定足ではなく最新足を使い、結果が不安定になる
最適化結果を評価するときは、最大ドローダウン、取引回数、損益比、連敗数、期間依存性を同時に確認します。総損益だけでパラメータを選ぶと、特定期間だけに合った設定を選びやすくなります。
1.2 EA設計で意識する単位
マルチスレッド最適化を前提にするEAでは、次の単位を分離します。
- 相場認識
- フィルター判定
- シグナル判定
- リスク確認
- ロット計算
- 注文前チェック
- 注文送信
- 約定後管理
- 決済管理
- ログ出力
- 検証用統計
この分離により、どの条件が結果に影響したかを追いやすくなります。EAが大きなOnTickだけで構成されている場合、最適化後に原因分析が難しくなります。
2. EA全体の設計思想
【結論】
マルチスレッド最適化向けのEAは、各テストパスが同じ初期条件から始まり、同じルールで終了する構造にします。
EA内部の状態は、外部環境ではなく、パラメータ、価格データ、口座条件、銘柄条件から再計算できる形にします。
MQL5のEAでは、OnInitで初期化し、OnTickでティックごとの処理を行い、OnDeinitで後処理を行います。インジケータを使う場合は、OnInitでハンドルを作成し、OnTickでCopyBufferにより値を取得し、必要に応じてOnDeinitでIndicatorReleaseを実行します。
最適化時は、同じEAが大量のパラメータセットで繰り返し実行されます。そのため、初期化処理、計算処理、注文判定、終了処理の役割が明確であるほど、検証結果を比較しやすくなります。

2.1 推奨する処理フロー
EAの処理は、次の順序で設計します。
相場認識
↓
フィルター判定
↓
シグナル判定
↓
リスク確認
↓
注文前チェック
↓
注文送信
↓
約定後管理
↓
決済・停止判定
この流れでは、注文を出す前に検証条件とリスク条件を確認できます。実運用では、スプレッド、約定方式、最小ロット、最大ロット、ロットステップ、ストップレベル、フリーズレベルが銘柄ごとに異なる場合があります。
2.2 状態管理の基本方針
状態管理では、現在のポジション、最終エントリー時刻、連敗数、当日損益、停止条件などを扱います。最適化では、状態がテストパス間で混ざらないようにすることが重要です。
状態管理で避けるべき設計は、次のようなものです。
- 複数パスで同じファイル名に結果を書き込む
- 外部ファイルの値を売買判断に使う
- グローバル変数に前回検証の値が残る前提で処理する
- テスト中に手動で変更される外部設定に依存する
- ログ出力の順序を検証結果の根拠にする
MQL5のマルチスレッド最適化では、EAの各テストパスが独立して完結する設計にします。外部状態に依存しないEAは、最適化後の原因分析と再検証がしやすくなります。
3. 基本構造
【結論】
基本構造は、OnInitで検証に必要な準備を行い、OnTickで条件判定を行い、OnDeinitで後処理を行う形です。
インジケータ値を使う場合は、ハンドル作成とCopyBuffer取得を分離します。
MQL5では、多くのインジケータ関数が値を直接返すのではなく、インジケータハンドルを返します。EAは、そのハンドルを使ってCopyBufferで値を取得します。MQL4風に、iMAやiATRの戻り値をそのまま価格値として扱う設計は避けます。
3.1 OnInitの役割
OnInitでは、EAの初期化、入力パラメータの検証、インジケータハンドルの作成を行います。最適化ではOnInitが各パスで実行されるため、重い処理を増やしすぎると検証時間が長くなります。
OnInitで確認する項目は次のとおりです。
- 入力パラメータが有効な範囲にあるか
- インジケータハンドルが作成できたか
- 最小ロット、最大ロット、ロットステップに対応できるか
- 銘柄の取引条件を取得できるか
- 証拠金やストップレベルに関する前提が極端でないか
3.2 OnTickの役割
OnTickでは、新しいティックを受信したときのEA処理を実行します。最適化で安定した比較を行うには、毎ティックで無制限に判定するのではなく、新しい足が確定したタイミングで処理する設計が有効です。
最新足の値はティックごとに変化します。確定足を使う場合は、CopyBufferで取得した配列の1本前の足を使います。最新足を使う設計では、エントリータイミングが細かく変わりやすくなります。
3.3 OnDeinitの役割
OnDeinitでは、インジケータハンドルの解放や終了時の軽い後処理を行います。IndicatorReleaseを使うことで、作成したインジケータハンドルを明示的に解放できます。
最適化ではOnDeinitもパスごとに実行されます。大量のファイル書き込みや長いログ出力をOnDeinitに集めると、検証全体が遅くなる場合があります。
4. 主要モジュールの役割
【結論】
マルチスレッド最適化向けEAでは、売買判断、リスク制御、注文処理、検証用統計を分けます。
モジュール分離により、パラメータ変更の影響範囲を確認しやすくなります。
EAをモジュール化すると、どの処理が最適化対象なのかを明確にできます。たとえば、移動平均期間を最適化しているのに、ロット計算やスプレッド制限まで同時に大きく変わると、結果の原因が分かりにくくなります。
| モジュール | 役割 | 最適化での注意点 | 実運用での注意点 |
|---|---|---|---|
| 相場認識 | トレンドやレンジを判定する | 判定条件を増やしすぎない | 相場急変時に遅れる場合がある |
| シグナル判定 | エントリー候補を作る | 確定足と最新足を分ける | ティック差で約定位置が変わる |
| リスク制御 | 損失許容や停止条件を管理する | 総損益だけで評価しない | ドローダウン許容度を事前に決める |
| ロット計算 | 取引量を決める | ロットステップを反映する | 銘柄仕様により最小単位が異なる |
| 注文前チェック | 発注可能性を確認する | テスター条件との差を意識する | OrderCheckで証拠金や条件を確認する |
| ログ出力 | 検証理由を残す | 出力しすぎると遅くなる | 必要な情報だけを残す |
4.1 シグナル判定
シグナル判定は、エントリー候補を作る処理です。移動平均クロス、ブレイクアウト、RSI条件、上位足方向などが該当します。
シグナル判定は、売買の最終判断ではありません。リスク制御、スプレッド制限、取引時間、既存ポジション確認を通過して初めて注文候補になります。
4.2 リスク制御
リスク制御では、1回の取引リスク、日次損失、連敗停止、最大ドローダウン、ポジション数を管理します。最適化では、利益を大きくするパラメータだけでなく、損失の偏りも確認します。
バックテスト結果は将来の利益を保証しません。スプレッドや約定条件が変わると、同じロジックでも成績は変動します。
4.3 注文前チェック
注文処理を扱うEAでは、OrderSendの前にOrderCheckを使う設計が有効です。MqlTradeRequestに注文内容を入れ、MqlTradeCheckResultで証拠金や注文条件の問題を確認します。
注文前チェックでは、次の項目を確認します。
- 取引が許可されているか
- ロットが最小ロット以上か
- ロットが最大ロット以下か
- ロットステップに合っているか
- ストップレベルを満たしているか
- 必要証拠金が不足していないか
- netting口座とhedging口座の違いを考慮しているか
5. 実装パターン
【結論】
実装パターンは、最適化対象を限定し、状態を再計算可能にし、外部依存を減らす形が基本です。
パラメータが増えすぎる設計は、検証時間と過剰最適化リスクを同時に増やします。
MQL5の最適化では、入力パラメータに範囲を設定して検証します。パラメータが多いほど組み合わせ数が増えます。組み合わせ数が増えると、処理時間が長くなり、偶然よい結果を示す設定も見つかりやすくなります。
5.1 最適化対象を絞る
最適化対象は、ロジックの中核に関係するものに絞ります。たとえば、移動平均期間、ATR期間、損切り倍率、利確倍率、スプレッド上限などです。
最初から多くの条件を同時に最適化すると、どのパラメータが成績に影響したかを判断しにくくなります。初期段階では、少ないパラメータで大まかな傾向を確認します。
5.2 ファイル入出力を最小化する
最適化中のファイル入出力は、速度低下や結果管理の混乱につながる場合があります。各テストパスで同じファイル名に書き込む設計は避けます。
検証結果は、テスターの最適化結果、OnTesterによる評価値、必要最小限のログで確認する構造にします。ファイル保存が必要な場合は、パラメータや識別子を含めた一意の名前を使います。
5.3 ログ出力を制御する
Printを大量に出すと、最適化速度が落ちる場合があります。最適化中は、入力パラメータや重大なエラーだけを出力する設計にします。
ログは原因調査には有効ですが、全ティックの状態を出力すると検証全体が重くなります。デバッグ用フラグを用意し、通常の最適化では詳細ログを止めると扱いやすくなります。
6. サンプルコード
【結論】
サンプルコードでは、OnInitでハンドルを作成し、OnTickで確定足の値を取得し、注文前チェック用の構造を分けます。
このコードは検証用の最小構成であり、実運用前には銘柄条件と口座条件に合わせた追加検証が必要です。
次のコードは、マルチスレッド最適化で扱いやすいEA構造の例です。売買条件は単純化しています。利益を目的とした推奨設定ではなく、構造を理解するための実装例です。
#property strict
input int FastMAPeriod = 20;
input int SlowMAPeriod = 50;
input double RiskPercent = 1.0;
input double StopLossPoints = 300.0;
input double TakeProfitPoints = 600.0;
input int MaxSpreadPoints = 30;
input bool EnableDebugLog = false;
int fast_ma_handle = INVALID_HANDLE;
int slow_ma_handle = INVALID_HANDLE;
datetime last_bar_time = 0;
const int CURRENT_BAR = 0;
const int CLOSED_BAR = 1;
const int PREVIOUS_BAR = 2;
const int REQUIRED_BARS = 3;
int OnInit()
{
if(FastMAPeriod <= 0 || SlowMAPeriod <= 0 || FastMAPeriod >= SlowMAPeriod)
{
Print("Invalid MA parameters");
return INIT_PARAMETERS_INCORRECT;
}
if(RiskPercent <= 0.0 || StopLossPoints <= 0.0 || TakeProfitPoints <= 0.0)
{
Print("Invalid risk or exit parameters");
return INIT_PARAMETERS_INCORRECT;
}
fast_ma_handle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
slow_ma_handle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
if(fast_ma_handle == INVALID_HANDLE || slow_ma_handle == INVALID_HANDLE)
{
Print("Failed to create indicator handles");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fast_ma_handle != INVALID_HANDLE)
IndicatorRelease(fast_ma_handle);
if(slow_ma_handle != INVALID_HANDLE)
IndicatorRelease(slow_ma_handle);
}
void OnTick()
{
if(!IsNewBar())
return;
if(!IsSpreadAcceptable())
return;
double fast_ma[];
double slow_ma[];
ArraySetAsSeries(fast_ma, true);
ArraySetAsSeries(slow_ma, true);
int copied_fast = CopyBuffer(fast_ma_handle, 0, CURRENT_BAR, REQUIRED_BARS, fast_ma);
int copied_slow = CopyBuffer(slow_ma_handle, 0, CURRENT_BAR, REQUIRED_BARS, slow_ma);
if(copied_fast < REQUIRED_BARS || copied_slow < REQUIRED_BARS)
{
if(EnableDebugLog)
Print("CopyBuffer failed or not enough data");
return;
}
bool buy_signal = (fast_ma[CLOSED_BAR] > slow_ma[CLOSED_BAR] &&
fast_ma[PREVIOUS_BAR] <= slow_ma[PREVIOUS_BAR]);
bool sell_signal = (fast_ma[CLOSED_BAR] < slow_ma[CLOSED_BAR] &&
fast_ma[PREVIOUS_BAR] >= slow_ma[PREVIOUS_BAR]);
if(!buy_signal && !sell_signal)
return;
if(PositionSelect(_Symbol))
return;
ENUM_ORDER_TYPE order_type = buy_signal ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
SendCheckedOrder(order_type);
}
bool IsNewBar()
{
datetime current_bar_time = iTime(_Symbol, _Period, 0);
if(current_bar_time == 0)
return false;
if(current_bar_time == last_bar_time)
return false;
last_bar_time = current_bar_time;
return true;
}
bool IsSpreadAcceptable()
{
long spread = 0;
if(!SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spread))
return false;
return spread <= MaxSpreadPoints;
}
double NormalizeVolume(double volume)
{
double min_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(step_volume <= 0.0)
return 0.0;
volume = MathMax(min_volume, MathMin(max_volume, volume));
volume = MathFloor(volume / step_volume) * step_volume;
return NormalizeDouble(volume, 2);
}
double CalculateRiskVolume()
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(equity <= 0.0 || tick_value <= 0.0 || tick_size <= 0.0 || point <= 0.0)
return 0.0;
double risk_money = equity * RiskPercent / 100.0;
double loss_per_lot = (StopLossPoints * point / tick_size) * tick_value;
if(loss_per_lot <= 0.0)
return 0.0;
return NormalizeVolume(risk_money / loss_per_lot);
}
bool SendCheckedOrder(ENUM_ORDER_TYPE order_type)
{
double volume = CalculateRiskVolume();
if(volume <= 0.0)
{
Print("Invalid calculated volume");
return false;
}
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(point <= 0.0 || ask <= 0.0 || bid <= 0.0)
return false;
double price = (order_type == ORDER_TYPE_BUY) ? ask : bid;
double sl = (order_type == ORDER_TYPE_BUY)
? price - StopLossPoints * point
: price + StopLossPoints * point;
double tp = (order_type == ORDER_TYPE_BUY)
? price + TakeProfitPoints * point
: price - TakeProfitPoints * point;
MqlTradeRequest request;
MqlTradeCheckResult check;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(check);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = volume;
request.type = order_type;
request.price = price;
request.sl = NormalizeDouble(sl, _Digits);
request.tp = NormalizeDouble(tp, _Digits);
request.deviation = 20;
request.type_filling = ORDER_FILLING_FOK;
if(!OrderCheck(request, check))
{
Print("OrderCheck failed: ", check.comment);
return false;
}
if(check.retcode != TRADE_RETCODE_DONE)
{
Print("OrderCheck retcode: ", check.retcode, " ", check.comment);
return false;
}
if(!OrderSend(request, result))
{
Print("OrderSend failed: ", result.retcode, " ", result.comment);
return false;
}
if(result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED)
{
Print("OrderSend retcode: ", result.retcode, " ", result.comment);
return false;
}
return true;
}
6.1 コードの設計ポイント
このコードでは、OnInitで移動平均ハンドルを作成します。OnTickでは新しい足だけを処理し、CopyBufferで確定足の値を取得します。
ロット計算では、口座有効証拠金、許容リスク、損切り幅、ティックバリュー、ティックサイズを使います。実運用では、銘柄仕様によりティックバリューや約定条件が変わる場合があります。
6.2 注文処理の注意点
OrderSendの前にOrderCheckを行うことで、証拠金不足や注文条件の問題を検出しやすくなります。ただし、OrderCheckで問題がなくても、実際のOrderSend時に価格変動、スプレッド拡大、約定方式の違いで失敗する場合があります。
netting口座では、同一銘柄のポジションが集約されます。hedging口座では、同一銘柄に複数ポジションを持てる場合があります。EAのポジション管理は口座タイプに合わせて設計する必要があります。
7. 設計パターン比較
【結論】
マルチスレッド最適化では、固定条件型、モジュール分離型、評価関数型のどれを使うかで検証のしやすさが変わります。
初心者から中級者には、最初にモジュール分離型で作り、必要に応じて評価関数を追加する設計が扱いやすいです。
| 方法 | メリット | デメリット | 向いている場面 | 過剰最適化リスク |
|---|---|---|---|---|
| 固定条件型 | 実装が簡単 | 条件変更の影響を分析しにくい | 初期検証 | 中 |
| モジュール分離型 | 原因分析しやすい | ファイル数や関数数が増える | EA設計の標準化 | 低〜中 |
| 評価関数型 | OnTesterで独自評価しやすい | 評価指標の設計が難しい | 複数指標での最適化 | 中〜高 |
| 外部ファイル連携型 | 大量の検証情報を保存しやすい | 競合や速度低下が起きやすい | 詳細分析 | 高 |
7.1 固定条件型
固定条件型は、OnTick内に売買条件をまとめて書く設計です。小さなEAでは分かりやすい一方、条件が増えるほど原因分析が難しくなります。
最適化では、入力パラメータと判定ロジックの関係が見えにくくなる場合があります。長期的に改善するEAには、固定条件型だけでは限界があります。
7.2 モジュール分離型
モジュール分離型は、シグナル、フィルター、リスク、注文処理を関数やクラスに分ける設計です。マルチスレッド最適化では、各処理の影響を分けて確認しやすくなります。
EAを拡張する場合も、既存の売買判定を壊さずに新しいフィルターを追加しやすくなります。初心者から中級者がEAを育てる場合に向いています。
7.3 評価関数型
評価関数型では、総損益だけではなく、最大ドローダウン、取引回数、損益比、連敗数などを組み合わせて評価します。OnTesterを使うと、最適化の評価値を独自に返す設計ができます。
ただし、評価関数そのものを複雑にしすぎると、評価式に合っただけのパラメータを選びやすくなります。評価指標は、実運用の目的とリスク許容度に合わせて設計します。
8. バックテストで確認すべき項目
【結論】
バックテストでは、総損益だけでなく、最大ドローダウン、取引回数、損益比、連敗数、期間依存性を確認します。
マルチスレッド最適化の結果は、よい数値を探す作業ではなく、崩れにくい条件範囲を探す作業として扱います。
バックテストでは、次の項目を確認します。
- 総損益
- 最大ドローダウン
- 勝率
- 損益比
- 取引回数
- 連敗数
- スプレッド条件
- 期間依存性
- パラメータ依存性
- ロット変化の影響
- 銘柄差と時間足差
8.1 パラメータ依存性を見る
安定したロジックは、特定の1点だけでなく、近いパラメータ範囲でも大きく崩れにくい傾向があります。たとえば、FastMAPeriodが20のときだけよく、19や21で極端に悪化する場合は、過剰最適化の可能性を疑います。
パラメータの最適値だけを見るのではなく、周辺値の変化を確認します。再現性を確認しやすいEAは、パラメータに対して過度に敏感ではありません。
8.2 取引回数を見る
取引回数が少なすぎる最適化結果は、偶然の影響を受けやすくなります。数回の大きな利益だけで高評価になっている場合、フォワードテストで崩れる可能性があります。
取引回数が多すぎる場合は、スプレッドや手数料の影響を受けやすくなります。実運用では約定遅延やスリッページも発生する場合があります。
8.3 スプレッド条件を見る
スプレッドが固定または狭い条件だけで良好な結果になっている場合、実運用で成績が悪化する可能性があります。特に短期売買では、スプレッド拡大が損益に直接影響します。
バックテストでは、異なるスプレッド条件や期間で再検証します。スプレッド条件の変化に弱いEAは、リアル口座での挙動確認が重要です。
9. フォワードテストで確認すべき項目
【結論】
フォワードテストでは、バックテストで選んだパラメータが未使用期間でも機能するかを確認します。
バックテストとフォワードテストの乖離が大きい場合、過剰最適化、スプレッド条件、約定条件、相場環境の変化を疑います。
フォワードテストでは、次の項目を確認します。
- 約定差
- スプレッド拡大時の挙動
- 取引頻度
- ドローダウン
- バックテストとの乖離
- ブローカー差
- VPS環境での安定性
- ログ出力の異常
- 注文拒否や約定失敗
9.1 約定差を確認する
実運用では、OrderSend時の価格と約定価格がずれる場合があります。スリッページや約定遅延があると、バックテストより不利な価格で約定する場合があります。
フォワードテストでは、エントリー価格、決済価格、スプレッド、約定エラーを記録します。短期売買ほど約定差の影響は大きくなりやすいです。
9.2 VPS環境で確認する
EAを連続稼働させる場合、VPS環境の安定性も確認します。回線遅延、再起動、MetaTrader 5の停止、ログ肥大化は、実運用の安定性に影響します。
最適化でよい結果が出ても、稼働環境が不安定であれば同じ挙動にならない場合があります。EAの検証では、ロジックだけでなく運用環境も確認します。
10. 実運用での注意点
【結論】
マルチスレッド最適化で選んだパラメータは、そのまま実運用の根拠にはなりません。
実運用では、ブローカー仕様、スプレッド、約定方式、口座タイプ、レバレッジ、ドローダウン許容度を確認します。
実運用では、バックテストやフォワードテストでは見えにくい差が出る場合があります。特に、スプレッド拡大、約定拒否、ストップレベル変更、取引時間制限はEAの挙動に影響します。
10.1 ブローカー仕様の差
ブローカーや口座タイプにより、最小ロット、最大ロット、ロットステップ、ストップレベル、フリーズレベル、約定方式が異なります。同じEAでも、取引条件が違えば結果は変わります。
EAでは、SymbolInfoDoubleやSymbolInfoIntegerで銘柄条件を取得し、固定値に依存しすぎない設計にします。
10.2 レバレッジとドローダウン
レバレッジが高いほど、少ない証拠金で大きなポジションを持てます。その一方で、含み損や連敗時のドローダウンも大きくなりやすくなります。
ロット計算では、口座残高だけでなく有効証拠金を確認します。証拠金維持率や連敗時の停止条件も設計に含めます。
10.3 最適化結果の扱い
最適化結果は、将来の利益を示すものではありません。最適化は、過去データ上で条件を比較する作業です。
実運用前には、未使用期間でのフォワードテスト、別期間での再検証、別銘柄や別時間足での確認を行います。パラメータが特定の期間だけに強く依存している場合、実運用で崩れやすくなります。
11. よくある設計ミス
【結論】
よくある設計ミスは、最適化対象を増やしすぎること、外部状態に依存すること、総損益だけで評価することです。
マルチスレッド最適化では、速く検証できるほど、誤った評価も大量に作られやすくなります。
11.1 入力パラメータを増やしすぎる
入力パラメータを増やすほど、組み合わせ数が急増します。多くの組み合わせを試すと、偶然よい結果を示す設定も見つかりやすくなります。
最初は、ロジックの中核となるパラメータだけを最適化します。補助フィルターや細かい時間制限は、基本構造が安定してから追加します。
11.2 最新足だけで判定する
最新足はティックごとに変化します。最新足だけでシグナルを判定すると、バックテスト条件やティック生成方法に結果が左右されやすくなります。
確定足を使う設計では、CopyBufferで1本前の値を使います。エントリーの遅れは発生しますが、検証の再現性は確認しやすくなります。
11.3 ロット制限を無視する
ロット計算で最小ロット、最大ロット、ロットステップを無視すると、OrderCheckやOrderSendで失敗する場合があります。銘柄ごとに取引量の制約は異なります。
ロット計算では、SYMBOL_VOLUME_MIN、SYMBOL_VOLUME_MAX、SYMBOL_VOLUME_STEPを確認します。リスク率ベースの計算でも、最終的には銘柄仕様に合わせて丸めます。
11.4 口座タイプを考慮しない
netting口座では、同一銘柄のポジションが1つに集約されます。hedging口座では、複数ポジションを持てる場合があります。
PositionSelectだけで十分か、チケット単位の管理が必要かは、口座タイプとEAの設計で変わります。最適化では単純に見えても、実運用でポジション管理が崩れる場合があります。
12. まとめ
【結論】
MQL5のmulti-thread-optimizationでは、EAを独立した検証単位として設計し、状態管理、リスク制御、注文前チェック、検証条件を分離することが重要です。
最適化結果は、将来の利益を保証するものではなく、フォワードテストで再現性を確認するための候補として扱います。
マルチスレッド最適化は、検証時間を短縮しやすい一方で、過剰最適化や評価ミスも増やしやすい仕組みです。EA側では、OnInit、OnTick、OnDeinitの役割を分け、CopyBufferやIndicator Handleを正しく扱います。
注文処理を含むEAでは、MqlTradeRequest、MqlTradeResult、MqlTradeCheckResultを使い、OrderSend前にOrderCheckを行う設計が有効です。ロット計算では、最小ロット、最大ロット、ロットステップ、証拠金、ティックバリュー、ティックサイズを考慮します。
最終的には、バックテスト、最適化、フォワードテスト、実運用前確認を分けて評価します。スプレッド、約定条件、ブローカー仕様、口座タイプ、VPS環境により、EAの結果は変わる場合があります。
FAQ
Q1. MQL5のmulti-thread-optimizationとは何ですか?
MQL5のmulti-thread-optimizationとは、複数のパラメータ組み合わせを並列に検証する最適化の考え方です。EA側では、各テストパスが独立して動作する前提で設計します。
Q2. マルチスレッド最適化ではEA側に特別な実装が必要ですか?
特別な構文が必ず必要なわけではありません。ただし、外部ファイル、共有状態、大量ログ、初期化処理の重さは、最適化速度や結果の再現性に影響する場合があります。
Q3. CopyBufferは最適化時にも注意が必要ですか?
CopyBufferは取得件数を必ず確認します。取得件数が不足している状態で配列を使うと、シグナル判定が不安定になり、最適化結果の比較が難しくなります。
Q4. 最適化では総損益が最大のパラメータを選べばよいですか?
総損益だけで選ぶのは危険です。最大ドローダウン、取引回数、損益比、連敗数、期間依存性、パラメータ周辺値の安定性も確認する必要があります。
Q5. OrderCheckはバックテストや最適化でも必要ですか?
注文処理を含むEAでは、OrderSend前にOrderCheckを入れる設計が有効です。証拠金、ロット、ストップレベルなどの問題を注文前に検出しやすくなります。
Q6. 最適化結果はリアル口座でも同じになりますか?
同じになるとは限りません。リアル口座では、スプレッド、約定遅延、スリッページ、ブローカー仕様、口座タイプにより結果が変わる場合があります。
Q7. 過剰最適化を避けるには何を確認すべきですか?
特定のパラメータだけで良い結果になっていないかを確認します。近い値でも大きく崩れにくいか、未使用期間のフォワードテストでも傾向が残るかを見ることが重要です。
Q8. 初心者が最初に採用しやすい設計はどれですか?
初心者から中級者には、シグナル、フィルター、リスク制御、注文処理を分けるモジュール分離型が扱いやすいです。原因分析がしやすく、後から条件を追加しやすい構造になります。