利益強化アーキテクチャ:多層型口座保護
目次
はじめに
現代のアルゴリズム取引において、高いリターンを追求することは必然的に、市場のボラティリティ、約定の不確実性、そして資本を急速に毀損し得るシステミックリスクへのエクスポージャー増大を伴います。このような環境で成功するためには、EAは利益機会を見つけるだけでなく、あらゆる瞬間においてどれだけのリスクを許容するかを賢く制御する必要があります。ここで多層的な口座保護が重要となります。これは単一の安全装置に依存するのではなく、適応的なロットサイズ、ボラティリティに応じたエクスポージャー調整、リアルタイムのパフォーマンス監視を統合した統一的な防御フレームワークです。各保護機構は独立しているのではなく、市場状況に応じて動的に進化する複数の層として機能します。
取引ロジックに保護機能を構造的、行動的、そして体系的に組み込むことで、EAは規律あるリスク姿勢を維持しながら、高成長戦略を追求することが可能になります。EAは、ポジションの拡大、縮小、そして資本保全のための完全停止のタイミングを学習します。エクイティフロア、ドローダウン階層、セグメント化された実行ロジック、サーキットブレーカー、リカバリープロトコルを通じて、この取引システムは静的なルールの集合ではなく、自己調整型の有機体のように振る舞います。その結果として、上昇機会を捉えつつ、リアルタイムでリスクを管理・無効化できる、インテリジェントで耐障害性の高い取引アーキテクチャが実現されます。
システムの概要と理解
EAは、ゴールド(XAUUSD)向けに設計された高度な自動売買システムであり、マーチンゲール型リカバリ戦略と多層的な口座保護機構を組み合わせています。中核となるロジックはリスク管理されたサイクル構造です。EMAクロスオーバーによって新たなトレンドを検出し、初回のポジションを構築します。その取引が損失となった場合、「リカバリーフェーズ」に入り、選択されたマーチンゲール方式に従ってロットサイズを増加させ、損失の回収を図ります。このプロセスは、あらかじめ定められたステップ数の範囲内で継続されます。いずれかの取引が利益となった時点で、一連のシーケンスはリセットされます。このコアエンジンは、マーチンゲールシステムに一般的に伴う壊滅的な損失を防ぐために設計された、包括的な安全フレームワークによって包まれています。主な保護レイヤーには、動的エクイティストップ、日次損失制限、取引頻度の制御、そして連続損失回数または特定のドローダウン閾値に達した際にすべての取引を停止するサーキットブレーカーが含まれます。
EAの保護ロジックは、資本を守るための複数のシールドやトリップワイヤーのように機能します。階層型の防御システムとして捉えることができます。まず、個々の売買はスリッページチェックおよびボラティリティに応じて調整されるストップロスによって保護されます。次に、口座全体は総エクイティを追跡するリアルタイムモニターによって保護されます。口座のピークエクイティからのドローダウンが設定された割合を超えた場合、システムは取引を完全に停止することが可能です。さらに、一定時間内の取引数を制限することでオーバートレードを防止する仕組みや、大きな損失シーケンスの後に自動的にリスクを低減し、取引再開前に待機期間またはエクイティの回復を要求するリカバリープロトコルも含まれます。この構造により、攻撃的なマーチンゲールのリカバリーメカニズムは、資本保全を最優先とする、事前に定義された合理的な制約のもとに常に抑制されます。
最後に、このシステムは永続的な状態管理を採用し、市場セッションやプラットフォームの再起動をまたいでも防御ロジックを維持します。グローバル変数を用いることで、ピークエクイティ、連続損失回数、および「リカバリ」状態で一時停止しているかどうかを記憶します。これにより、単一の稼働中だけでなく、常に一貫してルールを適用することが可能になります。例えば、金曜日にサーキットブレーカーに到達して取引が停止された場合でも、その状態は月曜日まで維持され、リスクの高い状況への即時再エントリーを防ぎます。この設計により、本EAは単なる自動売買スクリプトから、状態認識を備えた耐障害性の高い取引システムへと進化します。すなわち、利益獲得を段階的に追求しながらも、不利な市場環境を生き延び、何よりも取引口座を保護するよう根本的に設計されています。

