MQL5での取引戦略の自動化(第24回):リスク管理とトレーリングストップを備えたロンドンセッションブレイクアウトシステム
はじめに
前回の記事(第23回)では、MetaQuotes Language 5 (MQL5)でエンベロープトレンド取引向けのゾーンリカバリー(Zone Recovery)システムを強化し、トレーリングストップおよびマルチバスケット取引を導入して、利益保護とシグナル処理の精度を向上させました。今回の第24回では、ロンドン市場開場前のレンジを検出し、ペンディング注文(指値・逆指値注文)を自動的に配置する「ロンドンセッションブレイクアウトシステムを開発します。このシステムには、リスクリワード比率、ドローダウン制限、リアルタイム監視用のコントロールパネルなど、複数のリスク管理機能を組み込みます。本記事では以下のトピックを扱います。
この記事を読み終える頃には、高度なリスク管理機能を備えた完全なMQL5ブレイクアウトプログラムを作成できるようになります。それでは、さっそく始めましょう。
ロンドンセッションブレイクアウト戦略の理解
ロンドンセッションブレイクアウト戦略は、ロンドン市場の開場時に発生するボラティリティの上昇を狙う手法です。ロンドン市場が開く前の時間帯に形成される価格レンジを特定し、そのレンジからのブレイクアウトを捉えるためにペンディング注文を配置します。この戦略が重要である理由は、ロンドンセッションが一般的に高い流動性と活発な価格変動を伴うため、安定した利益機会を提供する可能性が高い点にあります。ただし、偽のブレイクアウトやドローダウンを回避するためには、慎重なリスク管理が欠かせません。
本システムでは、ロンドン開場前の高値と安値を計算し、一定のオフセットを加えた位置に買いストップおよび売りストップ注文を設定します。さらに、利益確定にはリスクリワード比率を基にしたテイクプロフィットを採用し、利益を確保するためのトレーリングストップを組み込みます。また、保有ポジション数および1日のドローダウンに上限を設け、資金を保護します。リアルタイム監視をおこなうためのコントロールパネルと、セッション特有の条件チェックを組み合わせることで、取引が定義された範囲内でのみおこなわれるよう制御します。これにより、変化する市場環境にも柔軟に対応できるシステムを構築します。要約すると、私たちが目指すのは次のような構成を持つシステムです。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、インジケーターフォルダを見つけ、[新規]タブをクリックして、表示される手順に従ってファイルを作成します。ファイルが作成されたら、コーディング環境でプログラムをより柔軟にするための入力パラメータや構造体を宣言していきます。
//+------------------------------------------------------------------+ //| London 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" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations //--- Enumerations enum ENUM_TRADE_TYPE { //--- Enumeration for trade types TRADE_ALL, // All Trades (Buy and Sell) TRADE_BUY_ONLY, // Buy Trades Only TRADE_SELL_ONLY // Sell Trades Only }; //--- Input parameters sinput group "General EA Settings" input double inpTradeLotsize = 0.01; // Lotsize input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection sinput int MagicNumber = 12345; // Magic Number input double RRRatio = 1.0; // Risk to Reward Ratio input int StopLossPoints = 500; // Stop loss in points input int OrderOffsetPoints = 10; // Points offset for Orders input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated? input bool UseTrailing = false; // Use Trailing Stop? input int TrailingPoints = 50; // Trailing Points (distance) input int MinProfitPoints = 100; // Minimum Profit Points to start trailing sinput group "London Session Settings" input int LondonStartHour = 9; // London Start Hour input int LondonStartMinute = 0; // London Start Minute input int LondonEndHour = 8; // London End Hour input int LondonEndMinute = 0; // London End Minute input int MinRangePoints = 100; // Min Pre-London Range in points input int MaxRangePoints = 300; // Max Pre-London Range in points sinput group "Risk Management" input int MaxOpenTrades = 2; // Maximum simultaneous open trades input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading //--- Structures struct PositionInfo { //--- Structure for position information ulong ticket; // Position ticket double openPrice; // Entry price double londonRange; // Pre-London range in points for this position datetime sessionID; // Session identifier (day) bool trailingActive; // Trailing active flag };
ロンドンセッションブレイクアウトシステムを実装するには、まず、<Trade\Trade.mqh>ライブラリをインクルードし、取引タイプを定義する列挙型、入力パラメータ、ポジション管理用の構造体を定義します。<Trade\Trade.mqh>をインクルードすることで、注文発注やポジション変更などの取引操作をおこなうためのCTradeクラスを利用できます。列挙型ENUM_TRADE_TYPEには、売買両方をおこなうTRADE_ALL、買いのみのTRADE_BUY_ONLY、売りのみのTRADE_SELL_ONLYを定義し、取引方向を制限できるようにします。
次に、入力パラメータを「General EA Settings」「London Session Settings」「Risk Management」の各グループに分けて設定します。「General EA Settings」では、ロットサイズ(inpTradeLotsize)を0.01に設定し、取引タイプ(TradeType)は列挙型を使用してデフォルトをTRADE_ALLにします。EAの取引を識別するためにMagicNumberを12345に設定し、リスクリワード比率(RRRatio)を1.0に、ストップロス距離(StopLossPoints)を500に設定します。エントリー位置のオフセット(OrderOffsetPoints)を10に設定し、片方の注文が成立した際に反対の指値注文を削除するかどうかを制御するDeleteOppositeOrderをtrueにします。トレーリングストップの使用を切り替えるUseTrailingはfalseに設定し、トレーリングの距離(TrailingPoints)を50に、トレーリングを開始するための最低利益幅(MinProfitPoints)を100に設定します。
「London Session Settings」では、ロンドンセッションの開始時刻としてLondonStartHourを9、LondonStartMinuteを0に設定し、終了時刻としてLondonEndHourを8、LondonEndMinuteを0に設定します。プレロンドン期間のレンジ幅を確認するために、最小レンジ(MinRangePoints)を100、最大レンジ(MaxRangePoints)を300に設定します。「Risk Management」では、同時に保有できる最大ポジション数を制限するためにMaxOpenTradesを2に設定し、1日の最大ドローダウン率を制御するためにMaxDailyDrawdownPercentを5.0に設定します。また、ポジションを追跡するための構造体「PositionInfo」を定義します。この構造体では、ポジションチケットを保持するticket、エントリー価格を保持するopenPrice、そのポジションにおけるプレロンドンレンジ幅を保持するlondonRange、セッション識別用の日付を保持するsessionID、そしてトレーリングが有効かどうかを示すbool値trailingActiveを管理します。コンパイルすると以下の出力が得られます。

