MQL 標準ライブラリエクスプローラー(第1回):CTrade、CiMA、CiATRによる紹介
議論の構造
はじめに
多くの開発者、特にMQL5を始めたばかりの方は、クラスやライブラリの使用を敬遠しがちです。この躊躇は当然ともいえます。シンプルな手続き型プログラミングのほうが、直感的で取り組みやすく感じられるためです。とはいえ、より高度で洗練されたシステムを構築するためには、こうした発想の枠を一歩超える必要があります。オブジェクト指向プログラミング(OOP)を取り入れることは、単に複雑さを増やすためではなく、最終的にはより高い専門性、効率性、そしてシンプルさを手にするための重要な一歩なのです。
MQL5標準ライブラリは、まさにその理念を体現しています。これは難しい規則を強制するものではなく、作業の重複を減らし開発を効率化するための強力なツールセットです。ゼロから車輪を発明するのではなく、熟練の技術者が作り上げたパーツを組み合わせて高性能なマシンを構築するような感覚で捉えてください。ライブラリは優れたパーツを提供し、私たちの役割はそれらを組み合わせて完成形へ導くことです。
本連載は、その道案内として設計されています。初めてEAを作る方から、既存コードの洗練を目指す上級者まで、標準ライブラリを体系的かつわかりやすく解説し、その力を最大限に活かせるようにしていきます。MetaQuotesの開発者たちが積み上げてきた機能を利用することで、複雑かつ堅牢な取引システムを、最小限のボイラープレートコードで構築できるようになるでしょう。
私はこれまで、多くの開発者が標準ライブラリの持つ豊かな機能に気づかず、すでに備わっている仕組みを一から実装しようとして苦労している様子を度々目にしてきました。標準ライブラリは、複雑さを取り除き、アルゴリズム取引における共通の課題へ優雅な解決策を提供する近道でもあります。
さらに、このライブラリの潜在能力はまだ十分に活用されていません。多くのコンポーネントが日の目を見ずに埋もれ、まるで発見されるのを待つ宝石のように存在しています。MetaTrader 5の更新に伴って標準ライブラリは拡張され、機能も向上し続けています。これを使いこなすということは、単に既存のツールを利用するだけでなく、その効率性という言語を習得し、自ら新たな機能を拡張し、創造できるようになるということでもあります。
本ディスカッションでは、そうした隠れた宝石を取り上げ、その真価を探っていきます。MQL5標準ライブラリの潜在能力を最大限に引き出し、コードと取引の在り方そのものを変革する旅を始めましょう。
連載第1回となる本記事では、MQL5標準ライブラリの代表的な3つのクラス(CTrade、CiMA、CiATR)を組み合わせ、実際に動作するエキスパートアドバイザー(EA)を構築します。読者の皆さんがすぐにコンパイルし、テストし、調整できる、実用的で完成度の高いサンプルを提示することを目的としています。背景
MQL5のドキュメントでは、CTrade、CiMA、CiATRといったクラスの形式的な定義(継承関係、利用できるメソッド、簡単な説明)が提供されています。しかし、それらをどのように組み合わせて実際の取引システムを構築するのかまでは踏み込んでいません。とくに初心者にとって、このギャップは大きな壁になります。ドキュメントは「何が存在するのか」は教えてくれますが、「それをどう効果的に使うのか」は示してくれないからです。そのため、本ディスカッションや類似の記事が重要な役割を果たします。
本連載では、APIリファレンスの羅列を超えて実践的な使い方に踏み込みます。クラスの役割やEAライフサイクルへの統合方法、出力の読み取り方、他のクラスとの組み合わせによる戦略構築まで解説します。つまり本連載は、リファレンスドキュメントと実践的な開発との間にあるギャップを埋め、生のクラス定義から実際に動作する自動売買ロボットへと導く手引きを提供するものです。
なぜこれらのクラスなのか
MQL5標準ライブラリはMetaTrader 5に必ず同梱されており、そのソースファイルはMetaEditorから容易に確認できます。デフォルトでは、これらのファイルはMQL5インストールのIncludeディレクトリ(例:MQL5\Include\Trade\やMQL5\Include\Indicators\)に配置されています。各モジュールはCTrade、CiMA、CiATRといった再利用可能なクラスを定義する.mqhヘッダファイルとして整理されています。MetaEditorでは、ナビゲータパネルの[Include] → [Standard Library]から閲覧するか、[ファイル]>[開く]で直接開くことができます。このようにアクセスしやすいため、#includeを追加するだけでこれらのクラスをプロジェクトに自由に組み込めるだけでなく、ソースコードそのものを読み、ライブラリの構造を学び、自分の開発ニーズに合わせて拡張やカスタマイズすることも可能です。