導入手順
//+------------------------------------------------------------------+ //| GALEIT.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/ja/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ja/users/johnhlomohang/" #property version "1.00" //--- Include Libraries #include <Trade/Trade.mqh> //--- Global Variables CTrade trade; ulong lastTicket = 0; int martingaleStep = 0; double dailyProfit = 0.0; datetime lastTradeTime = 0; string tradeSequenceId = ""; //--- Global Variable Names #define GV_PEAK_EQUITY "GV_PeakEquity" #define GV_PAUSE_EA "GV_EA_Paused" #define GV_DD_LOCK_LEVEL "GV_DrawdownLockLevel" #define GV_CONSEC_LOSSES "GV_ConsecLosses" #define GV_TRADE_WINDOW_START "GV_TradeWindowStart" #define GV_TRADES_IN_WINDOW "GV_TradesInWindow" //--- Input Parameters input group "Trading Strategy" input int FastMAPeriod = 10; input int SlowMAPeriod = 50; input group "Martingale & Money Management" input double InitialLotSize = 0.01; input double LotMultiplier = 2.0; input int MaxMartingaleSteps = 5; input double RiskPercent = 2.0; input group "Volatility Management (ATR)" input int ATR_Period = 14; input double ATR_SL_Factor = 1.5; input double ATR_TP_Factor = 1.0; input group "Account Protection" input bool UseEquityStop = true; input double EquityStopPercent = 8.0; input bool UseDailyLossLimit = true; input double DailyLossPercent = 5.0; input bool UseMaxSpreadFilter = true; input int MaxSpreadPoints = 5; input group "Trailing Stop Parameters" input bool UseTrailingStop = true; input int BreakEvenAtPips = 500; input int TrailStartAtPips = 600; input int TrailStepPips = 100; input group "Circuit Breaker Settings" input int MaxConsecutiveLosses = 3; input double CircuitBreakerDD = 15.0; input group "Throttle Settings" input int MaxTradesPerHour = 10; input int ThrottleWindowSeconds = 3600; input group "Recovery Settings" input double RecoveryRiskReduction = 0.5; input double ResumeEquityPercent = 90.0; input int ResumeAfterSeconds = 86400;
まず最初に、EAが内部状態および取引挙動を追跡するために依存する、基本的なグローバル変数を定義します。 これには、注文実行のためのCTradeインスタンス、直近の取引チケットを保持する変数、マーチンゲールの進行状況、日次損益、さらに時間管理やシーケンス識別子などが含まれます。続いて、グローバル変数名のセットを宣言します。これらはMetaTraderの組み込みGlobalVariableシステムを用いて保存されます。これにより、ピークエクイティ、一時停止状態、ドローダウン階層、連続損失回数、取引ウィンドウの活動状況といった重要なリスク関連の状態を、MetaTraderの再起動やチャートの再読み込み後であっても維持することが可能になります。
次に、コアとなる取引戦略および市場環境との相互作用を定義する、グループ化された入力パラメータを導入します。その後、初期ロットサイズ、ロット倍率、最大リカバリステップ数、エクイティに対するリスク割合といった、マーチンゲールおよび資金管理の設定を指定します。「Volatility Management (ATR)」セクションでは、ATRに基づく動的なストップロスおよびテイクプロフィットの計算を取り入れており、固定のpips距離ではなく、市場のボラティリティに応じて取引の決済水準が自動的にスケーリングされるよう設計されています。
最後に、追加の入力グループを通じて複数層の保護ロジックを定義します。「Account Protection」セクションでは、エクイティストップ、日次損失制限、スプレッドフィルタリングといった、危険またはコストの高い市場条件下での取引を回避するための機能を管理します。トレーリングストップのパラメータにより、取引が有利に進行した際に利益を動的に確保することが可能になります。「Circuit Breaker」および「Throttle」の設定は、システム全体の安全装置として機能し、市場条件が過度にリスクの高い状態になった場合や、取引頻度が過剰になった場合に、取引を停止または制限します。最後に、「Recovery」セクションでは、保護機構による停止後にEAがどのように振る舞うべきかを定義しており、リスクをどの程度引き下げるか、そしてどのような条件下で安全に取引を再開できるかを規定します。これらの設定が組み合わさることで、多層的で適応性が高く、かつ耐障害性に優れた取引フレームワークが構築されます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("GaleIT EA Initialized"); trade.SetExpertMagicNumber(12345); tradeSequenceId = GenerateTradeSequenceId(); // Initialize all protection systems InitAccountShields(); InitFailSafes(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("GaleIT EA Deinitialized - Reason: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update protection systems UpdatePeakEquity(); TryResumeFromRecovery(); // Check if trading is allowed if(!IsTradingAllowed()) return; // Check all safety limits if(!CheckSafetyLimits()) return; // Manage existing positions ManageExistingPositions(); // Check for new trading opportunities if(IsNewBar()) { CheckForNewTrade(); } ManageOpenTrades(); }
初期化フェーズはOnInit()関数内で開始されます。この中でEAは起動メッセージを出力し、取引識別のための一意のマジックナンバーを設定し、現在の取引系列を追跡するためのtradeSequenceIdを生成します。これにより、EAが発行するすべての取引が一意にタグ付けされ、管理が容易になります。その後、EAは2つの重要な初期化関数、InitAccountShields()およびInitFailSafes()を呼び出します。これらの関数は、エクイティロック、日次損失トラッカー、サーキットブレーカー、リカバリ状態など、すべてのリスク管理レイヤーを準備し、EAがすべての保護システムを有効化し同期した状態で稼働を開始できるようにします。
EAが削除された場合やターミナルがシャットダウンされた場合には、OnDeinit()関数が呼び出され、MetaTraderから提供される理由コードとともに、EAが初期化解除されたことを示すメッセージがログに出力されます。この関数自体は重い処理を行いませんが、透明性とトレーサビリティを提供し、トレーダーや開発者がEAの停止が手動によるものか、再読み込みか、あるいはシステムイベントによるものかを把握できるようにします。適切な初期化解除はは、グローバル変数の状態の一貫性を維持し、将来のセッションにおいて古いデータが影響を及ぼすリスクを低減します。
コアとなる取引エンジンはOnTick()上で動作し、市場価格が変化するたびに実行されます。最初におこなわれる処理は、安全性とリカバリに関連するものです。EAはドローダウン状況を追跡するためにピークエクイティを更新し、以前にリカバリ状態または一時停止状態に入っていた場合には、取引の再開を試みます。E取引判断を行う前に、EAはIsTradingAllowed()によって取引が許可されているかを確認し、さらにCheckSafetyLimits()によって安全制限が超過していないかをチェックします。すべてが安全であると判断された場合、EAはポジションの管理、ストップの調整、および取引ロジックの評価へと進みます。新しいバーが形成されるたびに、CheckForNewTrade()が呼び出され、戦略ルールに基づいた新規エントリー機会を探索します。最後に、ManageOpenTrades()がトレーリングストップ、ブレークイーブンルール、部分決済などが正しく実行されるように管理し、各ティックごとの評価と実行のサイクルを完結させます。
//+------------------------------------------------------------------+ //| Check if trading is allowed | //+------------------------------------------------------------------+ bool IsTradingAllowed() { // Check if EA is paused by protection systems if(IsEAProtectedPaused()) return false; // Check circuit breaker if(CheckCircuitBreaker(MaxConsecutiveLosses, CircuitBreakerDD)) return false; // Check trade throttle if(!ThrottleAllowNewTrade(MaxTradesPerHour, ThrottleWindowSeconds)) return false; // Check spread filter if(UseMaxSpreadFilter) { long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); if(spread > MaxSpreadPoints * 10) return false; } return true; } //+------------------------------------------------------------------+ //| Check safety limits | //+------------------------------------------------------------------+ bool CheckSafetyLimits() { // Equity Stop protection if(UseEquityStop) { double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); double equityDropPercent = (1 - (currentEquity / currentBalance)) * 100; if(equityDropPercent >= EquityStopPercent) { Print("Equity stop triggered: ", equityDropPercent, "%"); CloseAllPositions(); ExpertRemove(); return false; } } // Daily loss limit if(UseDailyLossLimit) { double dailyLossLimit = (DailyLossPercent / 100) * AccountInfoDouble(ACCOUNT_BALANCE); if(dailyProfit <= -dailyLossLimit) { Print("Daily loss limit reached: ", dailyProfit); CloseAllPositions(); return false; } } return true; }
IsTradingAllowed()関数は、EAにおける最初のゲートキーパーとして機能し、新規取引の実行が安全かどうかを判断します。まず、リカバリーモードやドローダウンロックなどの保護メカニズムによってEAが一時停止されていないかを確認します。次に、サーキットブレーカーをチェックし、連続損失の増加や過度なドローダウンによって取引が停止されていないかを判定します。さらに、この関数はスロットリングルールを適用し、一定の時間枠内で実行できる取引数を制限します。最後に、スプレッドフィルターが有効な場合、現在の市場スプレッドが許容される最大値を超えていないかを確認します。これらすべてのチェックを通過した場合にのみ、EAは取引評価またはエントリー処理の実行を許可します。
CheckSafetyLimits()関数は、厳格な口座保護ルールを適用することで、第2層の防御として機能します。まず、エクイティストップロスシステムを評価します。これは、残高(バランス)からエクイティへの下落率を計算する仕組みです。この下落率が設定された閾値に達するか、またはそれを超えた場合、EAは直ちにすべてのポジションをクローズし、さらなる損失を防ぐために自身をチャートから削除します。次に、日次損失制限をチェックし、口座があらかじめ定義された1日の最大ドローダウンに到達した時点で取引を停止するようにします。これらいずれかの条件がトリガーされた場合、取引は停止され、EAはfalseを返して安全制限が破られたことを示します。両方の保護条件が安全範囲内にある場合にのみ、EAは取引サイクルの継続を許可します。
//+------------------------------------------------------------------+ //| Check for new trade opportunity | //+------------------------------------------------------------------+ void CheckForNewTrade() { if(PositionsTotal() > 0) return; int signal = GetTradingSignal(); if(signal != 0) { double lotSize = CalculateLotSize(); double sl, tp; CalculateSLTP(signal, sl, tp); if(OpenPosition(signal, lotSize, sl, tp)) { lastTradeTime = TimeCurrent(); martingaleStep = 0; tradeSequenceId = GenerateTradeSequenceId(); } } } //+------------------------------------------------------------------+ //| Get trading signal | //+------------------------------------------------------------------+ int GetTradingSignal() { int fastMA = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE); int slowMA = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE); if(fastMA == INVALID_HANDLE || slowMA == INVALID_HANDLE) return 0; double fastMAValues[], slowMAValues[]; ArraySetAsSeries(fastMAValues, true); ArraySetAsSeries(slowMAValues, true); if(CopyBuffer(fastMA, 0, 0, 3, fastMAValues) < 3) { IndicatorRelease(fastMA); IndicatorRelease(slowMA); return 0; } if(CopyBuffer(slowMA, 0, 0, 3, slowMAValues) < 3) { IndicatorRelease(fastMA); IndicatorRelease(slowMA); return 0; } double currentFast = fastMAValues[0]; double currentSlow = slowMAValues[0]; double prevFast = fastMAValues[1]; double prevSlow = slowMAValues[1]; int signal = 0; if(prevFast <= prevSlow && currentFast > currentSlow) signal = 1; else if(prevFast >= prevSlow && currentFast < currentSlow) signal = -1; IndicatorRelease(fastMA); IndicatorRelease(slowMA); return signal; } //+------------------------------------------------------------------+ //| Calculate lot size | //+------------------------------------------------------------------+ double CalculateLotSize() { if(martingaleStep == 0) { // Use risk-based lot sizing for first trade double atr = GetATR(_Symbol, _Period, ATR_Period); double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double slPrice = currentPrice - (atr * ATR_SL_Factor); return CalculateLotFromRisk(_Symbol, currentPrice, slPrice, RiskPercent); } else { // Martingale recovery trade return NormalizeDouble(InitialLotSize * MathPow(LotMultiplier, martingaleStep), 2); } } //+------------------------------------------------------------------+ //| Calculate Stop Loss and Take Profit | //+------------------------------------------------------------------+ void CalculateSLTP(int signal, double &sl, double &tp) { double atr = GetATR(_Symbol, _Period, ATR_Period); double currentPrice = signal > 0 ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); if(signal > 0) { sl = currentPrice - (atr * ATR_SL_Factor); tp = currentPrice + (atr * ATR_TP_Factor); } else { sl = currentPrice + (atr * ATR_SL_Factor); tp = currentPrice - (atr * ATR_TP_Factor); } // Validate distances double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double minDist = 100 * point; if(signal > 0) { if(currentPrice - sl < minDist) sl = currentPrice - minDist; if(tp - currentPrice < minDist) tp = currentPrice + minDist; } else { if(sl - currentPrice < minDist) sl = currentPrice + minDist; if(currentPrice - tp < minDist) tp = currentPrice - minDist; } int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); } //+------------------------------------------------------------------+ //| Open position | //+------------------------------------------------------------------+ bool OpenPosition(int signal, double lotSize, double sl, double tp) { double price = (signal > 0) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); ENUM_ORDER_TYPE orderType = (signal > 0) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; // Use protective execution return ExecuteProtectedOrder(_Symbol, orderType, lotSize, price, sl); } //+------------------------------------------------------------------+ //| Manage existing positions | //+------------------------------------------------------------------+ void ManageExistingPositions() { int totalPositions = PositionsTotal(); for(int i = totalPositions - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_COMMENT) == tradeSequenceId) { double currentProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TIME_UPDATE) > lastTradeTime) { bool wasProfit = (currentProfit > 0); OnTradeClosed(wasProfit); if(!wasProfit) { martingaleStep++; if(martingaleStep > MaxMartingaleSteps) { Print("Max martingale steps reached. Activating recovery protocol."); double reducedRisk = RiskPercent; RecoveryProtocol(reducedRisk, RecoveryRiskReduction, ResumeEquityPercent, ResumeAfterSeconds); martingaleStep = 0; } } else { martingaleStep = 0; } dailyProfit += currentProfit; lastTradeTime = TimeCurrent(); } } } }
取引エンジンはまず、新規ポジションの建玉が許可されているかどうかを評価し、既存のポジションが存在しないことを確認します。口座がクリアな状態であれば、システムは高速EMAと低速EMAのクロスによって生成される新たな売買シグナルを取得します。有効なシグナルが発生すると、EAは初回取引に対してはATRベースのリスク配分に基づいてロットサイズを動的に算出し、リカバリートレードに対しては構造化されたマーチンゲール増分に基づいてロットサイズを決定します。その後、ATR倍率に基づいてボラティリティ調整済みのストップロスおよびテイクプロフィット水準を算出し、ブローカーが要求する最小距離制限をすべて満たすようにします。すべての条件が検証された後、保護された実行関数を使用して安全に取引実行され、タイムスタンプおよび一意のシーケンスIDとともに取引セッションが初期化されます。
シグナル自体は、古典的な移動平均クロスロジックに基づいていますが、単一ティックの変動ではなく実際のクロスを確認するために、複数のバッファ要素を取得することで堅牢性が強化されています。ATRはロットサイズおよびSL/TP計算の中核を担っており、新規取引においてはストップロスまでの距離を基準としたリスクベースのロット算出に使用され、ポジション決済においては動的な利益目標および損失閾値の定義に使用されます。SLおよびTPの値は、銘柄の精度に合わせて正規化され、最小取引距離の制約を考慮したうえで制限されます。また、システムが買い(強気クロス)または売り(弱気クロス)のシグナルを受け取るかによって、それぞれ異なる値が適用されます。
ManageOpenTrades()関数は、保持ポジションをインテリジェントに管理し、その結果から学習する役割を担います。現在のシーケンスに紐づく取引が更新されるたびに、その取引が利益で終了したのか、損失で終了したのかが評価されます。利益で終了した場合はマーチンゲールのステップカウンタがリセットされ、損失で終了した場合はステップがインクリメントされ、必要に応じて追加のマーチンゲールエントリーがトリガーされます(ただし事前に定義された上限までに制限されます)。最大ステップに到達した場合、EAはリカバリープロトコルを発動し、リスクを低減するとともに、エクイティが安定するまで取引を停止します。その後、市場環境の改善またはクールダウン期間の経過を条件として取引を再開します。この仕組みにより、連続損失後の制御された回復を試みると同時に、過剰なエクスポージャーから口座を保護する、安全性を考慮した強化的なサイクルが形成されます。
double CalculateLotFromRisk(string symbol, double entry_price, double sl_price, double risk_percent) { if(risk_percent <= 0) return(InitialLotSize); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double risk_amount = equity * (risk_percent / 100.0); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double sl_points = MathMax(1.0, MathAbs(entry_price - sl_price) / point); 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) return(InitialLotSize); double value_per_point_per_lot = tick_value / (tick_size / point); double risk_per_lot = sl_points * value_per_point_per_lot; if(risk_per_lot <= 0.0) return(InitialLotSize); double volume = risk_amount / risk_per_lot; double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(step <= 0) step = 0.01; double normalized = MathFloor(volume / step) * step; if(normalized < minLot) normalized = minLot; if(normalized > maxLot) normalized = maxLot; return(NormalizeDouble(normalized, (int)MathMax(0, (int)(-MathLog10(step))))); } //+------------------------------------------------------------------+ //| STRUCTURAL ACCOUNT SHIELDS | //+------------------------------------------------------------------+ void InitAccountShields() { if(!GlobalVariableCheck(GV_PEAK_EQUITY)) GlobalVariableSet(GV_PEAK_EQUITY, AccountInfoDouble(ACCOUNT_EQUITY)); if(!GlobalVariableCheck(GV_PAUSE_EA)) GlobalVariableSet(GV_PAUSE_EA, 0.0); if(!GlobalVariableCheck(GV_DD_LOCK_LEVEL)) GlobalVariableSet(GV_DD_LOCK_LEVEL, 0.0); } void UpdatePeakEquity() { double current = AccountInfoDouble(ACCOUNT_EQUITY); double peak = GlobalVariableGet(GV_PEAK_EQUITY); if(current > peak) GlobalVariableSet(GV_PEAK_EQUITY, current); } double GetCurrentDrawdownPercent() { double peak = GlobalVariableGet(GV_PEAK_EQUITY); double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(peak <= 0) return(0.0); return ((peak - equity) / peak) * 100.0; } bool IsEAProtectedPaused() { return (GlobalVariableGet(GV_PAUSE_EA) != 0.0); } //+------------------------------------------------------------------+ //| TRADE-LEVEL REINFORCEMENT | //+------------------------------------------------------------------+ bool ExecuteProtectedOrder(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl_price) { if(IsEAProtectedPaused()) return false; double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double allowableDeviationPoints = 10; if(type == ORDER_TYPE_BUY) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if(MathAbs(ask - price) / point > allowableDeviationPoints) return false; } else { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); if(MathAbs(bid - price) / point > allowableDeviationPoints) return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(volume < minLot || volume > maxLot) return false; bool ok = false; if(type == ORDER_TYPE_BUY) ok = trade.Buy(volume, symbol, price, sl_price, 0, NULL); else if(type == ORDER_TYPE_SELL) ok = trade.Sell(volume, symbol, price, sl_price, 0, NULL); if(!ok) PrintFormat("Order failed: %s (err %d)", symbol, GetLastError()); return ok; } //+------------------------------------------------------------------+ //| SYSTEMIC FAIL-SAFES | //+------------------------------------------------------------------+ void InitFailSafes() { if(!GlobalVariableCheck(GV_CONSEC_LOSSES)) GlobalVariableSet(GV_CONSEC_LOSSES, 0); if(!GlobalVariableCheck(GV_TRADE_WINDOW_START)) GlobalVariableSet(GV_TRADE_WINDOW_START, TimeCurrent()); if(!GlobalVariableCheck(GV_TRADES_IN_WINDOW)) GlobalVariableSet(GV_TRADES_IN_WINDOW, 0); } void OnTradeClosed(bool wasProfit) { if(wasProfit) GlobalVariableSet(GV_CONSEC_LOSSES, 0); else GlobalVariableSet(GV_CONSEC_LOSSES, GlobalVariableGet(GV_CONSEC_LOSSES) + 1); GlobalVariableSet(GV_TRADES_IN_WINDOW, GlobalVariableGet(GV_TRADES_IN_WINDOW) + 1); } bool CheckCircuitBreaker(int maxConsecLosses, double drawdownThresholdPercent) { int consec = (int)GlobalVariableGet(GV_CONSEC_LOSSES); double dd = GetCurrentDrawdownPercent(); if(consec >= maxConsecLosses || dd >= drawdownThresholdPercent) { GlobalVariableSet(GV_PAUSE_EA, 1.0); Print("Circuit breaker engaged: consec=", consec, " dd=", dd); return true; } return false; } bool ThrottleAllowNewTrade(int maxTrades, int windowSeconds) { datetime start = (datetime)GlobalVariableGet(GV_TRADE_WINDOW_START); int count = (int)GlobalVariableGet(GV_TRADES_IN_WINDOW); datetime now = TimeCurrent(); if((now - start) > windowSeconds) { GlobalVariableSet(GV_TRADE_WINDOW_START, now); GlobalVariableSet(GV_TRADES_IN_WINDOW, 0); count = 0; } return (count < maxTrades); } void RecoveryProtocol(double &riskPercent, double reductionFactor, double resumeEquityPercentOfPeak, int resumeAfterSeconds) { riskPercent *= reductionFactor; GlobalVariableSet(GV_PAUSE_EA, 1.0); GlobalVariableSet("GV_RecoveryResumeTime", TimeCurrent() + resumeAfterSeconds); GlobalVariableSet("GV_ResumeEquityPercent", resumeEquityPercentOfPeak); } bool TryResumeFromRecovery() { double resumeTime = GlobalVariableGet("GV_RecoveryResumeTime"); if(resumeTime == 0) return false; if(TimeCurrent() < (datetime)resumeTime) return false; double resumePercent = GlobalVariableGet("GV_ResumeEquityPercent"); if(resumePercent <= 0) { GlobalVariableSet(GV_PAUSE_EA, 0.0); GlobalVariableSet("GV_RecoveryResumeTime", 0); return true; } double peak = GlobalVariableGet(GV_PEAK_EQUITY); double needed = peak * (resumePercent / 100.0); if(AccountInfoDouble(ACCOUNT_EQUITY) >= needed) { GlobalVariableSet(GV_PAUSE_EA, 0.0); GlobalVariableSet("GV_RecoveryResumeTime", 0); return true; } return false; }
CalculateLotFromRisk()関数は、ユーザーが定義したリスク割合を正確かつボラティリティを考慮したロットサイズへ変換することで、本エンジンの中核となる動的エクスポージャー制御を提供します。この関数は、口座エクイティ、エントリーからストップロスまでの距離(ポイント単位)、およびシンボルの1ポイントあたりの金銭価値に基づいて、1取引あたりに許容される金銭的リスクを算出します。これにより、EAは市場のボラティリティとトレーダーのリスク許容度の両方に比例したポジションサイズを設定することが可能になります。また、ブローカーの制約(最小ロット、最大ロット、ロットステップ)を適用し、すべての算出結果が実際に取引可能な範囲内に収まるようにします。最終的に正規化された出力は、高速に変動する市場環境においても、精度、準拠性、そして一貫性を備えたリスク配分を保証します。
次に、Structural Account Shieldsブロックは、エクイティの健全性を監視し、大きなドローダウンから口座を保護するための防御フレームワークを構築します。起動時にEAはピークエクイティを記録し、保護用変数を初期化した後、エクイティが新たな高値に到達するたびにそのピーク値を動的に更新します。これらの値に基づき、システムはピークに対する割合としてリアルタイムのドローダウンを測定し、エクイティのストレスレベルに応じた論理的判断をEAが行えるようにします。また、サーキットブレーカーやリカバリープロトコルといった外部モジュールが、保護閾値を超えた際にすべての取引を一時停止できるよう、一時停止フラグも用意されています。
Trade-Level Reinforcementシステムは、各注文の実行時における検証および保護に重点を置いています。EAは取引をサーバーへ送信する前に、保護モードが有効でないこと、実行価格が現在のBid/Askに十分近いこと(スリッページによる不利な約定を防止するため)、そしてロットサイズがブローカーの規則内に収まっていることを確認します。その後、注文はMQL5の取引オブジェクトを用いて発注され、追加の安全対策としてオプションでストップロスが設定されます。取引が失敗した場合には、EAは該当銘柄およびエラーコードをログに記録し、より高度なデバッグや適応的な処理を可能にします。この保護付き実行モデルにより、価格、ロットサイズ、実行条件のすべてにおいて適正である取引のみが市場に送られることが保証されます。
最後に、Systemic Fail-Safesモジュールは、EAの緊急安定化エンジンとして機能し、時間の経過に伴う取引挙動を監視して危険なパターンを検出します。このモジュールは、連続損失回数、一定時間内の取引数(スライディングウィンドウ)、および現在のドローダウンを追跡します。サーキットブレーカーは、損失の連鎖やドローダウンが閾値を超えた場合に取引を停止し、破壊的なスパイラルを防止します。スロットリングロジックは、定義された時間枠内で許可される取引数を制限し、ボラティリティが高い局面における過剰取引を抑制します。さらに状況が悪化した場合、リカバリープロトコルがリスクを低減し、クールダウン期間中は取引を停止します。また必要に応じて、エクイティがピークの一定割合まで回復するのを待ってから取引を再開します。この仕組みにより、資本を能動的に保護しつつ、健全な条件下でのみEAの運用を再開する、多層的なフェイルセーフシステムが構築されます。
バックテスト結果
バックテストは、M15時間足において約2か月間のテスト期間(2025年10月1日から2025年11月30日まで)で評価されました。設定は以下のとおりです。
| 変数 | 入力値 |
|---|---|
| FastMAPeriod | 20 |
| SlowMAPeriod | 160 |
| InitialLotSize | 1 |
| LotMultiplier | 5.2 |
| MaxMartingaleStep | 13 |
| RistPercent | 7.8 |
| ATR_Period | 69 |
| ATR_SL_Factor | 3.3 |
| ATR_TP_Factor | 9.8 |
| UseEquityStop | false |
| EquityStopPercentage | 27.2 |
| UseDailyLossLimit | true |
| DailyLossPercent | 12.7 |
| UseMaxSpreadFilter | true |
| MaxSpreadPoints | 25 |
| UseTrailingStop | true |
| BreakEvenAtPips | 500 |
| TrailStartAtPips | 600 |
| TrailStepPips | 100 |
| MaxConsecutiveLosses | 22 |
| CircuitBreaketDD | 115.5 |
| MaxTradesPerHour | 33 |
| ThrottleWindowSeconds | 21025 |
| RecoveryRiskReduction | 3.6 |
| ResumeEquityPercent | 135 |
| ResumeAfterSeconds | 845307 |