このように入力項目を体系的に設定したうえで、次にプログラム全体で使用するいくつかのグローバル変数を定義します。
//--- Global variables CTrade obj_Trade; //--- Trade object double PreLondonHigh = 0.0; //--- Pre-London session high double PreLondonLow = 0.0; //--- Pre-London session low datetime PreLondonHighTime = 0; //--- Time of Pre-London high datetime PreLondonLowTime = 0; //--- Time of Pre-London low ulong buyOrderTicket = 0; //--- Buy stop order ticket ulong sellOrderTicket = 0; //--- Sell stop order ticket bool panelVisible = true; //--- Panel visibility flag double LondonRangePoints = 0.0; //--- Current session's Pre-London range PositionInfo positionList[]; //--- Array to store position info datetime lastCheckedDay = 0; //--- Last checked day bool noTradeToday = false; //--- Flag to prevent trading today bool sessionChecksDone = false; //--- Flag for session checks completion datetime analysisTime = 0; //--- Time for London analysis double dailyDrawdown = 0.0; //--- Current daily drawdown bool isTrailing = false; //--- Global flag for any trailing active const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute
ここではプログラムで使用するグローバル変数を定義します。obj_Tradeは取引操作をおこなうためのCTradeオブジェクトです。PreLondonHighとPreLondonLowはレンジの高値・安値を格納するdouble型変数で、PreLondonHighTimeとPreLondonLowTimeはそれぞれの時刻を表すdatetimes型です。buyOrderTicketおよびsellOrderTicketは指値注文チケットを格納するulong型で、panelVisibleはパネル表示の有無を示すbool型です。LondonRangePointsは現在セッションのプレ・ロンドンレンジ幅を表すdouble型で、positionListはPositionInfo構造体の配列です。lastCheckedDayは最終確認日を示すdatetime、noTradeTodayは当日の取引を禁止するフラグのbool、sessionChecksDoneはセッションチェック完了フラグのboolです。analysisTimeはロンドン分析の時刻を表すdatetime、dailyDrawdownは1日あたりのドローダウンを表すdouble、isTrailingは任意のトレーリングが有効かを示すboolです。最後にPreLondonStartHourとPreLondonStartMinuteはプレロンドン開始時刻を示す定数のint型として定義します。
これでプログラムのグローバル変数が定義できたので、次にコントロールパネルを作成します。これは最も簡単なステップであり、その後により複雑な取引ロジックへ進みます。ここではまず、パネル作成に必要な関数を定義します。
//+------------------------------------------------------------------+ //| Create a rectangle label for the panel background | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for panel elements | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 10, string font = "Arial") { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
ここでは、コントロールパネルのユーザーインターフェース(UI)要素を作成するためのユーティリティ関数を実装します。まず、createRecLabel関数を作成し、パネルの背景となる矩形ラベルを生成します。この関数はいくつかのパラメータを受け取ります。最初にResetLastError関数でエラーをリセットし、ObjectCreate関数を使用してOBJ_RECTANGLE_LABELオブジェクトを作成します。作成に失敗した場合はPrint関数でエラーをログに記録し、falseを返します。その後、ObjectSetInteger関数を使用してOBJPROP_XDISTANCEなど、必要なすべての整数型プロパティを設定します。設定が完了したらChartRedraw関数でチャートを再描画し、trueを返します。
次に、createLabel関数を作成し、パネル内にテキストラベルを生成します。この関数は「objName」「xD」「yD」「txt」「clrTxt」「fontSize」「font」といったパラメータを受け取ります。最初にResetLastErrorでエラーをリセットし、ObjectCreateでOBJ_LABELオブジェクトを作成します。作成に失敗した場合はPrintでログを出力し、falseを返します。続いて、createRecLabel関数と同様にObjectSetInteger関数で位置やサイズなどのプロパティを設定しますが、テキストラベルではさらにObjectSetString関数を使用してOBJPROP_TEXTおよびOBJPROP_FONTプロパティも設定します。最後にチャートを再描画し、trueを返します。これらの関数を使用することで、セッションデータやプログラムの状態を監視するための動的なコントロールパネルを構築および更新できるようになります。
string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects //+------------------------------------------------------------------+ //| Create the information panel | //+------------------------------------------------------------------+ void CreatePanel() { createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state } //+------------------------------------------------------------------+ //| Update panel with current data | //+------------------------------------------------------------------+ void UpdatePanel() { string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color }
ここでは、すべてのパネルオブジェクト名に共通の接頭辞を付けて整理しやすくするために、文字列「panelPrefix」を「LondonPanel_」として定義します。これにより、コントロールパネル内のオブジェクトを一元的に識別できるようになります。次に、情報パネルのUIを構築するCreatePanel関数を作成します。まず、createRecLabel関数を呼び出して、「panelPrefix + "Background"」という名前のパネル背景を生成します。位置は(10,10)、サイズは270×200、背景色はclrMidnightBlue、枠線の幅は1、枠の色はシルバーとします。続いて、createLabel関数を使用して、タイトル「London Breakout Control Center」を位置(20,15)、文字色ゴールド、フォントサイズ12で追加します。さらに、レンジ、最高値、最安値、買いレベル、売りレベル、残高、純資産、ドローダウンの各ラベルを、それぞれの位置に白色・フォントサイズ10で配置します。
非表示ボタンを作成するために、createRecLabelを呼び出し、「panelPrefix + "Hide"」という名前で、位置(250,10)、サイズ30×22、背景色clrCrimsonの矩形を作成します。次に、createLabel関数を使って、「panelPrefix + "HideText"」という名前で、CharToString(251)(Wingdingsフォント)を位置(258,12)、文字色clrWhite、フォントサイズ13で追加します。この非表示ボタンをインタラクティブにするために、ObjectSetInteger関数を使用してOBJPROP_SELECTABLEおよびOBJPROP_STATEをtrueに設定します。使用するWingdingsコードはデザインの好みに応じて選択可能です。以下に使用できるWingdingsコード一覧を示します。

