MQL5での取引戦略の自動化(第42回):セッションベースのオープニングレンジブレイクアウト(ORB)システム
はじめに
前回の記事(第41回)では、MetaQuotes Language 5 (MQL5)でローソク足レンジ理論(CRT, Candle Range Theory)に基づく取引システムを開発しました。このシステムでは、指定した時間枠での蓄積レンジを特定し、押しの深さを考慮したブレイクの検出、そして分配フェーズにおけるローソク足の確定を通じた反転確認によってエントリーをおこなう仕組みを組み込みました。今回の第42回では、完全にカスタマイズ可能なセッションベースのオープニングレンジブレイクアウト(ORB)システムを開発します。
このシステムでは、任意のセッション開始時刻と期間を分単位で定義できます。選択した時間足で、その期間の真の高値と安値を自動で取得します。ブレイクアウトを検出し、オプションで複数バーの確定を確認して誤シグナルを減らします。エントリーはブレイクアウトの方向にのみ実行されます。ストップロスやテイクプロフィットは、レンジサイズに基づく動的設定、あるいは固定値の静的設定のいずれかが使用可能です。利益閾値に達した後にトレーリングストップを使用することもできます。また、方向ごとのポジション上限も設定可能です。本記事では以下のトピックを扱います。
記事の最後には、ロンドン、ニューヨーク、アジア市場、あるいは任意のカスタムオープニングを含むあらゆる市場セッションで、明確なオープニングレンジブレイクアウトを取引できるMQL5プログラムが完成し、さらにカスタマイズする準備が整います。それでは、さっそく始めましょう。
オープニングレンジブレイクアウト(ORB)戦略の理解
オープニングレンジブレイクアウト(ORB)は、典型的なデイトレード向けモメンタム戦略で、取引セッションの開始時に形成される初期の方向性バイアスを利用します。「オープニングレンジ」とは、市場が開いた直後の最初の数分間(通常5~60分)に形成される高値と安値を指します。その後、価格がレンジの高値を明確に上抜けた場合(強気ブレイクアウト)やレンジの安値を下抜けた場合(弱気ブレイクアウト)に、ブレイク方向に沿ってエントリーします。この戦略の前提はシンプルですが強力です。オープニングレンジは、前日のニュースや注文フローを市場が消化する過程で、買い手と売り手の戦いを反映することが多く、明確なブレイクアウトはどちらかの勢力が市場の主導権を握ったことを示します。この場合、ブレイク方向に持続的なトレンドが発生することがよくあります。システム自体は比較的簡単です。以下に想定される各セットアップの例を示します。