図1:MetaEditor 5でMQL5標準ライブラリにアクセスする
CTrade:実行エンジン
CTradeは、発注や基本的な注文管理を扱うMQL5標準ライブラリのラッパークラスです。低レベルのMqlTradeRequest構造体を毎回作成する代わりに、CTradeはBuy、Sell、PositionCloseなどのシンプルなメソッドを提供し、統一された結果を返してくれます。今回のEAでは、エントリーの注文送信、結果ステータスの確認、リクエスト拒否時のログ出力、必要に応じた安全なリトライ、システムエラー発生時の取引停止など、実行に関わるすべての処理でCTradeを使用します。CTradeを使うことで、実行コードが短くなり、監査も容易になり、ブローカー間での挙動もより一貫したものになります。
#include <Trade\Trade.mqh> // Global declarations CTrade trade;
CiATR:ボラティリティとサイズ計算
CiATR (Indicators\Oscillators.mqh)は、価格単位でATRを提供するクラスで、これをボラティリティエンジンとして使用します。EAではATR値を取得し、(a) 市場が荒れすぎていないか、または静かすぎないかの判断、(b) ATR×倍率での動的なストップ距離の算出、(c) ストップ距離をロットあたりの損失額に変換し、リスクに基づいたロットサイズを計算 をおこなうのに使用します。EAはCiATRインスタンスを一度だけ作成し、毎ティックで更新し、その出力が有効であることを確認し、ATR→ロットあたり損失→ブローカーのボリュームステップに正規化されたロットサイズ、という変換をおこなう小さなヘルパールーチンに渡します。
#include <Indicators\Oscilators.mqh>
CiATR atr;
CiMA:トレンド検出器
CiMA (Indicators\Trend.mqh)は、移動平均のロジックとバッファ処理をラップし、EA内で安定して読み取りやすいMA値を提供します。本EAでは高速MAと低速MAの2つのCiMAインスタンスを作成し、高速MA>低速MA → 上昇トレンド、高速MA<低速MA → 下降トレンドというシンプルな方向性フィルタを構築します。またCiMAは、現在バーまたは確定バーのMA値へ便利にアクセスできるため、たとえばATRの一定割合までのプルバックを利用したエントリー条件なども実装できます。他のクラス同様、1回だけ作成し、ティックごとに更新し、EMPTY_VALUEを必ずチェックし、非リペイントのシグナルが必要な場合はMain(1)を使うことが推奨されます。
#include <Indicators\Trend.mqh>
CiMA fastMA, slowMA;
基本ライフサイクル(概要)
- OnInit():各クラスインスタンスをCreateで一度だけ作成する
- OnTick():各ティックでRefresh()を呼び出し、Main()を読み取り(EMPTY_VALUEを確認)、判定ロジックの実行、サイズ計算、CTradeメソッドの呼び出しで実行をおこなう
- OnDeinit():インジケーターのFullRelease()を呼び出し、チャートオブジェクトの後処理をおこなう
これらの原則(クラスごとの明確な役割、規律あるライフサイクル管理、単位に応じたサイズ計算、堅牢な実行チェック)を押さえることで、CiMA、CiATR、CTradeを連携させたEA実装(Init → Refresh → Detect → Size → Execute → Manage)へと進む準備が整います。この設計は安全性やログ出力の仕様も含んでおり、信頼性の高いテストに必要な基盤となります。
実装
基礎が整ったので、いよいよ本題である実装に進みます。ここでは、各クラスを実際にどのように組み合わせて、動作する取引ソリューションとして構築するのかを説明します。本セクションの目的は、抽象的な説明をおこなうことではなく、取引実行、インジケーター分析、口座監視といった各クラスが、より大きな取引ロジックの中でどのような役割を果たすのかを、明確で構造的な手順として示すことにあります。このセクションを通じて、概念からコードへとスムーズに移行できるよう、理論から実践への橋渡しを論理的かつ取り組みやすい形で提供します。
ファイルヘッダ、インクルード、およびユーザー入力パラメータ
最初のブロックでは、プログラムの概要を宣言し、EAが使用する基本的な構成要素を読み込みます。また、期間設定、リスク、セーフティリミットなどをコードを書き換えずに調整できるよう、ユーザー入力パラメータを公開します。こうしたパラメータをファイルの先頭にまとめておくことで、実験やクラスでのテストが容易になります。
//+------------------------------------------------------------------+ //| CiMA_Trend_Trader.mq5 | //| Compact, working EA that uses CiMA (trend), CiATR (volatility) | //| and CTrade (execution). Adjustable inputs. | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property link "https://www.mql5.com/ja/users/billionaire2024/seller" #property version "1.00" #include <Trade\Trade.mqh> #include <Indicators\Trend.mqh> // CiMA #include <Indicators\Oscilators.mqh> // CiATR input int InpFastMAPeriod = 8; // Fast MA period input int InpSlowMAPeriod = 34; // Slow MA period input int InpATRPeriod = 14; // ATR period input double InpRiskPercent = 0.5; // % of balance risk per trade input double InpATRstopMult = 1.5; // SL = ATR * this input double InpPullbackFactor = 0.5; // price within this * ATR of fastMA to consider entry input double InpTakeProfitRR = 2.0; // TP = R * RR input double InpMaxChopPct = 0.006; // skip if ATR/price > this (too choppy) input double InpMinVolPct = 0.0005; // skip if ATR/price < this (too quiet) input double InpMaxDailyLossPct= 2.0; // disable trading for the day if loss exceeds this % input int InpMaxPosPerSym = 1; // max open positions per symbol (basic cap) input int InpMagicNumber = 202507; // EA magic number for labeling input int InpSlippage = 5; // slippage in points (not required for CTrade but useful to log) input bool InpShowTradeLabels = true; // draw arrows/labels on chart
グローバルオブジェクトとシンプルな状態
ここでは、EAが継続的に使用する共有オブジェクトを宣言します。具体的には、取引ハンドラとインジケーターインスタンスです。また、直近の取引時刻を記録する小さな状態変数も用意します。これらのグローバル変数は初期化、ティック処理、終了処理の間で保持されるため、ハンドルを再利用でき、コストの高い再作成を避けられます。
// Global objects CTrade trade; CiMA fastMA, slowMA; CiATR atr; // Internal tracking datetime g_lastTradeTime = 0;
初期化:インジケーターの作成とEAの準備
起動時には、各インジケーターを1回だけ作成し、その生成が正しくおこなわれたかを検証します。いずれかの作成手順が失敗した場合、EAは明確なメッセージを出して停止します。また、任意のメンテナンス処理のために短いタイマーを設定します。このパターンにより、リソースリークを防ぎ、ティックごとの作業量を最小限に抑えることができます。
int OnInit() { // Create indicator objects if(!fastMA.Create(_Symbol, _Period, InpFastMAPeriod, 0, MODE_EMA, PRICE_CLOSE)) { Print("[OnInit] fastMA.Create failed for ", _Symbol); return(INIT_FAILED); } if(!slowMA.Create(_Symbol, _Period, InpSlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE)) { Print("[OnInit] slowMA.Create failed for ", _Symbol); return(INIT_FAILED); } if(!atr.Create(_Symbol, _Period, InpATRPeriod)) { Print("[OnInit] atr.Create failed for ", _Symbol); return(INIT_FAILED); } EventSetTimer(1); // optional: ensure OnTimer for housekeeping if needed Print("CiMA_Trend_Trader initialized: ", _Symbol, " ", EnumToString(_Period)); return(INIT_SUCCEEDED); }
初期化解除とオプションのタイマー
EAがチャートから削除される際には、すべてのインジケーターリソースを解放し、タイマーを停止します。タイマーハンドラーそのものは、小さなフックとして残してあり、後でEAを拡張したい場合に診断やリセット処理を追加するために利用できます。
void OnDeinit(const int reason) { // release indicator resources fastMA.FullRelease(true); slowMA.FullRelease(true); atr.FullRelease(true); EventKillTimer(); } void OnTimer() { // Simple daily reset or housekeeping can be added here if required }
ポジション数の確認(安全管理)
新規取引を実行する前に、EAは現在この銘柄でいくつポジションが開いているかを確認します。この関数は、現在のポジションを走査してその数を返します。これにより、同時保有数の最大値というシンプルなルールを守ることができます。
int CountOpenPositionsForSymbol() { int total = PositionsTotal(); int count = 0; for(int i=0;i<total;i++) { ulong ticket = PositionGetTicket(i); if(ticket==0) continue; if(!PositionSelectByTicket(ticket)) continue; string sym = PositionGetString(POSITION_SYMBOL); if(sym == _Symbol) count++; } return(count); }
日次損失ガード(リスク保護)
暴走的な損失を避けるために、このルーチンは本日クローズされた約定を反復処理し、損益を合計します。本日の損失が設定された口座残高の割合を超えた場合、EAはそれ以上の新規取引をおこないません。シンプルですが非常に効果的な緊急ブレーキです。
bool IsDailyLossExceeded() { // compute today's closed profit/loss for the account in account currency double closedPL = 0.0; datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); // start of current day on chart's server timezone ulong from = (ulong)dayStart; int deals = HistoryDealsTotal(); for(int i=deals-1;i>=0;i--) { ulong ticket = HistoryDealGetTicket(i); if(ticket==0) continue; datetime t = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); if(t < dayStart) continue; double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_COMMISSION) + HistoryDealGetDouble(ticket, DEAL_SWAP); closedPL += profit; } // If closedPL is negative and its absolute exceeds percent of balance, return true double threshold = AccountInfoDouble(ACCOUNT_BALANCE) * (InpMaxDailyLossPct/100.0); if(closedPL < -threshold) return(true); return(false); }
ロットの正規化(ブローカールール)
ブローカーによっては、ボリュームに対して最小値、最大値、ステップサイズが制限されています。このヘルパー関数は、計算されたロット数を銘柄設定に従う有効なロット値へ変換し、計算結果がブローカーの最小ロットを下回る場合はゼロを返します。正規化をおこなうことで、無効なボリュームによる注文拒否を防ぐことができます。
double NormalizeLotToStep(double lots) { double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(lotStep <= 0) lotStep = 0.01; // fallback if(minLot <= 0) minLot = 0.01; // floor to step double steps = MathFloor(lots / lotStep); double result = steps * lotStep; if(result < minLot) result = 0.0; if(result > maxLot) result = maxLot; // round to reasonable decimals (broker may require 2-3 decimals) int digits = 2; // attempt to infer decimals from lotStep if(lotStep < 0.01) digits = MathMax(0, (int)MathCeil(-MathLog10(lotStep))); result = NormalizeDouble(result, digits); return(result); }
ATRからのボリューム計算(リスクに基づくサイズ決定)
このルーチンは、ボラティリティに基づく距離を実際の取引ボリュームへ変換する方法を示しています。まず、提案されたストップ距離に対する1ロットあたりの損失額を計算し、その後、許容リスク額をその損失額で割ることでロット数を算出します。また、ティック値のメタデータを持たない銘柄向けに、安全なフォールバック処理も含まれています。
double CalculateVolume(double stop_distance_price) { if(stop_distance_price <= 0) return(0.0); double risk_amount = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent/100.0); double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tick_value <= 0 || tick_size <= 0) { // fallback approximate using point and lot value: (this may be inaccurate for some symbols) double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); if(point<=0 || contract_size<=0) return(0.0); double value_per_price = contract_size; // rough double loss_per_lot = stop_distance_price * value_per_price; if(loss_per_lot <= 0) return(0.0); double lots = risk_amount / loss_per_lot; return NormalizeLotToStep(lots); } double value_per_price = tick_value / tick_size; // money per price unit for 1 lot double loss_per_lot = stop_distance_price * value_per_price; if(loss_per_lot <= 0) return(0.0); double lots = risk_amount / loss_per_lot; double normalized = NormalizeLotToStep(lots); return(normalized); }
エントリーの視覚的なマーク付け
テスト時の分かりやすさのために、このヘルパーはエントリー時刻に矢印と短いラベルを描画します。こうした視覚的な手がかりは、数値ログとチャート上のイベントを照合する際に非常に有用で、学生がEAの判断を一目で確認する助けにもなります。
void DrawEntryLabel(bool isLong, datetime when, double price, string tag) { if(!InpShowTradeLabels) return; string objName = tag + "_" + IntegerToString((int)when); int arrow = isLong ? 233 : 234; // Wingdings arrows // create arrow if(!ObjectCreate(0, objName, OBJ_ARROW, 0, when, price)) { // try to remove old and recreate ObjectDelete(0, objName); ObjectCreate(0, objName, OBJ_ARROW, 0, when, price); } ObjectSetInteger(0, objName, OBJPROP_COLOR, isLong ? clrBlue : clrRed); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrow); ObjectSetString(0, objName, OBJPROP_TEXT, tag); }
注文エントリーの試行(ロングとショート)
これら2つの手続きは、各方向に対する事前チェック、サイズ計算、実行という一連の流れをカプセル化したものです。安全チェックをおこない、ストップとターゲットを計算し、正規化されたボリュームを取得したうえで注文を出します。失敗した場合はその理由をログに記録し、成功した場合は時刻を記録し、必要に応じてチャートにマークを付けます。
void TryOpenLong(double atr_val) { // safety checks if(CountOpenPositionsForSymbol() >= InpMaxPosPerSym) return; if(IsDailyLossExceeded()) return; double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); double stop_distance_price = atr_val * InpATRstopMult; if(stop_distance_price<=0) return; double sl = price - stop_distance_price; double tp = price + stop_distance_price * InpTakeProfitRR; double vol = CalculateVolume(stop_distance_price); if(vol <= 0) return; // attempt to place market buy bool ok = trade.Buy(vol, NULL, 0.0, sl, tp, "CiMA_Long_"); if(!ok) { PrintFormat("[TryOpenLong] Buy failed. retcode=%d comment=%s", trade.ResultRetcode(), trade.ResultComment()); return; } // success PrintFormat("[TryOpenLong] Bought %.2f lots at %.5f SL=%.5f TP=%.5f", vol, price, sl, tp); g_lastTradeTime = TimeCurrent(); DrawEntryLabel(true, TimeCurrent(), price, "CiMA_LONG"); } void TryOpenShort(double atr_val) { // safety checks if(CountOpenPositionsForSymbol() >= InpMaxPosPerSym) return; if(IsDailyLossExceeded()) return; double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double stop_distance_price = atr_val * InpATRstopMult; if(stop_distance_price<=0) return; double sl = price + stop_distance_price; double tp = price - stop_distance_price * InpTakeProfitRR; double vol = CalculateVolume(stop_distance_price); if(vol <= 0) return; bool ok = trade.Sell(vol, NULL, 0.0, sl, tp, "CiMA_Short_"); if(!ok) { PrintFormat("[TryOpenShort] Sell failed. retcode=%d comment=%s", trade.ResultRetcode(), trade.ResultComment()); return; } PrintFormat("[TryOpenShort] Sold %.2f lots at %.5f SL=%.5f TP=%.5f", vol, price, sl, tp); g_lastTradeTime = TimeCurrent(); DrawEntryLabel(false, TimeCurrent(), price, "CiMA_SHORT"); }
ティックごとの意思決定パイプライン
市場のティックが到来するたびに、EAはインジケーターを更新し、最新の値を読み取り、基本的なボラティリティフィルタを確認し、プルバックエントリーのルールを適用します。この中心となるループがすべてのヘルパーを結びつけます。ここが戦略の核となる部分であり、インジケーター、サイズ計算、実行が交わるポイントです。
void OnTick() { // refresh indicators (cheap relative to trading operations) // Refresh indicators (the Refresh method returns void in the Standard Library; use flag -1 to update recent data) fastMA.Refresh(-1); slowMA.Refresh(-1); atr.Refresh(-1); // Note: Refresh does not return a status — check indicator values for EMPTY_VALUE below and skip until ready double fast = fastMA.Main(0); double slow = slowMA.Main(0); double atr_val = atr.Main(0); // price units if(fast==EMPTY_VALUE || slow==EMPTY_VALUE || atr_val==EMPTY_VALUE) return; double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); bool uptrend = (fast > slow); bool downtrend = (fast < slow); // normalized volatility check double norm_vol = atr_val / price; if(norm_vol > InpMaxChopPct || norm_vol < InpMinVolPct) { // skip trading in these conditions return; } // Pullback distance between price and fast MA double dist = MathAbs(price - fast); // Only consider entries on bar close for non-repainting behaviour // ensure we use the current completed bar (shift=1) for MA reference if you prefer // for simplicity here we use Main(0) but teachers may show Main(1) alternative if(uptrend && dist <= InpPullbackFactor * atr_val) { TryOpenLong(atr_val); } else if(downtrend && dist <= InpPullbackFactor * atr_val) { TryOpenShort(atr_val); } }
可読性の高いログ用の小さなユーティリティ
この小さなヘルパーは、時間足の列挙値を短いテキストラベルへ変換します。これにより、ログメッセージの可読性が向上し、複数の時間足で実行している際のデバッグが分かりやすくなります。
string EnumToString(const ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return("M1"); case PERIOD_M5: return("M5"); case PERIOD_M15: return("M15"); case PERIOD_H1: return("H1"); case PERIOD_D1: return("D1"); default: return(IntegerToString((int)tf)); } }
テストと結果
CiMA-Trend-Trader EAはコンパイル後、ストラテジーテスターで評価できます。この段階で私が注目したのは、即時の収益性ではなく、EAが正しく機能し、主目的である「トレンドフォロー」を確実に実行しているかどうかという点でした。テストを通じて、EAはH4以上の上位時間足でより安定した動作を示すことが分かりました。これらの時間足では市場のトレンドが明確で、システムへの影響が素直に反映されるためです。
反対に、下位時間足では相場構造がノイズに満ちて不規則になりがちで、それが本EAのようなトレンドフォロー手法の結果に悪影響を与える場合があります。以下の画像は、ストラテジーテスターでの私の観察を示し、異なる市場環境に対してEAがどのように反応するかを視覚的に説明しています。
30分足チャートでの初期テスト

図2:GBPUSD、M30(30分足)におけるCiMA_Trend_Trader EAのストラテジーテスター表示(2024年6月)

図3:M30で見られた一貫した資産減少のエクイティカーブ

図4:GBPUSD、M30、バックテスト結果
初期のGBPUSD (M30)テストは、収益性という観点では期待外れでした。1年間のシミュレーションで、EAは損失取引を連続して積み重ね、エクイティは約50%下落しました。ストラテジーテスターでの視覚的確認により、その原因は明らかでした。M30の市場構造には、すぐに失速する一時的な値動きや、狭いレンジでの揉み合いが多く見られ、本システムはそれらを「トレンド」と誤って判断して何度もエントリーし、結果としてストップアウトされていたのです。
このノイズの多いシグナルに対して過度に調整をおこなうのではなく、ストラテジーテスターの時間足を解像度がより高いH4へ変更し、再度テストを実施しました。次のセクションで示すとおり、その結果、動作が大幅に改善されました。トレンドがより明確で、シグナルの頻度も低下し、各取引がより大きな市場構造に沿って動いていたためです。
4時間足チャートでのテスト

図5:H4(4時間足)でのテストにおけるわずかなエクイティ上昇

図6:GBPUSD、H4、バックテスト結果
上位時間足では、トレンドがより強く、一貫した形で維持される傾向があり、上記の結果がそれを裏付けています。これは、トレンドフォロー型の本システムが、上位時間足のデータをフィルターとして用いることで、より安定した成果を出せる可能性を示しています。H4に切り替えた判断は、システムのパフォーマンスを改善するための追加ステップでしたが、その効果は明確に確認できました。もちろん、EAにはさらにファインチューニングできる多くのパラメータが残されています。
しかし、上位時間足での取引には別のトレードオフも存在します。まず、シグナルが少なく、ポジションが数日から数週間にわたって推移することもあるため、高い忍耐力が必要です。このアプローチは、短期売買や即時の利益を求めるトレーダーには向いておらず、むしろ長期的視点と規律を持つトレーダーに適しています。また、上位時間足での運用は、より大きな資金を必要とします。これは取引単位でより大きなロットを扱えるようにするためで、長期的な利益を伸ばしやすくなるためです。
以下に示す図7は、H4における取引チャートで、約定履歴の矢印とシグナル矢印が表示されています。トレンドははっきり視認でき、CiMAクラスによって生成された移動平均が市場の方向性を滑らかに示しています。この明瞭さは、下位時間足では多くのノイズが混在してしまうことが主な理由であり、上位時間足を選択した結果です。

図7:GBPUSD、H4 - ストラテジーテスターにおけるトレンドの可視化
結論
標準ライブラリを活用することで、エラーの少ない実用的なEAを構築することは十分に現実的です。CTrade、CiMA、CiATRを組み合わせることで、トレンドフォローのコンセプトを、開発上の負荷を抑えつつ、冗長なハンドル生成や注文管理ミスを減らした、コンパクトで可読性の高いコードとして実装することができました。信頼性の高いライブラリクラスに依存することで、低レベルの実装に煩わされず、戦略ロジックそのものに集中できた点は大きな利点です。
ストラテジーテスターではEAが安定して動作し、複数の時間足をまたいだ迅速な反復検証が可能でした。結果にはよく知られた傾向が表れ、H4といった上位時間足では明確で力強いトレンドを捉えられる一方、M30などの下位時間足では騙しのブレイクやノイズの多いレンジ相場によってパフォーマンスが低下しました。これは、トレンドフォロー型システムには忍耐と長期的視点が必要であり、短期戦略では追加のフィルターや安全策が欠かせないことを示しています。
今回特に価値があったのは、「アイデア → コード → 結果」という一連のプロセスそのものでした。まず取引アイデアを定義し、それを標準ライブラリのモジュール化されたクラスで表現し、その動作をストラテジーテスターの視覚化とバックテストで検証するという体系立ったワークフローによって、動作するEAが完成しただけでなく、関心の分離、視覚的な診断の活用、市場環境の違いを踏まえたテストなど、MQL5開発における優れた実践を再確認することができました。これらはプロフェッショナルなEA開発の基礎を形作るものです。
主要な洞察をまとめるため、本プロジェクトで得られたプログラミング原則、戦略上の注意点、テスト手法を整理した表を用意しています。各項目は、実際に直面した課題とその解決策に基づいており、読者が自身のEA開発を進める際の実践的な指針となるはずです。
以下に、プロジェクトのソースファイルを含む添付資料を掲載します。CiMA_Trend_Trader.mq5 EAは、実践的なデモであると同時に、読者がさらに実験や改良を進めるための再利用可能な出発点として役立つよう設計されています。テストや調整、拡張は自由であり、たとえば CiCCI、CiMACD、その他のボラティリティフィルターといった標準ライブラリのクラスを統合することで、新しいアプローチを探索し、自身の取引システムの精度を高めることも可能です。
皆さまからのフィードバックやご意見を歓迎いたします。経験の共有、改善案、新たな方向性の提案など、ぜひお寄せください。次回の公開もどうぞご期待ください。
重要なポイント
| 重要な学び | 説明 |
|---|---|
| コンセプトからコードへ | 実装前に、ルールを明確なステップごとのアルゴリズムや疑似コードへ落とし込みます。これにより、動作を再現しやすくなり、テストも簡単になります。 |
| OOP構造 | 小さく焦点の定まったクラスやモジュール(インジケーター、サイズ計算、実行など)を使い、それぞれが単一の責務を持つようにします。これにより再利用や置き換えが容易になります。 |
| ライフサイクル管理 | インジケーターインスタンスは一度だけ作成し、ティックごとに更新し、初期化解除時に解放します。これによりリソースリークやテストの不安定化を防ぎます。 |
| 単位変換 | 単位を明確に扱います。ATR(価格単位)をポイントや金額損失へ変換する際は、ティックサイズやティック値を使用して正確なストップ距離とサイズ計算をおこないます。 |
| ポジションサイズ | サイズ計算は純粋な計算として扱います。「リスク許容量 ÷ ロットあたりの損失」で求め、その後ブローカーのボリュームルールに基づいて正規化することで、一貫したエクスポージャーを保ちます。 |
| 実行とエラー処理 | 注文送信ロジックを中央で扱い、結果コードやメッセージを必ず確認し、一般的なエラーに対して明確なリトライ方針やフェイルセーフを実装します。 |
| 状態管理 | オープン数、最終取引時刻、有効フラグなどの状態を明示的に保持し、一箇所で更新することでレースコンディションやロジックの崩壊を防ぎます。 |
| 防御的コーディング | 欠損データ、ゼロティック値、短い履歴などを想定し、ログ出力やフォールバック動作で安全に失敗するようにします。クラッシュさせないことが重要です。 |
| モジュール性 | シグナル、サイズ計算、安全管理、描画、実行などを関心ごとに分離し、関数またはファイルに分けることで、テストと保守が容易になります。 |
| ログ記録と可視化 | 意思決定レベルの値をログに記録し、チャート上のオーバーレイ(MA、ATRバンド、エントリーマーカー)を利用して、数値ロジックと視覚的挙動を突き合わせます。 |
| 高品質なテスト | 99%ティックモードで、現実的なスプレッドやスワップを設定してバックテストをおこないます。質の低いデータでは誤った結論につながります。 |
| 最適化の規律 | 最適化は慎重におこない、アウトオブサンプルやウォークフォワード検証を使用し、バックテスト利益のピークではなく安定したパラメータ領域に注目します。 |
| パフォーマンス意識 | ティックごとのメモリアロケーションを最小限にし、ハンドルを再利用し、多数の銘柄や時間足で実行する際はCPU/メモリを計測します。 |
| 実行時保護 | 1日の最大損失、最大ポジション数、緊急停止、各種ヘルスチェックなど、稼働中のリスクを制限するための安全策を組み込みます。 |
| 実験ワークフロー | 実験(コードバージョン、.setファイル、メトリクス)を記録し、A/B比較をおこない、小さく測定可能な変更を重ねて改善していきます。 |
添付ファイル
| ファイル名 | 詳細 |
|---|---|
| CiMA_Trend_Trader.mq5 | 単体で動作するトレンドフォロー型のEAで、MQL5標準ライブラリの3つの実用的なクラスを示しています。CiMA(方向検出のための高速・低速移動平均)、CiATR(動的なストップとポジションサイズ算出のためのATRベースのボラティリティ)、およびCTrade(注文実行)です。チューニング可能な入力項目(MA期間、ATR期間と倍率、リスク%、TP/SLのRR、ボラティリティフィルタ、日次最大損失、最大ポジション数)を備え、ブローカー仕様に対応したロット正規化、安全チェック、シンプルな視覚的エントリーマーカー、ストラテジーテスター検証のための明確なログ出力を特徴としています。 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19341
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
サイクルベースの取引システム(DPO)の構築と最適化の方法
機械学習の限界を克服する(第4回):複数ホライズン予測による既約誤差の回避
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
Market Sentimentインジケーターの自動化
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索