次に、現在のデータでパネルを更新するUpdatePanel関数を実装します。まず、LondonRangePointsの値をDoubleToStringでフォーマットし、値が0の場合は「Calculating...」と表示するようにします。その後、ObjectSetString関数で「panelPrefix + "RangePoints"」のテキストを更新します。同様に、最高値、最安値、買いレベル(PreLondonHigh + OrderOffsetPoints * _Point )、売りレベル(PreLondonLow - OrderOffsetPoints * _Point)、残高(AccountInfoDouble(ACCOUNT_BALANCE))、純資産(AccountInfoDouble(ACCOUNT_BALANCE))、およびドローダウン(dailyDrawdown)をそれぞれフォーマットして更新します。
ドローダウンの色は、ObjectSetIntegerを使用して、dailyDrawdownがMaxDailyDrawdownPercent / 2を超える場合は黄色、それ以外の場合は白に設定します。最後に、これらの関数を初期化関数内で呼び出すことで、パネルを動作可能な状態にします。
//+------------------------------------------------------------------+ //| Initialize EA | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number ArrayFree(positionList); //--- Free position list CreatePanel(); //--- Create panel panelVisible = true; //--- Set panel visible return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Deinitialize EA | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ArrayFree(positionList); //--- Free position list }
OnInitイベントハンドラ内では、まず取引オブジェクトを使用してマジックナンバーを設定します。その後、ArrayFree関数を使ってポジションリストを解放し、CreatePanel関数を呼び出してパネルを生成します。パネルの作成が完了したら、パネルの表示フラグをtrueに設定します。次に、OnDeinitイベントハンドラ内では、ObjectsDeleteAll関数を使用して、指定した接頭辞を持つすべてのオブジェクトを削除します。さらに、もう使用しないポジションリスト配列をArrayFreeで解放します。コンパイルすると、次の結果が得られます。

パネルを作成できたので、次はキャンセルボタンに動きを加えて、クリックされた際にパネルを削除できるようにします。これを実現するために、OnChartEventイベントハンドラ内で処理をおこないます。
//+------------------------------------------------------------------+ //| Handle chart events (e.g., panel close) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click panelVisible = false; //--- Set panel hidden ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ChartRedraw(0); //--- Redraw chart } }
OnChartEventイベントハンドラ内では、まずイベントIDがオブジェクトクリックであるかどうかを確認します。クリックされたオブジェクトが非表示ボタンまたはキャンセルボタンであった場合、パネルの表示フラグをfalseに設定します。その後、パネルを構成するすべてのオブジェクトを削除し、変更を反映させるためにチャートを再描画します。コンパイルすると、次の結果が得られます。