本記事では、任意の銘柄と任意のセッション(ニューヨーク、ロンドン、アジア、あるいはカスタムオープニング)で機能する、セッションに完全対応した柔軟なORBシステムを作成します。 ユーザーは正確な開始時刻を設定できます。たとえば、NYSEでは09:30、ロンドンでは08:00などです。また、レンジ期間を分単位で定義することも可能です。 システムは、選択した時間枠内でその期間の高値と安値を自動計算します。必要に応じて、複数バーの確定確認を有効にして、ブレイクアウトの正当性を検証することも可能です。
アルゴリズムは、1セッションあたり方向ごとに1回のみ取引を実行します。ストップロスおよびテイクプロフィットの計算は、レンジサイズに基づく動的方式と固定値の静的方式の2種類を提供し、リスクリワード比率もカスタマイズ可能です。 利益が一定水準に達した後に発動するポイントベースのトレーリングストップも利用できます。さらに、このシステムは豊富なチャート可視化機能を備えています。これには、塗りつぶし済みのレンジ矩形、セッション開始と終了の垂直マーカー、高値と安値のレベル表示、エントリー矢印などが含まれます。
可視化は、ここまで読んでお気づきの通り、戦略の理解を明確にするために非常に重要です。以下に、想定されるビジュアル表示を簡単に示します。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータとグローバル変数をいくつか宣言する必要があります。
//+------------------------------------------------------------------+ //| ORB Opening Range Breakout EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Enums | //+------------------------------------------------------------------+ enum SLTP_Method { // Define SL/TP method enum Dynamic_Method = 0, // Dynamic based on range size Static_Method = 1 // Static based on fixed points }; enum TrailingTypeEnum { // Define trailing type enum Trailing_None = 0, // None Trailing_Points = 1 // By Points }; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input ENUM_TIMEFRAMES RangeTF = PERIOD_M5; // Timeframe for Opening Range Calculation input int RangeDurationMinutes = 30; // Duration of Opening Range in Minutes input string SessionStartTime = "09:00"; // Session Start Time (HH:MM) input double TradeVolume = 0.01; // Trade Volume Size input double RR_Ratio = 2.0; // Risk to Reward Ratio input SLTP_Method SLTP_Approach = Dynamic_Method; // SL/TP Calculation Method input int SL_Points = 50; // SL Points (for Static Method) input TrailingTypeEnum TrailingType = Trailing_None; // Trailing Stop Type input double Trailing_Stop_Points = 20.0; // Trailing Stop in Points input double Min_Profit_To_Trail_Points = 30.0; // Min Profit to Start Trailing in Points input int UniqueID = 987654321; // Unique Trade Identifier input int MaxPositionsDir = 1; // Max Positions per Direction input bool UseBreakoutFilter = true; // Use Breakout Confirmation Filter input int ConfirmBars = 1; // Bars to Confirm Breakout on Close (0 to disable)
実装はまず「#include <Trade\Trade.mqh>」を使用してTradeライブラリをインクルードすることから始めます。これにより、CTradeクラスおよび注文実行やポジション管理に必要な関数が利用可能になります。次に、ユーザーオプションを明確に整理するために2つの列挙型を定義します。SLTP_Method列挙型では、オープニングレンジの実際のサイズに基づいてストップロスおよびテイクプロフィットを計算するDynamic_Methodと、固定ポイントによる計算をおこなうStatic_Methodが用意されています。同様に、TrailingTypeEnum列挙型では、トレーリングを無効にするTrailing_Noneと、利益が最低閾値に達した後にユーザー指定のポイント数でトレーリングを有効にするTrailing_Pointsが用意されています。
次に、プロパティウィンドウから直接プログラムを柔軟に設定できる入力パラメータを宣言します。これには以下が含まれます。これには、オープニングレンジの高値と安値を計算する時間足を選択するRangeTF、セッション開始からオープニングレンジとして計測する分数を設定するRangeDurationMinutes、各セッションの開始時刻を「HH:MM」形式の文字列で定義するSessionStartTime(例:NYSEは「09:30」、ロンドンは「08:00」)、ロットサイズを指定するTradeVolumeなどが含まれます。その他のパラメータも名称から内容が理解できるようにコメントを付けています。この入力セットにより、コードを変更することなく、任意の市場やセッションにシステムを完全に適応させることが可能です。次に必要なのは、グローバル変数の定義です。
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CTrade obj_Trade; //--- Trade object datetime sessionStart = 0; //--- Session start time datetime rangeEndTime = 0; //--- Range end time double rangeHigh = 0.0; //--- Range high double rangeLow = 0.0; //--- Range low bool rangeDefined = false; //--- Range defined flag bool breakoutHigh = false; //--- Breakout high flag bool breakoutLow = false; //--- Breakout low flag double breakoutPrice = 0.0; //--- Breakout price string highLevelObj = "ORB_HighLevel"; //--- High level object name string lowLevelObj = "ORB_LowLevel"; //--- Low level object name string highTextObj = "ORB_High_Text"; //--- High text object string lowTextObj = "ORB_Low_Text"; //--- Low text object bool tradedLong = false; //--- Traded long flag bool tradedShort = false; //--- Traded short flag datetime lastConfirmTime = 0; //--- Last confirm time
次に、各取引セッションを通じてプログラムの状態を維持し、オープニングレンジブレイクアウトのロジックを適切に追跡する一連のグローバル変数を宣言します。CTradeクラスからobj_Tradeをインスタンス化し、すべての注文実行やポジション修正を担当させます。タイミング管理用の変数としては、セッション開始の正確な日時を記録するsessionStart、オープニングレンジ期間が終了する時刻を示すrangeEndTimeがあります。オープニングレンジの境界はrangeHigh(初期値0.0)とrangeLowで追跡し、rangeDefinedは現在のセッションのレンジが完全に確定したかどうかを示すブール型フラグとして機能します。その他の変数も名前から概ね理解できるものです。これらの設定が完了したら、システムを初期化するためにマジックナンバーを設定すれば準備は完了です。
//+------------------------------------------------------------------+ //| EA Start Function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(UniqueID); //--- Set magic number return(INIT_SUCCEEDED); //--- Return success }
OnInitイベントハンドラでは、プログラムが初めて読み込まれたとき、またはチャートにアタッチされたときに自動実行されます。ここでは、ユーザー定義のUniqueIDをobj_Tradeオブジェクトのマジックナンバーとして設定するために、obj_Trade.SetExpertMagicNumber(UniqueID)を呼び出します。これにより、プログラムによって開かれたすべての取引にはこの一意の識別子が付与され、複数のプログラムや手動取引が同じ口座でおこなわれている場合でも、正確にフィルタリングおよび管理できるようになります。最後に、INIT_SUCCEEDEDを返すことで、初期化が問題なく完了し、プログラムが正常に動作可能であることをプラットフォームに通知します。また、ロジックが整い次第、可視化のために使用するいくつかのヘルパー関数を定義する予定です。
//+------------------------------------------------------------------+ //| Render Horizontal Level | //+------------------------------------------------------------------+ void RenderLevel(string objName, double levelVal, color levelClr, string levelDesc) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_HLINE, 0, 0, levelVal); //--- Create hline ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, levelClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_STYLE, STYLE_DOT); //--- Set style ObjectSetString(ChartID(), objName, OBJPROP_TOOLTIP, levelDesc); //--- Set tooltip ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Render Vertical Line | //+------------------------------------------------------------------+ void RenderVLine(string objName, datetime timeVal, color lineClr, string desc) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_VLINE, 0, timeVal, 0); //--- Create vline ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, lineClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_STYLE, STYLE_DOT); //--- Set style ObjectSetInteger(ChartID(), objName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(ChartID(), objName, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(ChartID(), objName, OBJPROP_RAY, true); //--- Set ray ObjectSetInteger(ChartID(), objName, OBJPROP_HIDDEN, true); //--- Set hidden ObjectSetString(ChartID(), objName, OBJPROP_TOOLTIP, desc); //--- Set tooltip ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Render Text Label | //+------------------------------------------------------------------+ void RenderText(string objName, datetime timeVal, double priceVal, string textStr, color textClr, int anchorVal) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_TEXT, 0, timeVal, priceVal); //--- Create text ObjectSetString(ChartID(), objName, OBJPROP_TEXT, textStr); //--- Set text ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, textClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_ANCHOR, anchorVal); //--- Set anchor ObjectSetInteger(ChartID(), objName, OBJPROP_FONTSIZE, 10); //--- Set fontsize ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Draw Entry Arrow | //+------------------------------------------------------------------+ void DrawEntryArrow(datetime timeVal, double priceVal, bool isBuy) { string markerName = "EntryMarker_" + IntegerToString(timeVal); //--- Marker name ObjectCreate(ChartID(), markerName, OBJ_ARROW, 0, timeVal, priceVal); //--- Create arrow int arrowCode = isBuy ? 233 : 234; //--- Arrow code color arrowClr = isBuy ? clrBlue : clrRed; //--- Arrow color int anchor = isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP; //--- Anchor ObjectSetInteger(ChartID(), markerName, OBJPROP_ARROWCODE, arrowCode); //--- Set code ObjectSetInteger(ChartID(), markerName, OBJPROP_COLOR, arrowClr); //--- Set color ObjectSetInteger(ChartID(), markerName, OBJPROP_ANCHOR, anchor); //--- Set anchor ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Count Active Positions by Type | //+------------------------------------------------------------------+ int ActivePositions(ENUM_POSITION_TYPE posType) { int total = 0; //--- Init total for (int pos = PositionsTotal() - 1; pos >= 0; pos--) { //--- Iterate positions if (PositionGetSymbol(pos) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID && PositionGetInteger(POSITION_TYPE) == posType) { //--- Check position total++; //--- Increment total } } return total; //--- Return total }
ここでは、チャートの可視化やポジション管理をおこなういくつかのヘルパー関数を作成します。これにより、オープニングレンジや取引シグナルが明確に表示され、かつコードの整理も保たれます。RenderLevel関数は、レンジの高値と安値の水平線を描画または更新します。指定した名前の既存オブジェクトを削除し、指定された価格レベルに新しいOBJ_HLINE を作成します。色を設定(高値は緑、安値は赤)、点線スタイルを適用し、説明用のツールチップを追加し、チャートを再描画して即座に確認できるようにします。
同様に、RenderVLine関数は、セッション開始やレンジ終了の時間を示す垂直線を描画します。既存のオブジェクトを削除し、指定された日時にOBJ_VLINEを作成します。青色、点線スタイル、幅1、背景表示、右方向へのレイ表示、オブジェクトリストから非表示、正確な時間を示すツールチップを設定し、ChartRedraw関数でチャートを再描画します。RenderText関数は、開始・終了時刻や「ORB High」「ORB Low」などの注釈を含む、カスタマイズ可能なテキストラベルを追加します。既存のテキストオブジェクトを削除し、指定された時間と価格の座標にOBJ_TEXTを作成し、テキスト内容やその他のプロパティを設定します。
取引エントリーにはDrawEntryArrowを実装しています。この関数は、実行時にチャート上に視覚的マーカーを配置します。現在の時刻を使って一意の名前を生成し、OBJ_ARROWを作成します。買いの場合はWingdingsのシンボル233(上矢印)、売りの場合は234(下矢印)を選択します。ロングは青、ショートは赤を適用し、矢印を正しい位置(下または上)に固定してチャートを再描画します。矢印コードはMQL5のWingdingsフォントに対応しており、任意に切り替えることが可能です。