結論
要約すると、我々は動的なエクスポージャー制御、構造的なエクイティ保護、取引レベルでの実行強化、そしてシステム全体のフェイルセーフを統合した、多層的な口座保護フレームワークを開発しました。このアーキテクチャは、リスクベースのロットサイズ計算、ピークエクイティの追跡、制御されたドローダウンロック、スリッページを考慮した注文検証、サーキットブレーカー、そしてスロットリング機構を組み合わせることで、すべての取引が知的にサイズ調整され、かつ責任を持って管理されることを保証します。これらのコンポーネントを統合された保護エンジンとして階層化することにより、システムはボラティリティに継続的に適応し、エクイティの健全性を監視し、取引環境が不利になった場合には自動的に介入します。
結論として、この多層的な保護モデルは、資本保全を最優先としながらも高いパフォーマンスを追求できる、極めて耐障害性の高い自己調整型の環境をトレーダーに提供します。単一の防御機能に依存するのではなく、EAは協調的に動作する複数の保護機構のネットワーク内で動作し、感情的な判断を最小化し、破滅的な損失スパイラルを防ぎ、持続的な取引の長期安定性を確保します。これによりトレーダーは、市場のあらゆる局面においてシステムがリスクを能動的に管理し、パフォーマンスを安定化させ、口座を保護するよう設計されているという安心感のもとで、より高い利益を追求することが可能になります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20449
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
共和分株式による統計的裁定取引(第8回):ポートフォリオのリバランスのためのローリングウィンドウ固有ベクトル比較
MQL5で他の言語の実用的なモジュールを実装する(第5回):PythonのLoggingモジュールによるプロ仕様のログ
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
取引戦略の開発:出来高制限アプローチの使用
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索