可視化からわかるように、パネルは正常に動作し、クリック操作によって閉じられるようになりました。次のステップとして、パネルをさらに拡張し、すべての要素が初期化されるように更新していきます。そのためには、日次レンジを設定する関数と、ドローダウンを計算する機能を実装する必要があります。
//+------------------------------------------------------------------+ //| Check if it's a new trading day | //+------------------------------------------------------------------+ bool IsNewDay(datetime currentBarTime) { MqlDateTime barTime; //--- Bar time structure TimeToStruct(currentBarTime, barTime); //--- Convert time datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day if (currentDay != lastCheckedDay) { //--- Check new day lastCheckedDay = currentDay; //--- Update last day sessionChecksDone = false; //--- Reset checks noTradeToday = false; //--- Reset no trade buyOrderTicket = 0; //--- Reset buy ticket sellOrderTicket = 0; //--- Reset sell ticket LondonRangePoints = 0.0; //--- Reset range return true; //--- Return new day } return false; //--- Return not new day } //+------------------------------------------------------------------+ //| Update daily drawdown | //+------------------------------------------------------------------+ void UpdateDailyDrawdown() { static double maxEquity = 0.0; //--- Max equity tracker double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity if (equity > maxEquity) maxEquity = equity; //--- Update max equity dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded }
まず、IsNewDay関数を実装し、新しい取引日かどうかを確認します。この関数では、MqlDateTime構造体「barTime」を作成し、TimeToStruct関数を使用してcurrentBarTimeをその構造体に変換します。次に、barTime.year、barTime.mon、barTime.dayをStringFormatで結合し、StringToTime関数を使ってcurrentDayを算出します。currentDayがlastCheckedDayと異なった場合、lastCheckedDayを更新し、sessionChecksDoneとnoTradeTodayをfalseにリセットします。さらに、buyOrderTicketとsellOrderTicketを0に戻し、LondonRangePointsを0.0に設定して初期化します。その後、trueを返します。もし同じ日であれば、変更はおこなわずfalseを返します。この関数により、セッション分析や取引フラグが毎日自動的にリセットされます。
次に、日次リスクを監視するためのUpdateDailyDrawdown関数を実装します。この関数では、ピーク時の純資産を追跡するために、静的変数maxEquityを0.0で初期化します。現在の純資産をAccountInfoDouble(ACCOUNT_EQUITY)で取得し、もし現在の純資産がこれまでの最大値を上回る場合はmaxEquityを更新します。その後、maxEquityからの減少率を計算してdailyDrawdown(日次ドローダウン)を算出します。このドローダウンがMaxDailyDrawdownPercentに以上の場合、noTradeTodayをtrueに設定して、その日の取引を停止します。これにより、過剰な損失を防ぎ、リスク管理を強化することができます。最後に、これらの関数をOnTickイベントハンドラ内で呼び出し、リアルタイムでデータを更新しながらパネルに反映させます。
//+------------------------------------------------------------------+ //| Main tick handler | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time IsNewDay(currentBarTime); //--- Check new day UpdatePanel(); //--- Update panel UpdateDailyDrawdown(); //--- Update drawdown }
これにより、毎日の取引状況やリスク状態が自動的にモニタリングされ、最新情報が常にパネルに表示されるようになります。プログラムを実行すると、次の結果が得られます。