最後に、ActivePositions関数を定義します。これは、このプログラムに属する特定のタイプ(買いまたは売り)のポジションがいくつ開かれているかを正確に数えるための関数です。関数はすべてのポジションを後ろから順にループし、銘柄が一致するか、マジックナンバーが「UniqueID」と一致するか、さらにポジションタイプがPOSITION_TYPE_BUYであるかPOSITION_TYPE_SELLであるかを確認し、該当するポジション数を返します。これで、毎日のレンジをまず定義するところから、戦略の実装を開始できるようになります。
//+------------------------------------------------------------------+ //| Tick Processing Function | //+------------------------------------------------------------------+ void OnTick() { datetime currentTime = TimeCurrent(); //--- Get current time MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert to struct // Determine if a new session has started string currentTimeStr = StringFormat("%02d:%02d", timeStruct.hour, timeStruct.min); //--- Format time string if (currentTimeStr == SessionStartTime && sessionStart != currentTime - (timeStruct.hour * 3600 + timeStruct.min * 60 + timeStruct.sec)) { //--- Check new session sessionStart = currentTime - timeStruct.sec; //--- Align to minute start rangeEndTime = sessionStart + RangeDurationMinutes * 60; //--- Calc end time rangeHigh = 0.0; //--- Reset high rangeLow = DBL_MAX; //--- Reset low rangeDefined = false; //--- Reset defined breakoutHigh = false; //--- Reset high breakout breakoutLow = false; //--- Reset low breakout tradedLong = false; //--- Reset long traded tradedShort = false; //--- Reset short traded lastConfirmTime = 0; //--- Reset confirm time // Clean previous visuals for current levels ObjectDelete(ChartID(), highLevelObj); //--- Delete high level ObjectDelete(ChartID(), lowLevelObj); //--- Delete low level ObjectDelete(ChartID(), highTextObj); //--- Delete high text ObjectDelete(ChartID(), lowTextObj); //--- Delete low text } if (sessionStart == 0) return; //--- Return if no session double currBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask // Define the opening range if (currentTime < rangeEndTime) { //--- Check within range rangeHigh = MathMax(rangeHigh, iHigh(_Symbol, RangeTF, 0)); //--- Update high rangeLow = MathMin(rangeLow, iLow(_Symbol, RangeTF, 0)); //--- Update low } else if (!rangeDefined) { //--- Check not defined rangeDefined = true; //--- Set defined // Draw the opening range rectangle string rectObj = "ORB_Rectangle_" + IntegerToString(sessionStart); //--- Rect name ObjectCreate(ChartID(), rectObj, OBJ_RECTANGLE, 0, sessionStart, rangeHigh, rangeEndTime, rangeLow); //--- Create rect ObjectSetInteger(ChartID(), rectObj, OBJPROP_COLOR, clrLightBlue); //--- Set color ObjectSetInteger(ChartID(), rectObj, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(ChartID(), rectObj, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(ChartID(), rectObj, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ChartRedraw(ChartID()); //--- Redraw chart // Add vertical lines for start and end string startVLineObj = "ORB_StartVLine_" + IntegerToString(sessionStart); //--- Start vline name RenderVLine(startVLineObj, sessionStart, clrBlue, "ORB Start at " + TimeToString(sessionStart, TIME_MINUTES)); //--- Render start vline string endVLineObj = "ORB_EndVLine_" + IntegerToString(sessionStart); //--- End vline name RenderVLine(endVLineObj, rangeEndTime, clrBlue, "ORB End at " + TimeToString(rangeEndTime, TIME_MINUTES)); //--- Render end vline // Add time text labels for start and end double textOffset = (rangeHigh - rangeLow) * 0.05; //--- Calc offset string startTimeTextObj = "ORB_StartTime_Text_" + IntegerToString(sessionStart); //--- Start text name RenderText(startTimeTextObj, sessionStart, rangeLow - textOffset, TimeToString(sessionStart, TIME_MINUTES), clrBlue, ANCHOR_UPPER); //--- Render start text string endTimeTextObj = "ORB_EndTime_Text_" + IntegerToString(sessionStart); //--- End text name RenderText(endTimeTextObj, rangeEndTime, rangeLow - textOffset, TimeToString(rangeEndTime, TIME_MINUTES), clrBlue, ANCHOR_UPPER); //--- Render end text // Render high and low levels RenderLevel(highLevelObj, rangeHigh, clrGreen, "ORB High"); //--- Render high level RenderLevel(lowLevelObj, rangeLow, clrRed, "ORB Low"); //--- Render low level // Add text labels RenderText(highTextObj, rangeEndTime, rangeHigh, "ORB High", clrGreen, ANCHOR_RIGHT_LOWER); //--- Render high text RenderText(lowTextObj, rangeEndTime, rangeLow, "ORB Low", clrRed, ANCHOR_RIGHT_UPPER); //--- Render low text } }
OnTickイベントハンドラでは、まずTimeCurrentを使ってサーバーの現在時刻をcurrentTimeに取得し、TimeToStructでMqlDateTime構造体に変換して、時刻や分などの個別の要素にアクセスできるようにします。その後、StringFormatを使って現在時刻を「HH:MM」形式の文字列に整形し、currentTimeStrに格納します。これにより、時刻情報を簡単に扱える構造が完成します。
struct MqlDateTime { int year; // Year int mon; // Month int day; // Day int hour; // Hour int min; // Minutes int sec; // Seconds int day_of_week; // Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) int day_of_year; // Day number of the year (January 1st is assigned the number value of zero) };
構造体に分けることで、時間の各要素(時・分など)を簡単に取り出せるようになります。新しい取引セッションの正確な開始を検出するために、この文字列をユーザー定義のSessionStartTimeと比較します。さらに、追加の条件として、sessionStartがすでに当日の同じ分に設定されていないことを確認することで、1日に1回だけ処理が実行されるようにしています(秒単位を引いて正規化)。新しいセッションが始まった場合は、sessionStartをその分の開始時刻に正確に合わせ(残り秒を引く)、rangeEndTime「RangeDurationMinutes × 60秒」で計算します。初期更新を正しくおこなうためにrangeHighを0.0に、rangeLowをdouble型の最大値にリセットし、フラグ(rangeDefined、breakoutHigh、breakoutLow、tradedLong、tradedShort、lastConfirmTime)をすべてクリアします。また、前回のセッションで描画した水平ラインやテキストオブジェクトを削除し、クリーンな状態に準備します。まだアクティブなセッションが検出されていない場合(sessionStart == 0)は、余計な処理を避けるために単に戻ります。それ以外の場合は、SymbolInfoDoubleで現在のBIDとASK価格を取得します。
オープニングレンジ形成期間中(currentTimeがrangeEndTimeより小さい間)、レンジの境界を継続的に更新します。rangeHighは現在の値と、RangeTFの最新バー(シフト0)の高値(iHigh)の最大値に設定し、rangeLowは現在の値と最新バーの安値(iLow)の最小値に設定します。レンジ期間が終了し、まだレンジが確定していない場合(!rangeDefined)、rangeDefinedをtrueに設定し、完成したオープニングレンジの可視化に進みます。
オープニングレンジの可視化では、開始時刻のsessionStartと高値rangeHighから終了時刻のrangeEndTimeと安値rangeLowまで塗りつぶしの長方形を描画し、セッションタイムスタンプに基づくユニーク名、塗りつぶしスタイル、背景配置を適用します。開始時刻と終了時刻には、RenderVLineを使って垂直の青点線を追加し、説明用のツールチップを付けます。時間ラベルはレンジの下に、レンジサイズの5%オフセットで青色、上向きアンカーで配置します。最後に、RenderLevelを使って持続的な水平ラインを描画します。高値は緑、安値は赤で表示し、対応するテキストラベルをレンジ終了時刻の右側にアンカーして配置します。これにより、数時間や数日経過しても、正確なブレイクアウトレベルを常に確認できるようにします。コンパイルすると、次の結果が得られます。

レンジが確定したら、あとはそのレンジとブレイクアウトを追跡するだけです。いずれかのレンジを突破した時点で方向を判断し、それに応じたブレイクアウトセットアップを決定してエントリーします。とてもシンプルです。そのためのロジックを以下に実装します。
if (!rangeDefined) return; //--- Return if not defined // Detect breakout bool justBreached = false; //--- Init just breached if (currAsk > rangeHigh && !breakoutHigh) { //--- Check high breakout breakoutHigh = true; //--- Set high breakout justBreached = true; //--- Set just breached breakoutPrice = currAsk; //--- Set breakout price } else if (currBid < rangeLow && !breakoutLow) { //--- Check low breakout breakoutLow = true; //--- Set low breakout justBreached = true; //--- Set just breached breakoutPrice = currBid; //--- Set breakout price } if ((breakoutHigh || breakoutLow) && !(tradedLong || tradedShort)) { //--- Check breakout and not traded // Confirm breakout with bar closures if enabled bool confirmed = false; //--- Init confirmed if (ConfirmBars == 0) { //--- Check no confirm confirmed = true; //--- Set confirmed } else { //--- Else datetime currConfirmTime = iTime(_Symbol, RangeTF, 0); //--- Get confirm time if (currConfirmTime != lastConfirmTime) { //--- Check new confirm lastConfirmTime = currConfirmTime; //--- Update last confirm int confirmCount = 0; //--- Init count for (int i = 1; i <= ConfirmBars; i++) { //--- Iterate bars double closePrice = iClose(_Symbol, RangeTF, i); //--- Get close if (breakoutHigh && closePrice > rangeHigh) confirmCount++; //--- Check high if (breakoutLow && closePrice < rangeLow) confirmCount++; //--- Check low } if (confirmCount >= ConfirmBars) confirmed = true; //--- Set confirmed } } if (confirmed && UseBreakoutFilter) { //--- Check confirmed and filter // Additional filter logic if needed, but for now assume confirmed } if (confirmed) { //--- Check confirmed double sl = 0.0, tp = 0.0; //--- Init SL TP if (breakoutHigh && ActivePositions(POSITION_TYPE_BUY) < MaxPositionsDir && !tradedLong) { //--- Check long entry if (SLTP_Approach == Dynamic_Method) { //--- Check dynamic double rangeSize = rangeHigh - rangeLow; //--- Calc range size sl = NormalizeDouble(rangeLow, _Digits); //--- Set SL tp = NormalizeDouble(currAsk + rangeSize * RR_Ratio, _Digits); //--- Set TP } else { //--- Static sl = NormalizeDouble(currAsk - SL_Points * _Point, _Digits); //--- Set SL tp = NormalizeDouble(currAsk + (SL_Points * _Point) * RR_Ratio, _Digits); //--- Set TP } if (obj_Trade.Buy(TradeVolume, _Symbol, currAsk, sl, tp, "ORB Long Breakout")) { //--- Open buy if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success Print("Long Breakout: Entry at ", DoubleToString(currAsk, _Digits), " SL at ", DoubleToString(sl, _Digits), " TP at ", DoubleToString(tp, _Digits)); //--- Log entry DrawEntryArrow(currentTime, currBid, true); //--- Draw arrow tradedLong = true; //--- Set long traded } } } else if (breakoutLow && ActivePositions(POSITION_TYPE_SELL) < MaxPositionsDir && !tradedShort) { //--- Check short entry if (SLTP_Approach == Dynamic_Method) { //--- Check dynamic double rangeSize = rangeHigh - rangeLow; //--- Calc range size sl = NormalizeDouble(rangeHigh, _Digits); //--- Set SL tp = NormalizeDouble(currBid - rangeSize * RR_Ratio, _Digits); //--- Set TP } else { //--- Static sl = NormalizeDouble(currBid + SL_Points * _Point, _Digits); //--- Set SL tp = NormalizeDouble(currBid - (SL_Points * _Point) * RR_Ratio, _Digits); //--- Set TP } if (obj_Trade.Sell(TradeVolume, _Symbol, currBid, sl, tp, "ORB Short Breakout")) { //--- Open sell if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success Print("Short Breakout: Entry at ", DoubleToString(currBid, _Digits), " SL at ", DoubleToString(sl, _Digits), " TP at ", DoubleToString(tp, _Digits)); //--- Log entry DrawEntryArrow(currentTime, currAsk, false); //--- Draw arrow tradedShort = true; //--- Set short traded } } } } }
オープニングレンジが完全に確定したら、まずrangeDefinedがまだfalseであればすぐに戻り、ブレイクアウトのロジックが早まって実行されないようにします。その後、ブレイクアウトを監視します。現在のAsk価格がrangeHighを上回り、かつbreakoutHighがまだ設定されていない場合は、強気ブレイクアウトとしてbreakoutHighをtrueに設定し、正確なブレイクアウト価格をbreakoutPriceに記録し、新しい突破が発生したことを示します。安値ブレイクアウトについても同様に処理します。少なくとも1方向のブレイクアウトが発生しており、このセッションでまだロング・ショートいずれのエントリーもおこなわれていない場合(!tradedLong && !tradedShort)、確認ステージに進みます。ConfirmBarsが0の場合、ブレイクアウトは即時に確認済みと見なされます。それ以外の場合は、レンジ時間足の各新しいバーごとに(シフト0のiTimeをlastConfirmTimeと比較して検出)、過去のConfirmBars本のバーがレンジ外で決定的に終値を付けたかをカウントします。強気ならrangeHigh以上、弱気ならrangeLow以下です。必要な確認バー数が揃ったときにのみconfirmedをtrueに設定します。
入力パラメータUseBreakoutFilterは尊重されますが、現状では確認結果をそのまま通すだけで、将来的に追加フィルターを組み込む余地があります。確認が済んだら、選択された方法に応じてストップロスとテイクプロフィットを計算します。強気ブレイクアウトの場合、買いポジションがMaxPositionsDir未満でまだロングがない場合、ダイナミックモードではストップロスをレンジ安値(正規化済み)に、テイクプロフィットを現在のAsk価格にレンジ幅×RR_Ratioを加えた位置に設定します。静的モードでは、ストップロスをアスク価格の下に固定のSL_Points、テイクプロフィットを同距離×比率上に置きます。その後、obj_Trade.Buyで買い注文を実行し、成功(TRADE_RETCODE_DONE)した場合、Expertsタブにエントリーの詳細を記録し、DrawEntryArrowで青色の上向き矢印を描画し、tradedLongをtrueに設定してこのセッションでの追加ロングを防ぎます。弱気ブレイクアウトも同様の手順で処理されます。コンパイルすると、次の結果が得られます。

画像から分かるように、ブレイクアウトが発生した時点でエントリーします。次に必要なのは、必要に応じて市場が有利な方向へ動いた際にトレーリングストップを適用し、ポジションを管理することです。
//+------------------------------------------------------------------+ //| Apply Points Trailing Stop | //+------------------------------------------------------------------+ void ApplyPointsTrailing() { double point = _Point; //--- Get point for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate positions if (PositionGetTicket(i) > 0) { //--- Check ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID) { //--- Check symbol magic double sl = PositionGetDouble(POSITION_SL); //--- Get SL double tp = PositionGetDouble(POSITION_TP); //--- Get TP double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get ticket if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Points * point, _Digits); //--- Calc new SL if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Points * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Points * point, _Digits); //--- Calc new SL if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Points * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } } } } } //--- Call the function per tick in the "OnTick" event handler if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing ApplyPointsTrailing(); //--- Apply trailing }
ここでは、Trailing_Pointsモードが選択されている場合にストップロスを動的に追従させ、市場が有利な方向に動いた際に利益を保護するためのApplyPointsTrailing関数を実装します。関数の冒頭では、_Pointを使用して銘柄のポイント値をpointに格納します。その後、インデックスの競合を避けながら安全に変更処理をおこなうため、全ポジションを後ろから順にループします。各有効なチケットについて、そのポジションが現在の銘柄に属しており、かつマジックナンバーがUniqueIDであることを確認します。そして、現在のストップロス、テイクプロフィット、建値、チケット番号を取得します。
買いポジションの場合、現在のBidから「Trailing_Stop_Points×point」を引いた値(銘柄の桁数に正規化)を新しいストップロス候補として計算します。この新しいレベルが既存のストップロスより高く(つまりストップを引き締める形で)、かつ未実現利益がMin_Profit_To_Trail_Points×pointを超えている場合にのみ変更を適用します。これにより、十分な利益バッファが生まれてからトレーリングが開始されます。ポジションはobj_Trade.PositionModifyで更新され、元のテイクプロフィットはそのまま保持されます。売りポジションの場合も、まったく同じロジックを反対方向で適用します。最後に、OnTickの末尾でトレーリングが有効か(TrailingType==Trailing_Points)かつポジションが存在するかを確認します。条件を満たす場合、ティックごとにApplyPointsTrailingを呼び出し、遅延なくリアルタイムで利益保護をおこないます。最後に、プログラムをチャートから削除した際には、作成した可視化オブジェクトを削除する必要があります。
//+------------------------------------------------------------------+ //| EA Stop Function | //+------------------------------------------------------------------+ void OnDeinit(const int code) { ObjectDelete(ChartID(), highLevelObj); //--- Delete high level ObjectDelete(ChartID(), lowLevelObj); //--- Delete low level ObjectDelete(ChartID(), highTextObj); //--- Delete high text ObjectDelete(ChartID(), lowTextObj); //--- Delete low text // Clean dynamic objects ObjectsDeleteAll(ChartID(), "ORB_Rectangle_", OBJ_RECTANGLE); //--- Delete rectangles ObjectsDeleteAll(ChartID(), "ORB_StartVLine_", OBJ_VLINE); //--- Delete start vlines ObjectsDeleteAll(ChartID(), "ORB_EndVLine_", OBJ_VLINE); //--- Delete end vlines ObjectsDeleteAll(ChartID(), "ORB_StartTime_Text_", OBJ_TEXT); //--- Delete start texts ObjectsDeleteAll(ChartID(), "ORB_EndTime_Text_", OBJ_TEXT); //--- Delete end texts ObjectsDeleteAll(ChartID(), "EntryMarker_", OBJ_ARROW); //--- Delete entry markers }
OnDeinit関数では、プログラムがチャートから削除されたとき、またはターミナルが終了するときに処理が実行されます。まず、名前で指定された4つの永続オブジェクトを削除します。具体的には、水平ラインであるhighLevelObjとlowLevelObj、そしてそれぞれのテキストラベルであるhighTextObjとlowTextObjです。そのあと、ObjectsDeleteAllを使用して、現在のセッションおよび過去のセッションで動的に作成されたすべてのオブジェクトを削除します。この完全なクリーンアップにより、複数のセッションやチャートの再読み込みによってオブジェクトが蓄積してしまうことを防ぎます。トレーリングストップを有効にしてコンパイルすると、次のような結果になります。

この可視化から分かるように、レンジを定義し、ポジションを取り、必要に応じてトレーリングストップを適用してポジションを管理することで、目的としていた動作を実現できています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストによって、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
本記事ではMQL5を用いて、セッションベースのオープニングレンジブレイクアウト(ORB)システムを開発しました。このシステムでは、セッション開始時刻やオープニングレンジの長さ(分単位)を自由に設定でき、選択した時間足に基づいて実際の高値と安値を自動的に特定します。また、任意で複数バーの終値による確認をおこないながらブレイクアウトを検出し、ブレイクした方向にのみエントリーします。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
このセッションベースのオープニングレンジブレイクアウト戦略を使えば、任意の市場セッションでのデイトレードのブレイクアウトセットアップに対応でき、今後の取引戦略の最適化にも活用することができます。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20339
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5入門(第28回):MQL5のAPIとWebRequest関数の習得(II)
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
分析型ボリュームプロファイル取引(AVPT):流動性アーキテクチャ、市場メモリ、アルゴリズム実行
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
こんにちは。親切なフィードバックありがとう。ブローカーの時刻の代わりに現地時刻を使用することで、コード内で時刻を定義することができます。例をご覧ください。
現地時間:
TimeLocal()取引サーバーを使用して、コンピュータの設定に従って直接時間を使用することもできます:
TimeTradeServer()GMT時間:
TimeGMT()また、以下のように専用の日時を定義することもできます:
最適な方法をお選びください。ありがとうございました。
あなたの問題は十分に明確ではありません。しかし、Local TimezonesとLocal Session Hours ライブラリを試すか、もっとシンプルなTimeServerDaylightSavingsを 試すことができます。時間調整なしでは、通常サマータイムやタイムゾーンの切り替えの影響を受ける履歴でストラテジーを確実にテストすることはできません。または、おそらく オンラインでタイムゾーンの変更を検出するためにブローカーのデイライト(サマータイム)スケジュールを決定 したいのでしょう 。
残念ながら、組み込みのMQL5 APIは、既製の、より雄弁なソリューションを提供していません。