English Русский Deutsch
preview
取引規律をコードに組み込む(第1回):MQL5でライブトレードに構造的な規律を実装する

取引規律をコードに組み込む(第1回):MQL5でライブトレードに構造的な規律を実装する

MetaTrader 5 |
14 0
Christian Benjamin
Christian Benjamin

内容



はじめに

トレードで利益を上げるためには、戦略の品質だけでは十分ではありません。戦略が論理的に設計され、バックテストで検証され、統計的に妥当であったとしても、実際の市場でプレッシャーを受けた際に規律が崩れれば、その戦略は期待どおりに機能しない可能性があります。市場は常に変化し、価格変動が大きく、心理的な負荷も伴います。しかし、規律はそのような環境でも一貫して維持されなければなりません。

次のような状況に心当たりがある場合、本記事は参考になるはずです。

  • 明確なルールを定めているにもかかわらず、実運用では市場のプレッシャーにより緩めたり無視したりしてしまうことがある。
  • 利益目標を達成した後も取引を続け、結果として利益を減らしてしまう。
  • 1日の取引回数やリスクの上限を設定していても、取引中にその制限を解除してしまう。
  • 損失を取り戻そうとして、あらかじめ決めていた損失停止ルールを守れなくなる。
  • ルールは文書上存在していても、リアルタイムでは自らを強制できない。

    これらの行動は、最終的に損失につながる可能性を高めます。その場の判断でルールを破ることは、やがて自分自身を追い詰める結果にもなりかねません。これは戦略そのものの問題ではなく、ルールを実際に機能させる仕組み(エンフォースメント)の問題です。

    本記事では、この問題を構造的な観点から解決します。MQL5を用いて、既存の売買ロジックと並行して動作するガバナンスレイヤーを設計し、取引状況をリアルタイムで監視するとともに、設定した制約を超えた場合には取引を自動的に停止・制御する仕組みを実装します。目的は、売買戦略そのものを作り直すことではありません。戦略と実際の運用との間に存在するエンフォースメントギャップを埋め、規律を裁量や意志力に頼るものではなく、システムによって自動的に実現されるものへと変えることです。


    なぜルールは守られないのか

    長年にわたり、ライブ市場で構造化されたFXシステムを観察してきた中で、一つの共通したパターンが見えてきました。それは、トレーダーはルールを理解しているにもかかわらず、実際の市場でプレッシャーにさらされると、執行が規律に基づいた計画から、状況に反応する意思決定へと変化してしまうということです。明確に定義された戦略として始まったものが、やがては価格の短期的な動きや自信、切迫感といった要因に左右された一連の行動へと変わっていきます。

    以下のチャートは、このような状況をよく表しています。有効な供給ゾーンが特定され、リスクも定義されており、最初のエントリーは計画どおりに実行されています。しかし、価格がそのゾーン内に留まり続けると、追加のエントリーがおこなわれます。それぞれのエントリーを個別に見れば妥当に思えるかもしれませんが、全体としてはエクスポージャーが拡大し、リスクが増加し、利益が徐々に失われていきます。変化したのはセットアップではなく、トレーダーの行動です。この例は、どれほど規律ある戦略であっても、ライブ市場のプレッシャー下では機能しなくなる可能性があることを示しています。


    問題の原因は、準備不足であることはほとんどありません。多くのトレーダーは、体系化された戦略を持ち、エントリーとエグジットのルール、1日の取引回数の上限、1回あたりのリスク上限、利益目標、ドローダウンの許容範囲などを明確に定義しています。その計画は一貫性があり、測定可能であり、理論上は十分に強制可能です。しかし、実際の市場環境に置かれると、執行は徐々に計画から逸脱し始めます。取引回数は増え、利益目標は取引中に引き上げられ、損失上限は無視され、ポジションサイズの基準も柔軟になっていきます。戦略そのものは存在し続けていますが、意思決定に対するその拘束力は次第に弱まっていきます。

    このような行動の逸脱は、新しくはありません。 歴史上最も著名な投機家の一人であるジェシー・リバモアは、自身の最も大きな損失について、市場分析の誤りではなく、規律を維持できなかったこと、すなわち感情や希望、あるいは外部からの影響に左右され、自ら定めたルールを守れなかったことが原因であると繰り返し述べています。

    根本的な原因は、エンフォースメントギャップにあります。文書化されたルールは意図を示すことはできますが、意思決定そのものには積極的に関与しません。トレーダーは制限を設定していてもそれを超えてしまい、利益目標を設定していても取引を続けてしまい、損失が発生したら停止すると決めていても、実際には損失を取り戻そうとしてしまいます。リアルタイムでの監視、カウント、および制約を適用する仕組みが存在しない限り、規律はその瞬間ごとの判断力に完全に依存することになります。そして、その判断力こそが、最も脆弱になるのがプレッシャーのかかった瞬間なのです。

    トレーダーは、その日のセッションをシンプルで規律ある目標から開始します。たとえば、「20ドルの利益を得たら取引を終了する」という目標です。この目標は控えめで、明確に測定可能であり、健全なリスク管理の原則にも合致しています。セッションの序盤では市場も思惑どおりに動き、1~2回の取引でその目標はすぐに達成されます。この時点で、計画は技術的には成功しています。しかし、それでも取引は続けられます。その変化は徐々に、そしてほとんど気付かないうちに起こります。市場環境は依然として良好に見え、自信は高まり、当初の目標は必要以上に保守的であるように感じられ始めます。

    制御された事前定義の目標として始まったものは、静かに拡大していきます。まずは50ドル、次に100ドル、そして最終的には、当初の計画には存在しなかった金額へと変化します。期待値が高まるにつれて、取引回数は増え、ポジションサイズは大きくなり、許容するリスクも徐々に引き上げられていきます。市場への関与は依然として高い状態ですが、その執行はもはや当初のルールに基づくものではありません。そして、やがて損失が発生すると、その場で取引を終了するのではなく、損失を取り戻そうという行動に移ります。セッション終了時には、最初に得た20ドルの利益は失われ、さらに追加の損失が積み重なっている可能性もあります。ルールは忘れられたのではありません。その瞬間に、意識的に脇へ置かれたのです。

    計画された規律と、ライブ環境での執行の現実

    項目 計画された規律 プレッシャー下の実運用での執行
    1日の目標
    +20ドルで取引を終了する
    取引中に目標が拡大する
    取引頻度
    制限され、厳選されている
    増加し、衝動的になる
    リスクエクスポージャー
    固定および事前定義済み
    徐々に拡大する
    意思決定の根拠
    セッション前の計画
    リアルタイムの感情と市場の勢い
    利益達成後の行動
    取引を終了する 取引を継続する
    結果
    小さく、管理された利益
    利益が失われる、または損失へ転じる

    チャートの例と日次利益目標の例は、どちらも同じ構造的な弱点を示しています。ルールは意図を示しているだけであり、実際の執行には関与していません。取引回数を数えたり、累積した結果を追跡したり、事前に定めた目標がすでに達成されていることを認識したりする仕組みが存在しないのです。

    構造的なエンフォースメントがなければ、プレッシャーや自信、あるいは新たな機会が生まれた瞬間に、規律は「守るかどうかを選択できるもの」になってしまいます。計画段階では十分に見えるルールであっても、ライブ市場では機能しないことが少なくありません。なぜなら、それらは記憶と判断力に依存しており、その両方が最も脆弱になるのが、プレッシャーが最も高まる瞬間だからです。


    なぜ心理的な対策だけでは不十分なのか

    規律の乱れには感情が関係することが多いため、トレード教育では心理面が重視される傾向があります。トレーダーは、マインドセットを改善し、感情的なレジリエンスを高め、より強い自己統制力を身につけるよう勧められます。こうした取り組みには価値があり、心理的な自己認識はパフォーマンスにおいて重要な役割を果たします。しかし、経験が繰り返し示しているように、心理的な準備だけでは十分ではありません。

    恐怖や自信、切迫感、そして苛立ちは、性格上の欠陥ではありません。それらは、不確実性や金銭的リスクに対する自然な反応です。経験の有無にかかわらず、ライブトレード中にこうした反応が消えることを期待するのは現実的ではありません。市場に対する深い理解を持つトレーダーであっても、実際の資金がリアルタイムの価格変動にさらされると、普段とは異なる行動を取ることがあります。

    この問題には、タイミングという側面もあります。ルール違反の多くは、長い内省や熟慮の末に起こるわけではありません。それらは非常に短時間で、場合によっては数秒以内に発生し、意図的な思考が介入する前に実行されてしまいます。意識的な判断が介入したときには、その意思決定はすでに実行された後であることが少なくありません。心理学は、なぜそのミスが起きたのかを説明する助けにはなりますが、プレッシャーが最も高まるその瞬間に、その行動自体を防ぐことはほとんどできません。

    行動を減速させたり、阻止したり、あるいは別の方向へ導いたりする外部の仕組みがなければ、精神的な規律にはそれを支える構造的な裏付けが存在しません。心理的な規律は自制を促すことはできますが、それを強制することはできません。



    規律をシステムの成果として実現する

    ある時点で、規律は内面的な自己統制だけに依存することはできないということが明らかになります。プレッシャーがかかると一貫して行動が変化してしまうのであれば、意思決定環境そのものにも制御機能の一部を担わせる必要があります。規律は、個人的な葛藤として捉える必要はありません。システムが生み出す成果として捉えることができます。

    取引環境そのものに境界や制約が組み込まれると、行動は自動的に変化します。制限は記憶しておくものではなく、監視されるものになります。ルール違反は、その場で判断や議論の対象となるのではなく、即座に検知されます。執行は、個人の自制心によって維持されるのではなく、システムの構造が逸脱を許さないことによって、あらかじめ定義された範囲内に保たれます。

    この考え方では、規律はその都度の意思決定から切り離され、設計の一部へと移されます。 プレッシャーそのものは依然として存在しますが、それが行動を左右することはありません。その役割はシステムの構造が担います。このように考えると、規律はトレーダーが絶えず維持しようと努力し続けるものではなくなります。規律は、取引環境が備える制約によって自然に生み出されるものとなるのです。



    ルールから制約へ

    この時点で、ルールを知っていることと、ルールを実際に強制することの違いが極めて重要になります。文書化されたガイドラインは意図を示すものであり、「何をするべきか」を定義します。しかし、それらは実際の売買執行には関与しません。ライブ市場のプレッシャー下では、この「意図」と「エンフォースメント」の間にあるギャップこそが、規律が最も崩れやすいポイントとなります。

    行動を監視できず、違反時に介入できないルールは、制御メカニズムではありません。

    ルールが継続的に取引活動を監視し、必要に応じて即座に介入できるよう取引システムへ組み込まれて初めて、それは規律を担保する実効的な制御手段となります。

    実例:コードに規律を組み込む

    ルールを制約へ変換する考え方が実際にどのように機能するのかを示すために、以下の例を見ていきます。いずれの例でも、ルール自体は事前に明確に定義されていることを前提としています。これらのコードスニペットは、意図的に最小限の構成としています。売買戦略を定義したり、ポジション管理を行ったりするものではありません。目的は、規律に関するルールをプログラムとして組み込み、リアルタイムで継続的に機能させる方法を示すことにあります。実際の運用では、イベント関数(例:OnInit、OnTimer)は、本番環境のEAに適切に統合する必要があります。

    例1:取引頻度の制限(オーバートレードの防止)

    裁量ルール:1日に実行する取引回数に上限を設けます。トレーダーは、序盤で利益を上げた後や、市場が長時間活発な状態にあると、この制限を忘れたり無視したりすることが少なくありません。その結果、オーバートレードが発生し、リスクが増大します。 

    以下のスニペットは、取引頻度を強制する仕組みを示しています。1日の取引回数をカウントし、設定した上限を超える取引は実行できないようにブロックします。

    制約として実装し、実際に取引をブロックする例

    #property strict
    
    input int MaxTradesPerDay = 5;
    
    int      tradesToday  = 0;
    datetime dayStartTime = 0;
    bool     tradeBlocked = false;
    
    //-------------------------------------------------------------------
    // Reset daily counters
    //-------------------------------------------------------------------
    void ResetDailyCounters()
      {
       tradesToday  = 0;
       dayStartTime = iTime(_Symbol, PERIOD_D1, 0);
       tradeBlocked = false;
      }
    
    //-------------------------------------------------------------------
    // Initialization
    //-------------------------------------------------------------------
    int OnInit()
      {
       ResetDailyCounters();
       return(INIT_SUCCEEDED);
      }
    
    //-------------------------------------------------------------------
    // Track new trade transactions and enforce
    //-------------------------------------------------------------------
    void OnTradeTransaction(const MqlTradeTransaction &trans,
                            const MqlTradeRequest &request,
                            const MqlTradeResult &result)
      {
       if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
          return;
    
    // Reset if new trading day
       datetime today = iTime(_Symbol, PERIOD_D1, 0);
       if(today != dayStartTime)
          ResetDailyCounters();
    
    // Select deal
       if(HistoryDealSelect(trans.deal))
         {
          long entryType = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
    
          if(entryType == DEAL_ENTRY_IN)
             tradesToday++;
         }
    
       if(tradesToday >= MaxTradesPerDay)
         {
          tradeBlocked = true;
          Alert("Daily trade limit reached — further trades blocked");
         }
      }
    
    //-------------------------------------------------------------------
    // Centralized trade gateway with enforcement
    //-------------------------------------------------------------------
    bool TryOpenOrder(MqlTradeRequest &req, MqlTradeResult &res)
      {
       if(tradeBlocked)
         {
          Print("Trade blocked: Daily limit exceeded");
          return false; // Actual blocking: trade not sent
         }
    
       if(!OrderSend(req, res))
         {
          Print("OrderSend failed: ", res.comment);
          return false;
         }
    
       return true; // Trade executed if allowed
      }
    

    取引活動は、取引トランザクションイベントを通じて客観的にカウントされます。取引回数が設定した上限に達すると、TryOpenOrder()がfalseを返すことで、それ以降の注文はブロックされ、新規取引は送信されません。この仕組みを既存のEAに組み込むには、OrderSend()の呼び出しをすべてTryOpenOrder()に置き換えます。たとえば、EAのOnTick()や売買シグナルを処理する関数では、「if (TryOpenOrder(request, result)) { /* success */ } else { /* blocked */ }」のように注文処理を記述します。これにより、売買戦略の中核となるロジックを変更することなく、ルールを強制できるようになります。

    例2:利益を保護する(利益達成後のオーバートレード防止)

    裁量ルール:日次の利益目標に到達したら取引を終了し、利益を確保するとともに、過度なエクスポージャーを防ぎます。

    制約として実装し、実際に取引をブロックする例

    // ---------- User Input ----------
    input double DailyProfitTarget = 2.0;   // Target profit in percent
    
    // ---------- Internal State ----------
    double   dayStartEquity       = 0.0;
    datetime dayStartTime         = 0;
    bool     profitLimitReached   = false;
    
    // ---------- Initialization ----------
    int OnInit()
      {
       ResetProfitTracking();
       EventSetTimer(1);   // Check every second
       return(INIT_SUCCEEDED);
      }
    
    // ---------- Reset Logic ----------
    void ResetProfitTracking()
      {
       dayStartTime       = iTime(_Symbol, PERIOD_D1, 0);
       dayStartEquity     = AccountInfoDouble(ACCOUNT_EQUITY);
       profitLimitReached = false;
      }
    
    // ---------- Monitoring Logic ----------
    void CheckDailyProfitLimit()
      {
       datetime today = iTime(_Symbol, PERIOD_D1, 0);
    
    // Reset if new trading day
       if(today != dayStartTime)
          ResetProfitTracking();
    
       if(profitLimitReached)
          return;
    
       double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
       double gainPercent   = ((currentEquity - dayStartEquity) / dayStartEquity) * 100.0;
    
       if(gainPercent >= DailyProfitTarget)
         {
          profitLimitReached = true;
          Alert("Daily profit target reached — further trading restricted");
         }
      }
    
    // ---------- Timer Event ----------
    void OnTimer()
      {
       CheckDailyProfitLimit();
      }
    
    //-------------------------------------------------------------------
    // Centralized trade gateway with enforcement (integrate with Example 1's TryOpenOrder)
    //-------------------------------------------------------------------
    bool TryOpenOrder(MqlTradeRequest &req, MqlTradeResult &res)
      {
       if(profitLimitReached)
         {
          Print("Trade blocked: Profit target reached");
          return false; // Actual blocking
         }
    // ... (add OrderSend logic here or merge with full gateway)
       return true;
      }
    //+------------------------------------------------------------------+
    

    システムは、その日の開始時点のエクイティを基準として、利益の増加を継続的に監視します。利益目標に到達するとフラグが設定され、その後はTryOpenOrder()が新規取引をブロックします。統合するには、一元的にルールを適用するため、profitLimitReachedの判定をTryOpenOrder()の共通処理へ追加します。

    例3:損失の拡大を抑制する(ドローダウン管理)

    裁量ルール:あらかじめ定義した1日の損失上限を超えた時点で取引を停止し、感情的な判断によって損失がさらに拡大することを防ぎます。

    制約として実装し、実際に取引をブロックする例

    // ---------- User Input ----------
    input double MaxDailyLoss = 3.0;   // Max daily loss in percent
    
    // ---------- Internal State ----------
    double   dayStartBalance    = 0.0;
    datetime dayStartTimeLoss   = 0;
    bool     lossLimitBreached  = false;
    
    // ---------- Initialization ----------
    int OnInit()
      {
       ResetLossTracking();
       EventSetTimer(1);   // Check every second
       return(INIT_SUCCEEDED);
      }
    
    // ---------- Reset Logic ----------
    void ResetLossTracking()
      {
       dayStartTimeLoss  = iTime(_Symbol, PERIOD_D1, 0);
       dayStartBalance   = AccountInfoDouble(ACCOUNT_BALANCE);
       lossLimitBreached = false;
      }
    
    // ---------- Monitoring Logic ----------
    void CheckDailyLossLimit()
      {
       datetime today = iTime(_Symbol, PERIOD_D1, 0);
    
       // Reset if new trading day
       if(today != dayStartTimeLoss)
          ResetLossTracking();
    
       if(lossLimitBreached)
          return;
    
       double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
       double lossPercent   = ((dayStartBalance - currentEquity) / dayStartBalance) * 100.0;
    
       if(lossPercent >= MaxDailyLoss)
         {
          lossLimitBreached = true;
          Alert("Daily loss limit breached — trading halted by constraint");
         }
      }
    
    // ---------- Timer Event ----------
    void OnTimer()
      {
       CheckDailyLossLimit();
      }
    
    //-------------------------------------------------------------------
    // Centralized trade gateway with enforcement (integrate with Example 1's TryOpenOrder)
    //-------------------------------------------------------------------
    bool TryOpenOrder(MqlTradeRequest &req, MqlTradeResult &res)
      {
       if(lossLimitBreached)
         {
          Print("Trade blocked: Loss limit breached");
          return false; // Actual blocking
         }
       // ... (add OrderSend logic here or merge with full gateway)
       return true;
      }

    損失の上限はリアルタイムで監視されます。上限を超えた場合、システムはその状態を即座に検知し、トレーダーの感情やその場の自信の有無に関係なく、取引を停止します。

    実用上の適用例

    これらのスニペットは、完全な売買システムとして設計されたものではありません。あくまでも、次のような重要な原則を示すための実装例です。

    • ルールを測定可能な条件へ変換する:記憶や手動による確認に依存するのではなく、制約を明示的にコードとして実装します。
    • 行動を継続的に評価する:監視は事後的ではなく、常時実行されます。
    • 違反が発生した瞬間に介入する:制約を超えた時点で即座にアクションを実行し、状況の悪化を防ぎます。

    このように、意図に依存する規律から構造的に強制される規律へと移行することが、本連載で扱うガバナンスレイヤーの基盤となります。

    制御あり


    制御なし


      エンフォースメントゲートウェイとモジュール型制約マネージャ

      前述の手続き的な実装例では、規律に関するルールをどのように監視できるかを示しました。しかし、実際のライブトレードでは、拡張性があり、確実にルールを適用できる仕組みが必要になります。そのため、本記事では一元化されたエンフォースメントゲートウェイとモジュール型制約マネージャを導入します。この構成により、すべての取引は実行される前に、現在適用中のすべての規律ルールに対して評価されます。


      上図は、シグナル生成から取引執行までのエンフォースメントパイプライン全体を示しています。すべての取引要求は、まず中央のエンフォースメントゲートウェイを通過し、その後、モジュール型制約マネージャによって評価されます。各制約は独立して動作し、その判定結果を共通の意思決定ノードへ通知します。そのため、いずれか一つでも制約違反が検出された場合、取引の実行は直ちにブロックされます。一方、実行された取引は、リアルタイムで制約の状態を更新するためにシステムへフィードバックされ、継続的なルールの適用が実現されます。


      一元化されたエンフォースメントゲートウェイ

      このアーキテクチャパターンでは、すべての取引操作は単一のゲートウェイ関数を経由します。このゲートウェイは、注文を送信する前に、すべての制約を順番に確認します。いずれか一つでも制約に違反している場合、その取引は自動的に拒否されます。これにより、オーバートレード、利益目標の超過後の取引、ドローダウン上限の超過といった状況を防ぐことができます。この仕組みによって規律は構造的に適用され、記憶、意思、あるいは感情に依存する必要がなくなります。
      bool TryOpenOrder(MqlTradeRequest &request, MqlTradeResult &result, ConstraintManager &manager)
      {
         if(manager.AnyBreached())
         {
            Alert("Trade blocked: ", manager.BreachReason());
            return false;  // Trade rejected
         }
      
         if(!OrderSend(request,result))
         {
            Print("OrderSend failed: ", result.comment);
            return false;
         }
      
         return true; // Trade executed successfully
      }
      
      すべての取引リクエストは、現在TryOpenOrder()を経由して処理されます。これにより、規律のエンフォースメントは自動的かつ一貫して適用されます。

      モジュール型制約フレームワーク

      監視とエンフォースメントを標準化するため、各規律ルールは共通インターフェースであるIConstraintを実装します。これにより、制約はモジュール化され、拡張可能であり、さらに相互に差し替え可能な構成になります。
      class IConstraint
      {
      public:
         virtual void ResetIfNewDay() = 0;
         virtual void UpdateOnTransaction(const MqlTradeTransaction &trans) = 0;
         virtual bool IsBreached() = 0;
         virtual string Reason() = 0;
      };
      

      このインターフェースは、抽象基底クラスを使用して実装されるデザイン契約パターンであり、MQL5で完全にサポートされています。

      • ResetIfNewDay():新しい取引日の開始時にカウンタをリセットする
      • UpdateOnTransaction():取引が発生するたびに内部状態を更新する
      • IsBreached():制約違反が発生している場合にtrueを返す
      • Reason():制約違反の内容を説明する、人間が理解可能なメッセージを返す
      制約マネージャ

      ConstraintManagerクラスは、すべての有効な制約を集約し、それらを評価した上で、エンフォースメントに関する判断をトレードゲートウェイへ通知します。
      class ConstraintManager
      {
      private:
         IConstraint* constraints[];
      public:
         void AddConstraint(IConstraint* c) { ArrayAdd(constraints, c); }
         void ResetAll() { for(int i=0;i<ArraySize(constraints);i++) constraints[i].ResetIfNewDay(); }
         void UpdateAll(const MqlTradeTransaction &trans) { for(int i=0;i<ArraySize(constraints);i++) constraints[i].UpdateOnTransaction(trans); }
      
         bool AnyBreached()
         {
            for(int i=0;i<ArraySize(constraints);i++)
               if(constraints[i].IsBreached()) return true;
            return false;
         }
      
         string BreachReason()
         {
            for(int i=0;i<ArraySize(constraints);i++)
               if(constraints[i].IsBreached()) return constraints[i].Reason();
            return "";
         }
      };
      

      本番環境のシステムでは、これらのオブジェクトは適切に破棄するか、静的割り当て、または適切なライフサイクル管理を使用して管理する必要があります。また、セッション中に取引トランザクションが発生しない場合でも、時間ベースの制約はタイマーイベントを使用してリセットできます。

      • AddConstraint():新しい規律ルールを追加する
      • ResetAll():新しい取引日に向けてすべての制約をリセットする
      • UpdateAll():取引後にすべての制約を更新する
      • AnyBreached() / BreachReason():いずれかのルールに違反しているかを確認し、その理由を通知する
      実践的な制約クラス

      ここまで説明した規律ルールは、このモジュール構成によって実装できるようになります。

      1. 取引頻度の制限
      class TradeFrequencyConstraint : public IConstraint
      {
      private:
         int tradesToday;
         datetime dayStart;
         int MaxTrades;
      public:
         TradeFrequencyConstraint(int maxTrades) { MaxTrades = maxTrades; ResetIfNewDay(); }
         void ResetIfNewDay() { tradesToday=0; dayStart=iTime(_Symbol,PERIOD_D1,0); }
         void UpdateOnTransaction(const MqlTradeTransaction &trans)
         {
            if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return;
            if(iTime(_Symbol,PERIOD_D1,0)!=dayStart) ResetIfNewDay();
            if(HistoryDealSelect(trans.deal))
               if(HistoryDealGetInteger(trans.deal,DEAL_ENTRY)==DEAL_ENTRY_IN)
                  tradesToday++;
         }
         bool IsBreached() { return tradesToday>MaxTrades; }
         string Reason() { return "Daily trade limit exceeded"; }
      };
      

      2. 利益の保護

      class DailyProfitConstraint : public IConstraint
      {
      private:
         double startEquity;
         datetime dayStart;
         bool breached;
         double targetPercent;
      public:
         DailyProfitConstraint(double target) { targetPercent=target; ResetIfNewDay(); }
         void ResetIfNewDay() { startEquity=AccountInfoDouble(ACCOUNT_EQUITY); dayStart=iTime(_Symbol,PERIOD_D1,0); breached=false; }
         void UpdateOnTransaction(const MqlTradeTransaction &trans) { if(iTime(_Symbol,PERIOD_D1,0)!=dayStart) ResetIfNewDay(); }
         bool IsBreached() { if(breached) return true; double gain=((AccountInfoDouble(ACCOUNT_EQUITY)-startEquity)/startEquity)*100.0; if(gain>=targetPercent) breached=true; return breached; }
         string Reason() { return "Daily profit target reached"; }
      };
      

      3. 損失拡大の抑制

      class DailyLossConstraint : public IConstraint
      {
      private:
         double startBalance;
         datetime dayStart;
         bool breached;
         double maxLossPercent;
      public:
         DailyLossConstraint(double maxLoss) { maxLossPercent=maxLoss; ResetIfNewDay(); }
         void ResetIfNewDay() { startBalance=AccountInfoDouble(ACCOUNT_BALANCE); dayStart=iTime(_Symbol,PERIOD_D1,0); breached=false; }
         void UpdateOnTransaction(const MqlTradeTransaction &trans) { if(iTime(_Symbol,PERIOD_D1,0)!=dayStart) ResetIfNewDay(); }
         bool IsBreached() { if(breached) return true; double loss=((startBalance-AccountInfoDouble(ACCOUNT_EQUITY))/startBalance)*100.0; if(loss>=maxLossPercent) breached=true; return breached; }
         string Reason() { return "Daily loss limit breached"; }
      };
      

      EAへの統合

      制約が定義されると、それらをライブEAに統合する作業はシンプルになります。初期化時に、各制約をConstraintManagerに登録します。その後、取引が発生するたびにOnTradeTransaction()がすべての制約をリアルタイムで更新し、すべての注文処理はTryOpenOrder()を経由します。これにより、システムは自動的に規律を適用できるようになります。制約違反は即座に検出され、オーバートレード、利益目標の超過、損失上限の突破といった問題を防止します。この仕組みにより、トレーダーが意識的に制御をおこなう必要はありません。

      ConstraintManager manager;
      manager.AddConstraint(new TradeFrequencyConstraint(5));
      manager.AddConstraint(new DailyProfitConstraint(2.0));
      manager.AddConstraint(new DailyLossConstraint(3.0));
      

      OnTradeTransaction()は、すべての制約をリアルタイムで更新します。

      void OnTradeTransaction(const MqlTradeTransaction &trans,
                              const MqlTradeRequest &request,
                              const MqlTradeResult &result)
      {
         manager.UpdateAll(trans);
      }
      

      取引執行:すべての注文は、ルールを自動的に適用するためにTryOpenOrder()を経由します。

      スコープ、フィルタリング、および取引計上

      ライブEAで制約を実用的に使用するためには、何を監視対象とするのか、また取引をどのようにカウントするのかについて、スコープを明確に定義することが重要です。

      • 銘柄単位か口座単位か:この実装では、各制約は銘柄単位で取引を追跡します。複数銘柄を扱うEAの場合、各銘柄ごとに個別の制約インスタンスを用意するか、追加の集約ロジックを実装する必要があります。
      • 手動取引:デフォルトでは、EAの外部で実行された手動取引も、HistoryDealSelectがそれらを取得できる場合には含めることができます。それ以外の場合、それらは無視されます。このフレームワークでは、どの取引をカウンタへ反映させるかを選択できます。
      • マジックナンバーフィルタリング:制約は、特定のMagicNumberによって開始された取引だけを対象に制限できます。これにより、同じ口座上で複数の戦略が動作している場合でも、戦略間の競合を防ぐことができます。

      取引カウントのルール

      •   カウント対象となるのは、成行エントリー取引(DEAL_ENTRY_IN)のみ 
      • 未決注文、部分約定、または決済取引は、制約ロジックで明示的に含めない限り、カウント対象になりません。
      //+------------------------------------------------------------------+
      //| Minimal Enforcement EA (Testing Demo Only)                       |
      //+------------------------------------------------------------------+
      #property strict
      input int MaxTradesPerDay = 2;
      
      int tradesToday = 0;
      datetime dayStartTime = 0;
      
      // Constraint Check Function
      bool TradeAllowed()
        {
         datetime today = iTime(_Symbol, PERIOD_D1, 0);
         if(today != dayStartTime)
           {
            tradesToday = 0;
            dayStartTime = today;
           }
      
         if(tradesToday >= MaxTradesPerDay)
           {
            Print("Trade blocked: Daily limit reached");
            return false;
           }
         return true;
        }
      
      // Trade Gateway
      bool TryOpenOrder(MqlTradeRequest &request, MqlTradeResult &result)
        {
         if(!TradeAllowed())
            return false;
      
         if(!OrderSend(request,result))
           {
            Print("OrderSend failed: ", result.comment);
            return false;
           }
      
         tradesToday++;
         Print("Trade executed successfully");
         return true;
        }
      
      // OnTick - test opening trades (demo only; in production, trigger on signals not every tick)
      void OnTick()
        {
         if(MathRand() % 1000 != 0)
            return; // Random throttle for safe testing; remove in real EA
      
         MqlTradeRequest req;
         MqlTradeResult  res;
         ZeroMemory(req);
         ZeroMemory(res);
      
      // Example market buy request
         req.action   = TRADE_ACTION_DEAL;
         req.symbol   = _Symbol;
         req.volume   = 0.1;
         req.type     = ORDER_TYPE_BUY;
         req.price    = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         req.magic    = 123456;
      
         TryOpenOrder(req,res);
        }
      //+------------------------------------------------------------------+
      //+------------------------------------------------------------------+
      

      対象範囲と対象読者

      本連載は、売買戦略の紹介や市場方向の予測、エントリー/エグジット手法の改善を扱うものではありません。また、トレーダーの判断を置き換えたり、裁量的な意思決定を自動化したりすることを目的としたものでもありません。本連載の対象範囲は、意図的に一つの目的に集中しています。それは、トレード規律を構造的に強制することです。

      中心となる目的は、あらゆるトレード手法と並行して動作するガバナンスレイヤーを、MQL5を用いて設計することです。その対象は、裁量型、自動売買型、またはハイブリッド型のいずれのアプローチにも適用できます。これらのレイヤーは、リアルタイムの行動を監視し、事前に定義されたリスク制限を適用し、設定した境界を超えた場合には一貫して介入します。これらはトレードエンジンではありません。シグナルを生成することも、ポジションを管理することも、取引を実行することもありません。その唯一の目的は、客観的な制約を取引環境へ組み込み、人間の意思決定がプレッシャーの影響を最も受けやすい場面において、構造による補強を提供することです。

      本連載は、主に以下の2つの読者層を対象としています。

      • 市場構造と執行を理解している経験豊富なトレーダー:ライブ市場環境において、より高い一貫性と強固な資金保護を求める方
      • 開発者および技術志向のトレーダー:既存システムへ行動制御やリスクガバナンスの仕組みを統合することに関心がある方

      トレードの基本原則に関する基礎知識と、MQL5プログラミングに関する基本的な理解を前提としています。高度な定量モデルや複雑な数学的フレームワークは必要ありません。連載全体を通じた重点は、実践性とアーキテクチャ設計にあります。衝動の制御や心理的なレジリエンスだけに依存するのではなく、意図的なシステム設計によって規律ある執行をどのように強化できるかを示します。


      結論

      本記事では、MQL5のトレード環境へ規律を直接組み込む方法を示しました。認識や意志力に依存するのではなく、裁量的なルールを測定可能な制約へ変換し、リアルタイム監視と、制限を超えた場合に執行をブロックする一元化されたエンフォースメントゲートウェイを実装しました。

      取引頻度の上限、日次利益の保護、損失拡大の抑制は、単なる文書化された意図から、実際に強制可能なシステムロジックへと変換されました。すべての取引操作はTryOpenOrder()を経由するため、違反時には警告が生成されるだけではなく、実際の取引執行そのものが停止されます。これが構造的な規律です。

      このフレームワークを実装することで、以下を得ることができます。

      • 定義済みで、強制可能なトレード制約
      • 注文執行を制御する一元化されたブロッキングゲートウェイ
      • 拡張可能なガバナンスを実現するモジュール型制約マネージャのテンプレート
      • 取引カウントおよび日次リセットに関する明確な管理ロジック
      • 規律が設計そのものに組み込まれた執行環境

      このフレームワークは、戦略ロジック自体を変更するものではありません。計画段階とライブ執行の間に存在するエンフォースメントギャップを解消することで、既存戦略を強化します。次回は、このアーキテクチャをさらに高度なガバナンスレイヤーへ拡張し、構造的な制御機能が、コアとなる戦略開発に干渉することなく、トレードシステムとともにどのように進化できるかを示します。

      本記事の説明および添付されたMQL5コードファイルは、厳密に教育目的で提供されています。これらは、トレード環境へ規律ルールを組み込む方法を示すための簡略化された例であり、金融アドバイスまたは完全なトレードシステムとして扱うべきものではありません。添付コードを使用する場合は、ライブ口座で運用する前に、十分なテストを実施してください。


      MetaQuotes Ltdにより英語から翻訳されました。
      元の記事: https://www.mql5.com/en/articles/21273

      添付されたファイル |
      test.mq5 (6.82 KB)
      流動性狩りの最適化:流動性狩りと市場構造転換の違いを見極める 流動性狩りの最適化:流動性狩りと市場構造転換の違いを見極める
      流動性狩り後のトレードセットアップをどのように活用するかを明確に解説することを目的とした、トレンドフォロー型EAに関する記事です。本記事では、流動性狩りおよび流動性一掃をトレードのエントリー条件として最適に活用したいトレーダー向けに特別に設計されたEAについて詳しく解説します。また、流動性狩りと市場構造転換を正しく区別する方法、それぞれが発生した際にどのように検証し活用すべきかについても取り上げます。これにより、両者を混同することで生じる損失を軽減することを目指します。
      MQL5取引ツール(第17回):ベクトルベースの角丸長方形と三角形を探る MQL5取引ツール(第17回):ベクトルベースの角丸長方形と三角形を探る
      MQL5のcanvasを使用して、ベクトルベースの手法で角丸長方形と三角形を描画する方法を解説します。さらに、アンチエイリアス処理を実現するためにスーパーサンプリングを利用します。実装では、スキャンライン塗りつぶし、円弧や接線に対する幾何学的事前計算、そして境界線描画を組み合わせることで、滑らかでカスタマイズ可能な図形を作成します。このアプローチは、今後の取引ツールにおけるモダンなUI要素を構築するための基盤となり、サイズ、角の半径、境界線、透明度などを入力で柔軟に設定できる設計になっています。
      MQL5取引ツール(第18回):向き制御対応の角丸吹き出し MQL5取引ツール(第18回):向き制御対応の角丸吹き出し
      角丸長方形と三角形のポインタを組み合わせることで、MQL5における角丸の吹き出しを作成する方法を紹介します。また、ポインタの向きを上、下、左、右に制御する方法についても解説します。さらに、形状のジオメトリの事前計算、スーパーサンプリングによる塗りつぶし、頂点部の丸みを持たせた円弧処理、および継ぎ目を滑らかに接続するための拡張比を用いた分割枠線の描画について詳しく説明します。サイズ、角丸半径、色、不透明度、線の太さなどを自由に設定できるコードを提供しており、トレーディングインターフェースのアラートやツールチップなどにそのまま利用できます。
      プライスアクション分析ツールキットの開発(第61回):3スイング検証による構造的傾斜トレンドラインブレイクアウト プライスアクション分析ツールキットの開発(第61回):3スイング検証による構造的傾斜トレンドラインブレイクアウト
      3点のスイングによる検証を基準として、客観的なプライスアクションシグナルを生成する、傾斜トレンドラインブレイクアウトツールを紹介します。このシステムは、スイング検出、トレンドライン構築、そしてクロス判定ロジックを用いたブレイクアウト確認を自動化することで、ノイズを低減し、運用・執行判断を標準化します。戦略ルールを解説し、MQL5による実装を示し、テスト結果を検証します。このツールは自動売買を目的としたものではなく、分析およびシグナル確認を支援するためのものです。