可視化から、パネルが最新のデータで更新され、現在のステータスを表示していることが確認できます。ここからは、より複雑なロジックであるセッションレンジの定義に進みます。まず、取引条件を確認し、条件が満たされた場合に注文を出す処理を実装します。その後、ポジション管理のロジックを追加していく流れになります。そこで、まずレンジを定義し、そのレンジを視覚化し、そして注文を出すための関数が必要になります。そのためのロジックを以下に実装します。
//+------------------------------------------------------------------+ //| Fixed lot size | //+------------------------------------------------------------------+ double CalculateLotSize(double entryPrice, double stopLossPrice) { return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size } //+------------------------------------------------------------------+ //| Calculate session range (high-low) in points | //+------------------------------------------------------------------+ double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) { int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time return (highVal - lowVal) / _Point; //--- Return range in points } //+------------------------------------------------------------------+ //| Place pending buy/sell stop orders | //+------------------------------------------------------------------+ void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) { double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket } if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } } //+------------------------------------------------------------------+ //| Draw session ranges on the chart | //+------------------------------------------------------------------+ void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) { string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID string preRectName = "PreRect_" + sessionID; //--- Rectangle name ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background }
レンジの計算、注文、チャート描画をおこなえるようにするため、まずは固定ロットサイズを計算するCalculateLotSize関数から始めます。この関数はentryPriceとstopLossPriceをパラメータとして受け取りますが、固定ロットを使用する場合はこれらの値は使いません。戻り値として、inpTradeLotsizeをNormalizeDouble関数で小数点第2位に丸めた値を返し、すべての取引で一貫したロットサイズを維持します。口座タイプによっては小数点以下の桁数を変更しても構いません。
次に、プレロンドンセッションのレンジを計算するGetRange関数を作成します。ここでは、iBarShift関数を使ってstartTimeとendTimeからstartBarとendBarを取得し、値が無効または「startBar < endBar」の場合は-1を返します。続いて、指定したバー範囲内での最高値バーをiHighest関数(MODE_HIGH)で、最安値バーをiLowest関数(MODE_LOW)で求めます。iHigh関数を使ってhighestBarの高値をhighValとし、iLow関数でlowestBarの安値をlowValとして取得します。また、highestBarの時間をiTimeでhighTime、lowestBarの時間をlowTimeとして記録します。最後に、レンジ幅を「(highVal-lowVal) / _Point」で計算し、その値を返します。
続いて、買いおよび売りのストップ注文を設定するPlacePendingOrders関数を定義します。この関数では、まずbuyPriceを「preLondonHigh + OrderOffsetPoints * _Point」として計算し、sellPriceを「preLondonLow - OrderOffsetPoints * _Point」として計算します。ストップロス幅を表すslPointsにはStopLossPointsを設定し、buySLは「buyPrice - slPoints * _Point」、sellSLは「sellPrice + slPoints * _Point」とします。さらに、テイクプロフィット幅tpPointsを「slPoints * RRRatio」として求め、buyTPは「buyPrice + tpPoints * _Point」、sellTPは「sellPrice - tpPoints * _Point」として設定します。ロットサイズについては、CalculateLotSize関数を使ってlotSizeBuyとlotSizeSellを算出します。
TradeTypeがTRADE_ALLまたはTRADE_BUY_ONLYの場合、obj_Trade.BuyStopを使って買いストップ注文を出します。パラメータとしてlotSizeBuy、buyPrice、buySL、buyTP、そして注文名「Buy Stop - London」を指定し、発注結果の注文チケットをResultOrderからbuyOrderTicketに保存します。同様に、TradeTypeがTRADE_ALLまたはTRADE_SELL_ONLYの場合は売りストップ注文を出します。
最後に、セッションレンジをチャート上に可視化するDrawSessionRanges関数を実装します。まず、sessionIDをIntegerToString(lastCheckedDay)を使用して「Sess_」に日付を付加した文字列として作成します。プレロンドンのレンジ矩形preRectNameは「PreRect_ + sessionID」とし、ObjectCreateを使ってOBJ_RECTANGLEとして生成します。座標にはPreLondonHighTime、PreLondonHighからPreLondonLowTime、PreLondonLowを指定し、OBJPROP_COLORをclrTeal、OBJPROP_FILLをtrue、OBJPROP_BACKをtrueに設定します。
次に、上限ラインpreTopLineNameを「PreTopLine_ + sessionID」として作成し、ObjectCreateでOBJ_TRENDを使用してpreLondonStart、PreLondonHighからlondonEnd、PreLondonHighまでを描画します。OBJPROP_COLORはclrBlack、OBJPROP_WIDTHは1、OBJPROP_RAY_RIGHTはfalse、OBJPROP_BACKはtrueに設定します。同様に、下限ラインpreBotLineNameを「PreBottomLine_ + sessionID」として作成し、preLondonStart、PreLondonLowからlondonEnd、PreLondonLowまでを範囲として赤色で描画します。これで、これらの関数を利用して取引条件をチェックする関数を定義できるようになります。
//+------------------------------------------------------------------+ //| Check trading conditions and place orders | //+------------------------------------------------------------------+ void CheckTradingConditions(datetime currentTime) { MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert time datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end analysisTime = londonStart; //--- Set analysis time if (currentTime < analysisTime) return; //--- Exit if before analysis double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges return; //--- Exit } LondonRangePoints = preLondonRange; //--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges }
CheckTradingConditions関数を実装し、ロンドンセッションブレイクアウトシステムにおけるセッション条件の判定と注文をおこないます。まず、MqlDateTime構造体timeStructを作成し、TimeToStruct関数を使って現在時刻をtimeStructに変換します。次に、timeStruct.year、timeStruct.mon、timeStruct.dayをStringFormatで結合し、StringToTime関数を用いてtodayを算出します。その後、preLondonStartをtodayにPreLondonStartHourとPreLondonStartMinuteを秒換算して加算した値、londonStartをtodayにLondonStartHourとLondonStartMinuteを加算した値、londonEndをtodayにLondonEndHourとLondonEndMinuteを加算した値として設定します。analysisTimeをlondonStartに設定し、現在時刻がこれより前であれば処理を終了します。
次に、GetRange関数を呼び出し、preLondonStart、currentTime、および参照渡しのPreLondonHigh、PreLondonLow、PreLondonHighTime、PreLondonLowTimeを引数として渡し、preLondonRangeを取得します。preLondonRangeがMinRangePointsより小さい、またはMaxRangePointsより大きい場合、noTradeTodayとsessionChecksDoneをtrueに設定し、DrawSessionRanges関数をpreLondonStartとlondonEndを引数として呼び出した後、処理を終了します。一方で、preLondonRangeが有効範囲内であれば、LondonRangePointsにpreLondonRangeを代入し、PlacePendingOrders関数をPreLondonHigh、PreLondonLow、およびtodayを引数として呼び出します。その後、noTradeTodayとsessionChecksDoneをtrueに設定し、DrawSessionRangesを再度呼び出します。このようにして、取引は有効なセッションレンジ内でのみおこなわれるようになります。シグナルのOnTickイベントハンドラで関数を呼び出すことができます。
if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions CheckTradingConditions(TimeCurrent()); //--- Check conditions }
本日まだ取引がおこなわれておらず、かつセッションチェックも完了していない場合、現在時刻に対して取引条件を確認するために関数を呼び出します。プログラムを実行すると、以下の結果が得られます。

この結果から、レンジが設定され、ペンディング注文が正しく出されたことが確認できます。レンジ幅は100ポイントであり、これは取引条件を満たしています。次のステップでは、注文済みの取引を管理していく処理に進みますが、その前に、どちらか一方の注文が約定(アクティブ)になった際に、残っているもう一方のペンディング注文を削除し、アクティブになったポジションをポジションリストに追加して管理できるようにします。
//+------------------------------------------------------------------+ //| Delete opposite pending order when one is filled | //+------------------------------------------------------------------+ void CheckAndDeleteOppositeOrder() { if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable bool buyOrderExists = false; //--- Buy exists flag bool sellOrderExists = false; //--- Sell exists flag for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders ulong orderTicket = OrderGetTicket(i); //--- Get ticket if (OrderSelect(orderTicket)) { //--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order } } //+------------------------------------------------------------------+ //| Add position to tracking list when opened | //+------------------------------------------------------------------+ void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) { if (londonRange <= 0) return; //--- Exit if invalid range int index = ArraySize(positionList); //--- Get current size ArrayResize(positionList, index + 1); //--- Resize array positionList[index].ticket = ticket; //--- Set ticket positionList[index].openPrice = openPrice; //--- Set open price positionList[index].londonRange = londonRange; //--- Set range positionList[index].sessionID = sessionID; //--- Set session ID positionList[index].trailingActive = false; //--- Set trailing inactive }
まず、どちらか一方の注文が約定した際に反対方向のペンディング注文を削除するためのCheckAndDeleteOppositeOrder関数を実装します。この関数では、DeleteOppositeOrderがfalseの場合、またはTradeTypeがTRADE_ALL以外の場合は、何もせずに処理を終了します。次に、buyOrderExistsとsellOrderExistsをfalseで初期化します。OrdersTotalを使って注文数を取得し、ループで後方から順にOrderGetTicketを呼び出して各注文チケットを取得します。OrderGetStringとOrderGetIntegerを使って注文が_SymbolおよびMagicNumberと一致し、そのチケットがbuyOrderTicketまたはsellOrderTicketと一致した場合、それぞれbuyOrderExistsまたはsellOrderExistsをtrueに設定します。
買い注文がなくなっていて売り注文が存在する場合は、obj_Trade.OrderDeleteを使用してsellOrderTicketを削除します。同様に、売り注文がなくなっていて買い注文が存在する場合は、buyOrderTicketを削除します。この関数は、どちらか一方の注文がトリガーされた際に反対方向のペンディング注文を削除し、約定後は一方向のみの取引となるようにします。
次に、オープンしたポジションを追跡するためのAddPositionToList関数を作成します。この関数では、londonRangeが0以下の場合、まだレンジが設定されていないため処理を終了します。次に、positionListの現在のサイズをArraySizeで取得し、ArrayResizeを使って配列を1つ拡張します。そして、positionList[index].ticket、openPrice、londonRange、sessionIDを設定し、trailingActiveをfalseに初期化します。これにより、トレーリングストップやセッションごとのデータを管理するためのポジションリストを維持することができます。これで、このロジックをOnTickイベントハンドラ内に実装できます。
CheckAndDeleteOppositeOrder(); //--- Delete opposite order // Add untracked positions for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position bool tracked = false; //--- Tracked flag for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked } if (!tracked) { //--- If not tracked double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list } } }
ここでは、CheckAndDeleteOppositeOrder関数を呼び出してペンディング注文を管理し、一方の方向の注文が約定した場合に、DeleteOppositeOrderの入力設定に従って反対方向の注文を削除することで、相反する取引が発生しないようにします。
次に、まだ追跡されていないポジションをpositionListに追加し、すべての関連するオープンポジションがトレーリングストップの監視対象となるようにします。PositionsTotalを使ってすべてのポジションをループし、PositionGetTicketで各ticketを取得します。PositionSelectByTicketが成功し、そのポジションがPositionGetStringとPositionGetIntegerを使って_SymbolおよびMagicNumberと一致する場合、まずtrackedフラグをfalseに設定します。次に、ArraySizeを使ってpositionListの要素数を取得し、内部ループでpositionList[j].ticketと照合して、そのticketがすでに登録されているかを確認します。trackedがfalseのままであれば、PositionGetDoubleを使ってPOSITION_PRICE_OPENからopenPriceを取得し、AddPositionToListをticket、openPrice、LondonRangePoints、lastCheckedDayの各引数とともに呼び出します。これにより、すべての該当ポジションが重複することなくpositionListに追加され、管理対象として適切に追跡されるようになります。以下はその結果です。

ここまでは完璧です。次にこれらのポジションを管理する関数を定義します。
//+------------------------------------------------------------------+ //| Remove position from tracking list when closed | //+------------------------------------------------------------------+ void RemovePositionFromList(ulong ticket) { for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list if (positionList[i].ticket == ticket) { //--- Match ticket for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements positionList[j] = positionList[j + 1]; //--- Copy next } ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array break; //--- Exit loop } } } //+------------------------------------------------------------------+ //| Manage trailing stops | //+------------------------------------------------------------------+ void ManagePositions() { if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing isTrailing = false; //--- Reset trailing flag double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double point = _Point; //--- Get point value for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions ulong ticket = positionList[i].ticket; //--- Get ticket if (!PositionSelectByTicket(ticket)) { //--- Select position RemovePositionFromList(ticket); //--- Remove if not selected continue; //--- Skip } if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching double openPrice = positionList[i].openPrice; //--- Get open price long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing double newSL = 0.0; //--- New SL variable if (positionType == POSITION_TYPE_BUY) { //--- Buy position newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL } else { //--- Sell position newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL } double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position positionList[i].trailingActive = true; //--- Set trailing active isTrailing = true; //--- Set global trailing } } } } }
ここでは、クローズされたポジションを追跡リストから削除し、トレーリングストップを管理するための関数を実装します。まず、ポジションがクローズされた際にpositionList配列を整理するためのRemovePositionFromList関数を作成します。この関数はticketをパラメータとして受け取り、ArraySizeでpositionListの要素数を取得してループします。positionList[i].ticketが引数のticketと一致した場合、内部ループでpositionList[j + 1]をpositionList[j]にコピーして要素を詰め、ArrayResizeを使って配列サイズを1つ減らします。その後、breakでループを抜けます。この関数により、クローズ済みポジションの不要なチェックを防ぎ、リストを常に最新の状態に保つことができます。特に、トレーリングや決済後の整理時に重要な処理です。
次に、オープン中の取引に対してトレーリングストップを処理するManagePositions関数を作成します。PositionsTotalが0、またはUseTrailingがfalseの場合は早期に終了します。続いてisTrailingをfalseにリセットし、SymbolInfoDoubleを使ってSYMBOL_BIDとSYMBOL_ASKからcurrentBidとcurrentAskを取得し、pointを_Pointとして取得します。次にArraySizeでpositionListの要素数を取得してループし、各ticketをPositionSelectByTicket関数で選択します。選択に失敗した場合はRemovePositionFromListを呼び出して次へ進みます。ポジションがPositionGetStringおよびPositionGetIntegerを使って_SymbolまたはMagicNumberと一致しない場合もスキップします。openPriceをpositionList[i]から取得し、PositionGetIntegerでpositionTypeを取得します。currentPriceをポジションタイプに応じて決定し、profitPointsをその差をpointで割って計算します。
profitPointsが「MinProfitPoints + TrailingPoints」以上である場合、買いポジションなら「currentPrice - TrailingPoints * point」、売りポジションなら「currentPrice + TrailingPoints * pointをnewSL」として計算します。PositionGetDoubleでcurrentSLを取得し、newSLがcurrentSLより少なくともpoint分有利であれば、PositionGetDoubleから取得した現在のTPとともにobj_Trade.PositionModifyでポジションを修正します。成功した場合、positionList[i].trailingActiveとisTrailingをtrueに設定します。これにより、利益を確保しながら、利益が伸びる取引を継続できるようになります。あとは、この関数をティックごとに呼び出してポジション管理をおこなうだけです。コンパイルすると、次の結果が得られます。

画像から分かるように、取引条件の確認、注文の発注、そしてそれらの管理が取引戦略に従って正しくおこなわれており、これによって目的を達成できていることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストの結果、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
まとめとして、私たちはMQL5でロンドンセッションブレイクアウトシステムを開発しました。このシステムは、事前セッションのレンジを分析してペンディング注文を発注し、カスタマイズ可能なリスクリワード比率、トレーリングストップ、多重取引制限を組み込み、さらにコントロールパネルでレンジ、レベル、ドローダウンをリアルタイムに監視できるようにしています。PositionInfo構造体のようなモジュール化されたコンポーネントを通じて、ブレイクアウト取引における規律あるアプローチを提供しており、セッション時間やリスクパラメータを調整することで、柔軟に改良可能です。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
ここで提示した概念と実装を活用することで、あなた自身の取引スタイルに合わせてこのブレイクアウトシステムを適応させ、アルゴリズム取引戦略を強化することができます。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18867
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5入門(第19回):ウォルフ波動の自動検出
知っておくべきMQL5ウィザードのテクニック(第76回): Awesome Oscillatorのパターンとエンベロープチャネルを教師あり学習で利用する
MQL5取引ツール(第6回):パルスアニメーションとコントロールを備えたダイナミックホログラフィックダッシュボード
データサイエンスとML(第46回):PythonでN-BEATSを使った株式市場予測
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
つ目の問題は、コントロール・パネルの高値・安値レベルと売買レベルが更新されないことです。
高値/安値レンジのレベルは明らかにチャート上に表示されているので、売買レベルもチャート上に表示され、高値/安値レンジのレベルから直接派生しているため、コントロールパネルで更新されるはずだと思います。
これを正しく動作させるために、何か提案はありますか?
よろしくお願いします。
あなたの2つ目の問題については、記事で説明されていますが、あなたの問題が貧弱なテストデータから生じていると仮定し、ヒントを与えると、レンジが計算中である場合、ロンドン・レンジ・セッションまたは入力で定義したセッションを設定するのに十分なデータがあるまで、常に "Calculating... "ステータスが表示されます。デフォルトの設定を使用していると仮定すると、ロンドン前の時間は3時間で、共有されたスクリーンショットの時間は2月13日で、22:00の2小節後は2*15分=30となり、したがって22:30はレンジ計算の時間外であるため、最初のセッションがまだ見つかっていない場合を除き、前の設定されたレンジがまだ有効であるため、パネル上のデータはまだ表示されているはずです。以下を参照してください:
レンジを見つけるための以下のロジックを参照してください。
そしてどのように設定されるのか。
下の画像をご覧ください。あなたのテストの年がわかりませんが、2025年とします。あなたのケースのように2020年であれば、そのための質の高いデータがありませんので、いずれにせよ、2025年を使用し、その結果、範囲計算は真夜中から始まるはずです。
画像から、23時55分のデータはそのままであることがわかります。しかし、午前0時になるとリセットされます。下をご覧ください。
他の範囲計算のために午前0時にデータをリセットしていることがわかります。実際、範囲計算が終了したとき、視覚化することによって、実際に何が行われたかを知ることができます。例えば、デフォルトの設定を使用した場合、0300 時から 0800 時までのラネッグ・バーが表示されます。以下をご覧ください:
これでまたはっきりしたかと思います。ご自分の取引スタイルに合わせて、すべてを調整することができます。あなたが直面している問題を避けるためには、信頼できるテストデータを使用することをお勧めします。ありがとうございました。
2つ目の問題については、記事で説明されていますが、テストデータが不十分であったことが問題であると仮定し、ヒントを提供すると、レンジが計算中である場合、ロンドンレンジのセッションまたは入力で定義したセッションを設定するのに十分なデータがあるまで、常に "Calculating... "のステータスが表示されます。デフォルトの設定を使用していると仮定すると、ロンドン前の時間は3時間で、共有されたスクリーンショットの時間は2月13日で、22:00の2小節後は2*15分=30となり、したがって22:30はレンジ計算の時間外であるため、最初のセッションがまだ見つかっていない場合を除き、前の設定されたレンジがまだ有効であるため、パネル上のデータはまだ表示されているはずです。以下を参照してください:
レンジを見つけるために、以下のロジックを参照する必要があるかもしれません。
そして、どのように設定されるのか。
下の画像を見てください。あなたのテストの年がわからないのですが、2025年とします。あなたのケースのように2020年であれば、そのための質の高いデータがないので、いずれにせよ、2025年を使用し、したがって範囲計算は真夜中から始まるはずです。
画像から、23時55分のデータはそのままであることがわかります。しかし、午前0時になるとリセットされます。下記参照。
他の範囲計算のために午前0時にデータをリセットしていることがわかります。実際、範囲計算が終了したとき、視覚化することによって、実際に何が行われたかを知ることができます。例えば、デフォルトの設定を使用した場合、0300 時から 0800 時までのラネッグ・バーが表示されます。以下をご覧ください:
これでまたはっきりしたかと思います。ご自分の取引スタイルに合わせて、すべてを調整することができます。あなたが直面している問題を避けるためには、信頼できるテストデータを使用することをお勧めします。ありがとうございます。
はい、記事を読み、私が説明した問題にぶつかるまで、私自身のコピーのコーディングに従いました。私が見たのは、デフォルトの時間帯でもパネルが更新されないことでした。私のスクリーンショットは、チャート上にボックスが描かれ、データが収集されているにもかかわらず、パネルが更新されていないことを示すものでした。さらに、ログには無効な価格やレベルに関するエラーメッセージはありませんでした。
私のバージョンにログメッセージを追加しました。そこから、レンジが大きすぎたり小さすぎたりするとパネルが更新されないことがわかりました。
テストデータの品質を再チェックしてみます。また、どのペアでテストされたかをご指摘いただきありがとうございます。
ご助力に感謝いたします。
はい、記事を読み、私が説明したような問題にぶつかるまで、自分なりにコーディングしました。私が見たのは、デフォルトの時間帯でもパネルが更新されなかったことです。私のスクリーンショットは、チャート上にボックスが描かれ、データが収集されているにもかかわらず、パネルが更新されていないことを示すものでした。さらに、ログには無効な価格やレベルに関するエラーメッセージはありませんでした。
私のバージョンにログメッセージを追加しました。そこから、レンジが大きすぎたり小さすぎたりするとパネルが更新されないことがわかりました。
テストデータの品質を再チェックしてみます。また、どのペアでテストされたかをご指摘いただきありがとうございます。
ご助力に感謝いたします。
もちろんです。
コードを共有していただきありがとうございます。
私自身もセッション依存のEAを書いたことがありますので、このコードが機能するのは、お使いのブローカーが常にGMT+1のタイムゾーンにあり、かつ英国夏時間を 使用している場合のみです。
それ以外のケースでは、開始時間は機能しません。なぜでしょうか?ロンドン・セッションは英国時間の午前8時に始まるからです。冬はGMT8:00、夏はGMT7:00です。
TimeCurrent() は、あなたのローカル時間を返すのではなく、常に取引サーバーからの時間を返します。