
MQL5での取引戦略の自動化(第19回):Envelopes Trend Bounce Scalping - 取引執行とリスク管理(その2)
はじめに
前回の記事(第18回)では、MetaQuotes Language 5 (MQL5)でのEnvelopes Trend Bounce Scalpingの基盤を構築し、エキスパートアドバイザー(EA)のコアインフラやシグナル生成ロジックを作成しました。第19回では、取引実行とリスク管理を実装し、戦略を完全に自動化します。次のトピックについて説明します。
この記事を読み終える頃には、トレンドバウンスをスキャルピングする完全なMQL5取引システムが完成し、パフォーマンス最適化も施されます。さっそく始めましょう。
戦略ロードマップとアーキテクチャ
第18回では、Envelopes Trend Bounce Scalping戦略の基盤を確立し、エンベロープ指標との価格の相互作用を利用して取引シグナルを検出するシステムを構築しました。シグナルは、移動平均やRSIなどのトレンドフィルターによって確認されます。市場状況を監視し、潜在的な取引機会を特定するためのインフラ構築に重点を置きましたが、取引実行やリスク管理はまだ実装していませんでした。第19回では、取引実行を有効化し、リスク管理を実装することにロードマップを移行させ、戦略がシグナルに基づいて効果的かつ安全に行動できるようにします。
私たちのアーキテクチャ計画では、モジュール化されたアプローチを優先し、シグナルを取引に変換する明確な流れを作りつつ、安全装置を組み込みます。アーキテクチャ計画では、モジュール化を優先し、シグナルを取引へ変換する明確な流れを作りながら、安全装置を組み込みます。具体的には、検証済みのシグナルに基づいて買い・売り注文を発注するメカニズムを開発し、ストップロスやテイクプロフィットを設定するリスク管理フレームワークを整備し、口座残高に応じてポジションサイズを調整し、資本を保護するために総損失を制限する仕組みを組み込みます。この設計により、一体感のある自動スキャルピングシステムが構築されます。さらに、取引を管理するための追加クラスを定義し、すべてのロジックをティック処理に組み込むことで、システムを実際に稼働させます。要するに、私たちが目指すのはこのようなシステムです。
MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルを作成したら、コーディング環境で複数のインターフェースやクラスを宣言し、シグナル生成の高度化、コード整理、取引管理の準備を進めます。まずは基本的なシグナル表現のインターフェースから始めましょう。
//--- Define interface for strategy signal expressions interface IAdvisorStrategyExpression { bool Evaluate(); //--- Evaluate signal bool GetFireOnlyWhenReset(); //--- Retrieve fire-only-when-reset flag void SetFireOnlyWhenReset(bool value); //--- Set fire-only-when-reset flag void ResetSignalValue(); //--- Reset signal value }; //--- Define base class for advisor signals class ASSignal : public IAdvisorStrategyExpression { private: bool _fireOnlyWhenReset; //--- Store fire-only-when-reset flag int _previousSignalValue; //--- Store previous signal value int _signalValue; //--- Store current signal value protected: //--- Declare pure virtual method for signal evaluation bool virtual EvaluateSignal() = 0; //--- Require derived classes to implement public: //--- Initialize signal void ASSignal() { _fireOnlyWhenReset = false; //--- Set fire-only-when-reset to false _previousSignalValue = -1; //--- Set previous value to -1 _signalValue = -1; //--- Set current value to -1 } //--- Evaluate signal bool Evaluate() { if (_signalValue == -1) { //--- Check if signal uncomputed _signalValue = EvaluateSignal(); //--- Compute signal } if (_fireOnlyWhenReset) { //--- Check fire-only-when-reset return (_previousSignalValue <= 0 && _signalValue == 1); //--- Return true if signal transitions to 1 } else { return _signalValue == 1; //--- Return true if signal is 1 } } //--- Retrieve fire-only-when-reset flag bool GetFireOnlyWhenReset() { return _fireOnlyWhenReset; //--- Return flag } //--- Set fire-only-when-reset flag void SetFireOnlyWhenReset(bool value) { _fireOnlyWhenReset = value; //--- Set flag } //--- Reset signal value void ResetSignalValue() { _previousSignalValue = _signalValue; //--- Store current as previous _signalValue = -1; //--- Reset current value } };
取引シグナルを効率的に扱うためのフレームワークを構築するにあたり、モジュール化され、信頼性の高いシグナル評価システムに注力します。まずIAdvisorStrategyExpressionインターフェースを定義し、シグナル操作の一貫した設計図として利用します。このインターフェースには4つの主要な関数が含まれています。Evaluate関数はシグナルがアクティブかどうかを判定し、GetFireOnlyWhenReset関数はシグナル発火を制御するフラグを確認し、SetFireOnlyWhenReset関数はそのフラグを変更し、ResetSignalValue関数は次の評価のためにシグナルの状態をクリアします。
次にASSignalクラスを開発します。このクラスはIAdvisorStrategyExpressionインターフェースを実装し、戦略内の特定シグナルの基盤として機能します。ASSignalクラス内では3つのプライベート変数を定義します。「_fireOnlyWhenReset」はシグナルがリセット後のみ発動するかを制御し、「_previousSignalValue」は前回のシグナル状態を追跡し、「_signalValue」は現在の状態を保持します。これらはASSignalコンストラクタで初期化され、「_fireOnlyWhenReset」はfalse、「_previousSignalValue」と「_signalValue」は-1に設定され、未計算の状態を示します。Evaluate関数は「_signalValue」が-1であるかどうかをチェックし、純粋仮想関数であるEvaluateSignalを呼び出してシグナルを計算します。そして「_fireOnlyWhenReset」に基づき、シグナル値が1のとき、または「_previousSignalValue」が非正から1に変化したときにtrueを返します。
シグナル挙動を管理するために、GetFireOnlyWhenReset関数で「_fireOnlyWhenReset」フラグの値を取得し、SetFireOnlyWhenReset関数でその値を更新することで、シグナルが発動するタイミングを調整できます。またResetSignalValue関数を使用して、「_signalValue」を「_previousSignalValue」に格納し、「_signalValue」を-1にリセットすることで、次の評価サイクルに備えます。EvaluateSignalを純粋仮想としてマークすることで、派生クラスは必ず具体的なシグナルロジックを提供する必要があり、柔軟性が確保されます。これで、シグナル管理ロジック用の追加クラスを定義する準備が整いました。
//--- Define class for managing trade signals class TradeSignalCollection { private: IAdvisorStrategyExpression* _tradeSignals[]; //--- Store array of signal pointers int _pointer; //--- Track current iteration index int _size; //--- Track number of signals public: //--- Initialize empty signal collection void TradeSignalCollection() { _pointer = -1; //--- Set initial pointer to -1 _size = 0; //--- Set initial size to 0 } //--- Destructor to clean up signals void ~TradeSignalCollection() { for (int i = 0; i < ArraySize(_tradeSignals); i++) { //--- Iterate signals delete(_tradeSignals[i]); //--- Delete each signal } } //--- Add signal to collection void Add(IAdvisorStrategyExpression* item) { _size = _size + 1; //--- Increment size ArrayResize(_tradeSignals, _size, 8); //--- Resize array with reserve _tradeSignals[(_size - 1)] = item; //--- Store signal at last index } //--- Remove signal at index IAdvisorStrategyExpression* Remove(int index) { IAdvisorStrategyExpression* removed = NULL; //--- Initialize removed signal as null if (index >= 0 && index < _size) { //--- Check valid index removed = _tradeSignals[index]; //--- Store signal to remove for (int i = index; i < (_size - 1); i++) { //--- Shift signals left _tradeSignals[i] = _tradeSignals[i + 1]; //--- Move signal } ArrayResize(_tradeSignals, ArraySize(_tradeSignals) - 1, 8); //--- Reduce array size _size = _size - 1; //--- Decrement size } return removed; //--- Return removed signal or null } //--- Retrieve signal at index IAdvisorStrategyExpression* Get(int index) { if (index >= 0 && index < _size) { //--- Check valid index return _tradeSignals[index]; //--- Return signal } return NULL; //--- Return null for invalid index } //--- Retrieve number of signals int Count() { return _size; //--- Return current size } //--- Reset iterator to start void Rewind() { _pointer = -1; //--- Set pointer to -1 } //--- Move to next signal IAdvisorStrategyExpression* Next() { _pointer++; //--- Increment pointer if (_pointer == _size) { //--- Check if at end Rewind(); //--- Reset pointer return NULL; //--- Return null } return Current(); //--- Return current signal } //--- Move to previous signal IAdvisorStrategyExpression* Prev() { _pointer--; //--- Decrement pointer if (_pointer == -1) { //--- Check if before start return NULL; //--- Return null } return Current(); //--- Return current signal } //--- Check if more signals exist bool HasNext() { return (_pointer < (_size - 1)); //--- Return true if pointer is before end } //--- Retrieve current signal IAdvisorStrategyExpression* Current() { return _tradeSignals[_pointer]; //--- Return signal at pointer } //--- Retrieve current iterator index int Key() { return _pointer; //--- Return current pointer } }; //--- Define class for managing trading signals class AdvisorStrategy { private: TradeSignalCollection* _openBuySignals; //--- Store open Buy signals TradeSignalCollection* _openSellSignals; //--- Store open Sell signals TradeSignalCollection* _closeBuySignals; //--- Store close Buy signals TradeSignalCollection* _closeSellSignals; //--- Store close Sell signals //--- Evaluate signal at specified level bool EvaluateASLevel(TradeSignalCollection* signals, int level) { if (level > 0 && level <= signals.Count()) { //--- Check valid level return signals.Get(level - 1).Evaluate(); //--- Evaluate signal } return false; //--- Return false for invalid level } public: //--- Initialize strategy void AdvisorStrategy() { _openBuySignals = new TradeSignalCollection(); //--- Create open Buy signals collection _openSellSignals = new TradeSignalCollection(); //--- Create open Sell signals collection _closeBuySignals = new TradeSignalCollection(); //--- Create close Buy signals collection _closeSellSignals = new TradeSignalCollection(); //--- Create close Sell signals collection } //--- Destructor to clean up signals void ~AdvisorStrategy() { delete(_openBuySignals); //--- Delete open Buy signals delete(_openSellSignals); //--- Delete open Sell signals delete(_closeBuySignals); //--- Delete close Buy signals delete(_closeSellSignals); //--- Delete close Sell signals } //--- Retrieve trading advice bool GetAdvice(TradeAction tradeAction, int level) { if (tradeAction == OpenBuyAction) { //--- Check open Buy action return EvaluateASLevel(_openBuySignals, level); //--- Evaluate Buy signal } else if (tradeAction == OpenSellAction) { //--- Check open Sell action return EvaluateASLevel(_openSellSignals, level); //--- Evaluate Sell signal } else if (tradeAction == CloseBuyAction) { //--- Check close Buy action return EvaluateASLevel(_closeBuySignals, level); //--- Evaluate close Buy signal } else if (tradeAction == CloseSellAction) { //--- Check close Sell action return EvaluateASLevel(_closeSellSignals, level); //--- Evaluate close Sell signal } else { Alert("Unsupported TradeAction in Advisor Strategy. TradeAction: " + DoubleToStr(tradeAction)); //--- Log unsupported action } return false; //--- Return false for invalid action } //--- Register open Buy signal void RegisterOpenBuy(IAdvisorStrategyExpression* openBuySignal, int level) { if (level <= _openBuySignals.Count()) { //--- Check if level already set Alert("Register Open Buy failed: level already set."); //--- Log failure return; //--- Exit } _openBuySignals.Add(openBuySignal); //--- Add signal } //--- Register open Sell signal void RegisterOpenSell(IAdvisorStrategyExpression* openSellSignal, int level) { if (level <= _openSellSignals.Count()) { //--- Check if level already set Alert("Register Open Sell failed: level already set."); //--- Log failure return; //--- Exit } _openSellSignals.Add(openSellSignal); //--- Add signal } //--- Register close Buy signal void RegisterCloseBuy(IAdvisorStrategyExpression* closeBuySignal, int level) { if (level <= _closeBuySignals.Count()) { //--- Check if level already set Alert("Register Close Buy failed: level already set."); //--- Log failure return; //--- Exit } _closeBuySignals.Add(closeBuySignal); //--- Add signal } //--- Register close Sell signal void RegisterCloseSell(IAdvisorStrategyExpression* closeSellSignal, int level) { if (level <= _closeSellSignals.Count()) { //--- Check if level already set Alert("Register Close Sell failed: level already set."); //--- Log failure return; //--- Exit } _closeSellSignals.Add(closeSellSignal); //--- Add signal } //--- Retrieve number of signals for action int GetNumberOfExpressions(TradeAction tradeAction) { if (tradeAction == OpenBuyAction) { //--- Check open Buy action return _openBuySignals.Count(); //--- Return Buy signal count } else if (tradeAction == OpenSellAction) { //--- Check open Sell action return _openSellSignals.Count(); //--- Return Sell signal count } else if (tradeAction == CloseBuyAction) { //--- Check close Buy action return _closeBuySignals.Count(); //--- Return close Buy signal count } else if (tradeAction == CloseSellAction) { //--- Check close Sell action return _closeSellSignals.Count(); //--- Return close Sell signal count } return 0; //--- Return 0 for invalid action } //--- Set fire-only-when-reset for all signals void SetFireOnlyWhenReset(bool value) { _openBuySignals.Rewind(); //--- Reset Buy signals iterator while (_openBuySignals.Next() != NULL) { //--- Iterate Buy signals _openBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _openSellSignals.Rewind(); //--- Reset Sell signals iterator while (_openSellSignals.Next() != NULL) { //--- Iterate Sell signals _openSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _closeBuySignals.Rewind(); //--- Reset close Buy signals iterator while (_closeBuySignals.Next() != NULL) { //--- Iterate close Buy signals _closeBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _closeSellSignals.Rewind(); //--- Reset close Sell signals iterator while (_closeSellSignals.Next() != NULL) { //--- Iterate close Sell signals _closeSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } } //--- Handle tick event for signals void HandleTick() { _openBuySignals.Rewind(); //--- Reset Buy signals iterator while (_openBuySignals.Next() != NULL) { //--- Iterate Buy signals _openBuySignals.Current().ResetSignalValue(); //--- Reset signal value if (_openBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _openBuySignals.Current().Evaluate(); //--- Evaluate signal } } _openSellSignals.Rewind(); //--- Reset Sell signals iterator while (_openSellSignals.Next() != NULL) { //--- Iterate Sell signals _openSellSignals.Current().ResetSignalValue(); //--- Reset signal value if (_openSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _openSellSignals.Current().Evaluate(); //--- Evaluate signal } } _closeBuySignals.Rewind(); //--- Reset close Buy signals iterator while (_closeBuySignals.Next() != NULL) { //--- Iterate close Buy signals _closeBuySignals.Current().ResetSignalValue(); //--- Reset signal value if (_closeBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _closeBuySignals.Current().Evaluate(); //--- Evaluate signal } } _closeSellSignals.Rewind(); //--- Reset close Sell signals iterator while (_closeSellSignals.Next() != NULL) { //--- Iterate close Sell signals _closeSellSignals.Current().ResetSignalValue(); //--- Reset signal value if (_closeSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _closeSellSignals.Current().Evaluate(); //--- Evaluate signal } } } };
ここでは、取引シグナルを管理するための追加クラスを実装します。前のパートですでにクラスについてある程度説明しているため、クラスの構文はすでにご存じだと仮定し、重要な部分だけを示します。まずTradeSignalCollectionクラスを作成します。このクラスでは、「_tradeSignals」にIAdvisorStrategyExpressionポインタを格納し、「_pointer」で反復処理をおこない、「_size」でシグナルの数を保持します。これらはTradeSignalCollectionコンストラクタで初期化されます。シグナルの追加はAdd関数でおこない、削除はRemove関数でおこないます。シグナルの移動や参照にはNext、Prev、Current、Rewind、HasNext関数を使用し、クリーンアップはデストラクタで処理されます。
次にAdvisorStrategyクラスでは、「_openBuySignals」「_openSellSignals」「_closeBuySignals」「_closeSellSignals」をTradeSignalCollectionオブジェクトとして定義し、コンストラクタで初期化します。シグナルの評価はGetAdviceとEvaluateASLevel関数でおこない、シグナルの登録はRegisterOpenBuy、RegisterOpenSell、RegisterCloseBuy、RegisterCloseSellでおこないます。GetNumberOfExpressions関数でシグナルの数を管理し、SetFireOnlyWhenReset関数でリセットフラグを適用します。HandleTick関数ではResetSignalValueとEvaluateを用いてシグナルをリセットおよび評価し、戦略における効率的なシグナル処理を実現します。これで、指定されたレベルでの買いシグナルと売りシグナルのクラスを定義する準備が整いました。まずは売りシグナル用のクラスから始めましょう。
//--- Define class for open Sell signal at level 1 class ASOpenSellLevel1 : public ASSignal { protected: //--- Evaluate Sell signal bool EvaluateSignal() { Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); //--- Retrieve recent open order if (openOrder != NULL && openOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order openOrder = NULL; //--- Clear if Buy to avoid conflict } if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order && ((BidFunc.GetValue(0) < fn_iMA_SMA_4(Symbol(), 0)) //--- Check Bid below 4-period SMA && ((BidFunc.GetValue(0) > fn_iMA_SMA8(Symbol(), 0)) //--- Check Bid above 8-period SMA && ((BidFunc.GetValue(0) < fn_iEnvelopes_ENV_UPPER(Symbol(), 0, 0)) //--- Check Bid below upper Envelope && ((fn_iRSI_RSI(Symbol(), 1) < OpenSell_Const_0) //--- Check previous RSI below threshold && (fn_iRSI_RSI(Symbol(), 0) >= OpenSell_Const_0) //--- Check current RSI above threshold ) ) ) ) ) || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order && (BidFunc.GetValue(0) > ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) + (PipPoint * OpenSell_Const_1))) //--- Check Bid above open price plus offset ) )) { return true; //--- Return true for Sell signal } return false; //--- Return false if no signal } public: //--- Initialize Sell signal void ASOpenSellLevel1() {} //--- Empty constructor };
ASOpenSellLevel1クラスを使って売りシグナルのロジックを開発します。このクラスはASSignalクラスを継承しています。保護されたEvaluateSignal関数内では、まず「_ea」のWalletからGetMostRecentOpenOrder関数を用いて最新の注文を確認し、もし買い注文であればクリアします。最近の注文が存在せず、かつBid価格(BidFuncのGetValue関数から取得)が4期間SMA (fn_iMA_SMA_4)未満、8期間SMA (fn_iMA_SMA8)以上、上部エンベロープバンド(fn_iEnvelopes_ENV_UPPER)未満であり、さらに直前のRSI (fn_iRSI_RSI)がOpenSell_Const_0未満で現在のRSIがそれ以上であれば、売りシグナルを発動します。また、注文が既に存在する場合は、Bid価格が始値に「PipPoint × OpenSell_Const_1」を加えた値を超えた場合も売りシグナルを発動します。
条件を満たせばtrueを返し、そうでなければfalseを返します。ASOpenSellLevel1のコンストラクタ関数は空で、ASSignal親クラスの初期化をそのまま利用します。買いシグナルについても同じロジックを適用します。
//--- Define class for open Buy signal at level 1 class ASOpenBuyLevel1 : public ASSignal { protected: //--- Evaluate Buy signal bool EvaluateSignal() { Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); //--- Retrieve most recent open order if (openOrder != NULL && openOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order openOrder = NULL; //--- Clear if Sell to avoid conflict } if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order && ((AskFunc.GetValue(0) > fn_iMA_SMA_4(Symbol(), 0)) //--- Check Ask above 4-period SMA && ((AskFunc.GetValue(0) < fn_iMA_SMA8(Symbol(), 0)) //--- Check Ask below 8-period SMA && ((AskFunc.GetValue(0) > fn_iEnvelopes_ENV_LOW(Symbol(), 1, 0)) //--- Check Ask above lower Envelope && ((fn_iRSI_RSI(Symbol(), 1) > OpenBuy_Const_0) //--- Check previous RSI above threshold && (fn_iRSI_RSI(Symbol(), 0) <= OpenBuy_Const_0) //--- Check current RSI below threshold ) ) ) ) ) || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order && (AskFunc.GetValue(0) < ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) - (PipPoint * OpenBuy_Const_1))) //--- Check Ask below open price minus offset ) )) { return true; //--- Return true for Buy signal } return false; //--- Return false if no signal } public: //--- Initialize Buy signal void ASOpenBuyLevel1() {} //--- Empty constructor };
買いシグナルのロジックを定義する際は、売りシグナルで用いたのと同じロジックをそのまま使用します。その後、シグナル取引のロジックを制御するための入力パラメータを追加することができます。
//--- Define input group for trade and risk module settings input string trademodule = "------TRADE/RISK MODULE------";//--- Label trade/risk module inputs //--- Define input group for open Buy constants input double LotSizePercentage = 1; //--- Set lot size as percentage of account balance (default: 1%) input double OpenBuy_Const_0 = 11; //--- Set RSI threshold for Buy signal (default: 11) input double OpenBuy_Const_1 = 10; //--- Set pip offset for additional Buy orders (default: 10 pips) input double OpenSell_Const_0 = 89; //--- Set RSI threshold for Sell signal (default: 89) input double OpenSell_Const_1 = 10; //--- Set pip offset for additional Sell orders (default: 10 pips)
シグナル生成のための入力変数を定義した後は、シグナル発動後のリスク管理および取引管理のロジックを定義していきます。そのためには、さらに別のインターフェースとクラスが必要になります。
//--- Define interface for money management interface IMoneyManager { double GetLotSize(); //--- Retrieve lot size int GetNextLevel(Wallet* wallet); //--- Retrieve next trading level }; //--- Define class for money management class MoneyManager : public IMoneyManager { public: //--- Initialize money manager void MoneyManager(Wallet* wallet) { _minLot = MarketInfo_LibFunc(Symbol(), MODE_MINLOT); //--- Set minimum lot size _maxLot = MarketInfo_LibFunc(Symbol(), MODE_MAXLOT); //--- Set maximum lot size _lotStep = MarketInfo_LibFunc(Symbol(), MODE_LOTSTEP); //--- Set lot step } //--- Retrieve calculated lot size double GetLotSize() { double lotSize = NormalizeLots(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * 0.0001 * LotSizePercentage / 100.0, 2)); //--- Calculate lot size return lotSize; //--- Return normalized lot size } //--- Retrieve next trading level int GetNextLevel(Wallet* wallet) { return wallet.GetOpenOrders().Count() + 1; //--- Return next level based on open orders } private: double _minLot; //--- Store minimum lot size double _maxLot; //--- Store maximum lot size double _lotStep; //--- Store lot step //--- Normalize lot size to broker specifications double NormalizeLots(double lots) { lots = MathRound(lots / _lotStep) * _lotStep; //--- Round to lot step if (lots < _minLot) lots = _minLot; //--- Enforce minimum lot else if (lots > _maxLot) lots = _maxLot; //--- Enforce maximum lot return lots; //--- Return normalized lot size } };
戦略の資金管理を設定するために、IMoneyManagerインターフェースを定義します。このインターフェースにはGetLotSizeとGetNextLevel関数を含め、取引サイズとレベルの進行を標準化します。IMoneyManagerを実装するMoneyManagerクラスでは、コンストラクタ内でMarketInfo_LibFunc関数を使って「_minLot」「_maxLot」「_lotStep」変数を初期化します。GetLotSize関数は、AccountInfoDoubleから取得した口座残高をもとにロットサイズを計算し、LotSizePercentageによって調整した後、NormalizeLots関数でブローカーのルールに適合するよう正規化します。
GetNextLevel関数は、GetOpenOrders関数で取得した未決済注文数に1を加えた値を返します。NormalizeLots関数は、ロットサイズを「_lotStep」に合わせて丸め、かつ「_minLot」と「_maxLot」を尊重することで有効なロットサイズを保証します。これにより、簡潔で管理しやすい取引数量およびレベル管理システムを構築できます。次は、実際の取引を管理するステップに進みますが、そのためにはさらに別のインターフェースが必要になります。
//--- Define enumeration for trading module demands enum TradingModuleDemand { NoneDemand = 0, //--- Represent no demand NoBuyDemand = 1, //--- Prevent Buy orders NoSellDemand = 2, //--- Prevent Sell orders NoOpenDemand = 4, //--- Prevent all open orders OpenBuySellDemand = 8, //--- Demand both Buy and Sell opens OpenBuyDemand = 16, //--- Demand Buy open OpenSellDemand = 32, //--- Demand Sell open CloseBuyDemand = 64, //--- Demand Buy close CloseSellDemand = 128, //--- Demand Sell close CloseBuySellDemand = 256 //--- Demand both Buy and Sell closes }; //--- Define interface for trading module signals interface ITradingModuleSignal { string GetName(); //--- Retrieve signal name bool Evaluate(Order* openOrder = NULL); //--- Evaluate signal }; //--- Define interface for trading module values interface ITradingModuleValue { string GetName(); //--- Retrieve value name double Evaluate(Order* openOrder = NULL); //--- Evaluate value }; //--- Define interface for trade strategy modules interface ITradeStrategyModule { TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand demand, int level = 1); //--- Evaluate module void RegisterTradeSignal(ITradingModuleSignal* tradeSignal); //--- Register signal }; //--- Define interface for open trade strategy modules interface ITradeStrategyOpenModule : public ITradeStrategyModule { TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand demand, int requestedEvaluationLevel = 0); //--- Evaluate open signals TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand demand); //--- Evaluate close signals }; //--- Define interface for close trade strategy modules interface ITradeStrategyCloseModule : public ITradeStrategyModule { ORDER_GROUP_TYPE GetOrderGroupingType(); //--- Retrieve grouping type void RegisterTradeValue(ITradingModuleValue* tradeValue); //--- Register value };
ここでは、取引判断を管理するための基盤を構築し、構造化された列挙型とインターフェースを定義します。まずTradingModuleDemand列挙型を作成します。これは取引アクションや制限を分類するために使用され、NoneDemandは何もおこなわない場合、NoBuyDemandやNoSellDemandは特定の注文タイプをブロックする場合、OpenBuyDemandやOpenSellDemandは取引を開始する場合、CloseBuyDemandやCloseSellDemandはポジションを決済する場合などに用います。この列挙型は、戦略が取引実行に関して意図する内容を明確に示す手段となります。
次にITradingModuleSignalインターフェースを定義します。これは取引シグナル操作を標準化するもので、GetName関数でシグナルの識別子を取得し、Evaluate関数でシグナルがアクティブかどうかを評価します。必要に応じて未決済注文の状態も考慮できます。同様にITradingModuleValueインターフェースを導入し、GetNameとEvaluate関数を使って利益目標など取引判断に関連する数値を管理します。戦略モジュール用にはITradeStrategyModuleインターフェースを作成し、Walletオブジェクトに基づいて取引要求を処理するEvaluate関数と、シグナルを組み込むRegisterTradeSignal関数を定義します。
さらにITradeStrategyOpenModuleインターフェースを拡張し、取引を開始および決済するシグナルを処理するEvaluateOpenSignalsとEvaluateCloseSignals関数を追加します。またITradeStrategyCloseModuleインターフェースには、注文のグループ化に関するGetOrderGroupingType関数と値登録用のRegisterTradeValue関数を含めます。これらの要素を組み合わせることで、戦略内で取引アクションを柔軟に調整するフレームワークを構築できます。次に、これらのモジュールを基に取引管理クラスを定義します。
//--- Define class for managing trade strategy class TradeStrategy { public: ITradeStrategyCloseModule* CloseModules[]; //--- Store close modules private: ITradeStrategyModule* _preventOpenModules[]; //--- Store prevent-open modules ITradeStrategyOpenModule* _openModule; //--- Store open module //--- Evaluate prevent-open modules TradingModuleDemand EvaluatePreventOpenModules(Wallet* wallet, TradingModuleDemand preventOpenDemand, int evaluationLevel = 1) { TradingModuleDemand preventOpenDemands[]; //--- Declare prevent-open demands array ArrayResize(preventOpenDemands, ArraySize(_preventOpenModules), 8); //--- Resize array for (int i = 0; i < ArraySize(_preventOpenModules); i++) { //--- Iterate modules preventOpenDemands[i] = _preventOpenModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module } return PreventOpenModuleBase::GetCombinedPreventOpenDemand(preventOpenDemands); //--- Return combined demand } //--- Evaluate close modules TradingModuleDemand EvaluateCloseModules(Wallet* wallet, TradingModuleDemand closeDemand, int evaluationLevel = 1) { TradingModuleDemand closeDemands[]; //--- Declare close demands array ArrayResize(closeDemands, ArraySize(CloseModules), 8); //--- Resize array for (int i = 0; i < ArraySize(CloseModules); i++) { //--- Iterate modules closeDemands[i] = CloseModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module } return CloseModuleBase::GetCombinedCloseDemand(closeDemands); //--- Return combined demand } //--- Evaluate close conditions for TP/SL void EvaluateCloseConditions(Wallet* wallet, TradingModuleDemand signalDemand) { OrderCollection* openOrders = wallet.GetOpenOrders(); //--- Retrieve open orders if (openOrders.Count() == 0) { //--- Check if no open orders return; //--- Exit } double bid = Bid_LibFunc(); //--- Retrieve Bid price double ask = Ask_LibFunc(); //--- Retrieve Ask price for (int i = openOrders.Count() - 1; i >= 0; i--) { //--- Iterate open orders Order* order = openOrders.Get(i); //--- Get order bool closeSignal = (order.Type == OP_BUY && signalDemand == CloseBuyDemand) || //--- Check Buy close signal (order.Type == OP_SELL && signalDemand == CloseSellDemand) || //--- Check Sell close signal signalDemand == CloseBuySellDemand; //--- Check Buy/Sell close signal bool closeManualSLTP = AllowManualTPSLChanges && ((order.StopLossManual != 0 && order.Type == OP_BUY && bid <= order.StopLossManual) || //--- Check manual Buy SL (order.StopLossManual != 0 && order.Type == OP_SELL && ask >= order.StopLossManual) || //--- Check manual Sell SL (order.TakeProfitManual != 0 && order.Type == OP_BUY && bid >= order.TakeProfitManual) || //--- Check manual Buy TP (order.TakeProfitManual != 0 && order.Type == OP_SELL && ask <= order.TakeProfitManual)); //--- Check manual Sell TP bool fullOrderClose = closeSignal || closeManualSLTP; //--- Determine full close OrderCloseInfo* activePartialCloseCloseInfo = NULL; //--- Initialize partial close info if (!fullOrderClose) { //--- Check if not full close if (!AllowManualTPSLChanges || order.StopLossManual == 0) { //--- Check manual SL for (int cli = 0; cli < ArraySize(order.CloseInfosSL); cli++) { //--- Iterate SL info if (order.CloseInfosSL[cli].IsOld) continue; //--- Skip old info if (order.CloseInfosSL[cli].IsClosePriceSLHit(order.Type, ask, bid)) { //--- Check SL hit if (activePartialCloseCloseInfo == NULL || order.CloseInfosSL[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage activePartialCloseCloseInfo = order.CloseInfosSL[cli]; //--- Set active info } } } } if (!AllowManualTPSLChanges || order.TakeProfitManual == 0) { //--- Check manual TP for (int cli = 0; cli < ArraySize(order.CloseInfosTP); cli++) { //--- Iterate TP info if (order.CloseInfosTP[cli].IsOld) continue; //--- Skip old info if (order.CloseInfosTP[cli].IsClosePriceTPHit(order.Type, ask, bid)) { //--- Check TP hit if (activePartialCloseCloseInfo == NULL || order.CloseInfosTP[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage activePartialCloseCloseInfo = order.CloseInfosTP[cli]; //--- Set active info } } } } fullOrderClose = activePartialCloseCloseInfo != NULL && activePartialCloseCloseInfo.Percentage == 100; //--- Check if full close } if (fullOrderClose) { //--- Handle full close TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, 0); //--- Evaluate prevent-open TradingModuleDemand openDemand = _openModule.EvaluateOpenSignals(wallet, finalPreventOpenAdvice, 1); //--- Evaluate open signals int orderTypeOfOpeningOrder = wallet.GetOpenOrders().Get(0).Type; //--- Get first order type if ((orderTypeOfOpeningOrder == ORDER_TYPE_BUY && openDemand == OpenBuyDemand) || //--- Check Buy re-entry (orderTypeOfOpeningOrder == ORDER_TYPE_SELL && openDemand == OpenSellDemand) || //--- Check Sell re-entry (openDemand == OpenBuySellDemand)) { //--- Check Buy/Sell re-entry return; //--- Block close to prevent re-entry } wallet.SetOpenOrderToPendingClose(order); //--- Move order to pending close } else if (activePartialCloseCloseInfo != NULL) { //--- Handle partial close Order* partialCloseOrder = order.SplitOrder(activePartialCloseCloseInfo.Percentage); //--- Split order if (partialCloseOrder.Lots < 1e-13) { //--- Check if last piece delete(partialCloseOrder); //--- Delete split order wallet.SetOpenOrderToPendingClose(order); //--- Move to pending close } else { partialCloseOrder.ParentOrder = order; //--- Link to parent order if (wallet.AddPendingCloseOrder(partialCloseOrder)) { //--- Add to pending close activePartialCloseCloseInfo.IsOld = true; //--- Mark info as old } } } } } public: //--- Initialize trade strategy void TradeStrategy(ITradeStrategyOpenModule* openModule) { _openModule = openModule; //--- Set open module } //--- Destructor to clean up strategy void ~TradeStrategy() { for (int i = ArraySize(_preventOpenModules) - 1; i >= 0; i--) { //--- Iterate prevent-open modules delete(_preventOpenModules[i]); //--- Delete module } delete(_openModule); //--- Delete open module for (int i = ArraySize(CloseModules) - 1; i >= 0; i--) { //--- Iterate close modules delete(CloseModules[i]); //--- Delete module } } //--- Evaluate trading strategy void Evaluate(Wallet* wallet) { int orderCount = wallet.GetOpenOrders().Count(); //--- Retrieve open order count TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, orderCount + 1); //--- Evaluate prevent-open if (orderCount > 0) { //--- Check if orders exist EvaluateCloseModules(wallet, NoneDemand); //--- Evaluate close modules TradingModuleDemand signalDemand = _openModule.EvaluateCloseSignals(wallet, finalPreventOpenAdvice); //--- Evaluate close signals EvaluateCloseConditions(wallet, signalDemand); //--- Evaluate close conditions } _openModule.Evaluate(wallet, finalPreventOpenAdvice, 0); //--- Evaluate open module } //--- Register prevent-open module void RegisterPreventOpenModule(ITradeStrategyModule* preventOpenModule) { int size = ArraySize(_preventOpenModules); //--- Get current array size ArrayResize(_preventOpenModules, size + 1, 8); //--- Resize array _preventOpenModules[size] = preventOpenModule; //--- Add module } //--- Register close module void RegisterCloseModule(ITradeStrategyCloseModule* closeModule) { int size = ArraySize(CloseModules); //--- Get current array size ArrayResize(CloseModules, size + 1, 8); //--- Resize array CloseModules[size] = closeModule; //--- Add module } }; //--- Define base class for module calculations class ModuleCalculationsBase { public: //--- Calculate profit for order collection static double CalculateOrderCollectionProfit(OrderCollection &orders, ORDER_PROFIT_CALCULATION_TYPE calculationType) { double collectionProfit = 0; //--- Initialize profit for (int i = 0; i < orders.Count(); i++) { //--- Iterate orders Order* order = orders.Get(i); //--- Get order collectionProfit += CalculateOrderProfit(order, calculationType); //--- Add order profit } return collectionProfit; //--- Return total profit } //--- Calculate profit for single order static double CalculateOrderProfit(Order* order, ORDER_PROFIT_CALCULATION_TYPE calculationType) { if (calculationType == Pips) { //--- Check pips calculation return order.CalculateProfitPips(); //--- Return profit in pips } else if (calculationType == Money) { //--- Check money calculation return order.CalculateProfitCurrency(); //--- Return profit in currency } else if (calculationType == EquityPercentage) { //--- Check equity percentage return order.CalculateProfitEquityPercentage(); //--- Return profit as percentage } else { Alert("Can't execute CalculateOrderCollectionProfit. Unknown calculationType: " + IntegerToString(calculationType)); //--- Log error return 0; //--- Return 0 for invalid type } } }; //--- Define base class for open trade modules class OpenModuleBase : public ITradeStrategyOpenModule { protected: AdvisorStrategy* _advisorStrategy; //--- Store advisor strategy IMoneyManager* _moneyManager; //--- Store money manager //--- Create new order Order* OpenOrder(ENUM_ORDER_TYPE orderType, bool mustBeVisibleOnChart) { Order* order = new Order(mustBeVisibleOnChart); //--- Create new order order.SymbolCode = Symbol(); //--- Set symbol order.Type = orderType; //--- Set order type order.MagicNumber = MagicNumber; //--- Set magic number order.Lots = _moneyManager.GetLotSize(); //--- Set lot size if (order.Type == ORDER_TYPE_BUY) { //--- Check Buy order order.OpenPrice = Ask_LibFunc(); //--- Set open price to Ask order.StopLoss = -DBL_MAX; //--- Set initial SL to minimum order.TakeProfit = DBL_MAX; //--- Set initial TP to maximum } else if (order.Type == ORDER_TYPE_SELL) { //--- Check Sell order order.OpenPrice = Bid_LibFunc(); //--- Set open price to Bid order.StopLoss = DBL_MAX; //--- Set initial SL to maximum order.TakeProfit = -DBL_MAX; //--- Set initial TP to minimum } order.LowestProfitPips = DBL_MAX; //--- Set initial lowest profit order.HighestProfitPips = -DBL_MAX; //--- Set initial highest profit order.Comment = OrderComment; //--- Set order comment OrderRepository::CalculateAndSetCommision(order); //--- Calculate and set commission return order; //--- Return order } public: //--- Initialize open module void OpenModuleBase(AdvisorStrategy* advisorStrategy, IMoneyManager* moneyManager) { _advisorStrategy = advisorStrategy; //--- Set advisor strategy _moneyManager = moneyManager; //--- Set money manager } //--- Retrieve trade actions void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) { TradeAction tempresult[]; //--- Declare temporary actions array if (wallet.GetOpenOrders().Count() > 0) { //--- Check if open orders exist Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order if (firstOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = CloseBuyAction; //--- Add close Buy action } else if (firstOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenSellAction; //--- Add open Sell action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = CloseSellAction; //--- Add close Sell action } else { Alert("Unsupported ordertype. Ordertype: " + DoubleToStr(firstOrder.Type)); //--- Log error } } else { ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = OpenSellAction; //--- Add open Sell action } for (int i = 0; i < ArraySize(tempresult); i++) { //--- Iterate actions if ((preventOpenDemand == NoOpenDemand && (tempresult[i] == OpenBuyAction || tempresult[i] == OpenSellAction)) || //--- Check no open demand (preventOpenDemand == NoBuyDemand && tempresult[i] == OpenBuyAction) || //--- Check no Buy demand (preventOpenDemand == NoSellDemand && tempresult[i] == OpenSellAction)) { //--- Check no Sell demand continue; //--- Skip action } ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize result array result[ArraySize(result) - 1] = tempresult[i]; //--- Add action } } //--- Register trade signal (empty implementation) virtual void RegisterTradeSignal(ITradingModuleSignal* tradeSignal) {} //--- Do nothing //--- Combine open demands static TradingModuleDemand GetCombinedOpenDemand(TradingModuleDemand &openDemands[]) { TradingModuleDemand result = NoneDemand; //--- Initialize result for (int i = 0; i < ArraySize(openDemands); i++) { //--- Iterate demands if (result == OpenBuySellDemand) { //--- Check if Buy/Sell demand return OpenBuySellDemand; //--- Return Buy/Sell demand } if (openDemands[i] == OpenBuySellDemand) { //--- Check if demand is Buy/Sell result = OpenBuySellDemand; //--- Set Buy/Sell demand } else if (result == NoneDemand && openDemands[i] == OpenBuyDemand) { //--- Check Buy demand result = OpenBuyDemand; //--- Set Buy demand } else if (result == NoneDemand && openDemands[i] == OpenSellDemand) { //--- Check Sell demand result = OpenSellDemand; //--- Set Sell demand } else if (result == OpenBuyDemand && openDemands[i] == OpenSellDemand) { //--- Check mixed demands result = OpenBuySellDemand; //--- Set Buy/Sell demand } else if (result == OpenSellDemand && openDemands[i] == OpenBuyDemand) { //--- Check mixed demands result = OpenBuySellDemand; //--- Set Buy/Sell demand } } return result; //--- Return combined demand } //--- Combine close demands static TradingModuleDemand GetCombinedCloseDemand(TradingModuleDemand &closeDemands[]) { TradingModuleDemand result = NoneDemand; //--- Initialize result for (int i = 0; i < ArraySize(closeDemands); i++) { //--- Iterate demands if (result == CloseBuySellDemand) { //--- Check if Buy/Sell demand return CloseBuySellDemand; //--- Return Buy/Sell demand } if (closeDemands[i] == CloseBuySellDemand) { //--- Check if demand is Buy/Sell result = CloseBuySellDemand; //--- Set Buy/Sell demand } else if (result == NoneDemand && closeDemands[i] == CloseBuyDemand) { //--- Check Buy demand result = CloseBuyDemand; //--- Set Buy demand } else if (result == NoneDemand && closeDemands[i] == CloseSellDemand) { //--- Check Sell demand result = CloseSellDemand; //--- Set Sell demand } else if (result == CloseBuyDemand && closeDemands[i] == CloseSellDemand) { //--- Check mixed demands result = CloseBuySellDemand; //--- Set Buy/Sell demand } else if (result == CloseSellDemand && closeDemands[i] == CloseBuyDemand) { //--- Check mixed demands result = CloseBuySellDemand; //--- Set Buy/Sell demand } } return result; //--- Return combined demand } //--- Retrieve number of open orders int GetNumberOfOpenOrders(Wallet* wallet) { return wallet.GetOpenOrders().Count(); //--- Return open order count } };
TradeStrategyクラスを使って取引戦略のフレームワークを構築します。CloseModulesにはITradeStrategyCloseModuleオブジェクトを、「_preventOpenModules」にはITradeStrategyModuleオブジェクトを、「_openModule」にはITradeStrategyOpenModuleオブジェクトを格納します。「_openModule」はTradeStrategyのコンストラクタで初期化し、モジュールはデストラクタでクリーンアップします。Evaluate関数では、GetOpenOrdersで未決済注文を評価し、「_preventOpenModules」をEvaluatePreventOpenModulesとGetCombinedPreventOpenDemandで評価し、CloseModulesをEvaluateCloseModulesとGetCombinedCloseDemandで処理します。
EvaluateCloseConditions関数では、注文タイプをCloseBuyDemandまたはCloseSellDemandと照合し、Ask_LibFuncやBid_LibFuncを使って手動のストップロスやテイクプロフィットを確認し、SetOpenOrderToPendingCloseまたはSplitOrderで決済を処理します。モジュールはRegisterPreventOpenModuleとRegisterCloseModuleで登録します。ModuleCalculationsBaseクラスはCalculateOrderCollectionProfitとCalculateOrderProfitで利益を計算し、OpenModuleBaseはOpenOrderで注文を作成し、GetTradeActionsで取引アクションを判定します。またGetCombinedOpenDemandとGetCombinedCloseDemandで要求を統合し、統一されたシステムを実現します。
これで取引をおこなう準備が整いました。過剰取引を防ぐために、インスタンスごとの最大取引数を制御する外部変数を設けることができます。
//--- Define input for maximum open orders input int MaxNumberOfOpenOrders1 = 1; //--- Set maximum number of open orders (default: 1)
取引回数制限の変数を設定した後、次に複数のモジュールを開くためのクラスを定義することができます。
//--- Define class for multiple open module class MultipleOpenModule_1 : public OpenModuleBase { protected: TradingModuleDemand previousSignalDemand; //--- Store previous signal demand public: //--- Initialize multiple open module void MultipleOpenModule_1(AdvisorStrategy* advisorStrategy, MoneyManager* moneyManager) : OpenModuleBase(advisorStrategy, moneyManager) { _advisorStrategy.SetFireOnlyWhenReset(true); //--- Configure signals to fire only when reset } //--- Evaluate and act on signals TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand preventOpenDemand, int level) { TradingModuleDemand newSignalsDemand = EvaluateSignals(wallet, preventOpenDemand, level); //--- Evaluate signals if (newSignalsDemand != NoneDemand) { //--- Check if demand exists EvaluateOpenConditions(wallet, newSignalsDemand); //--- Evaluate open conditions } return newSignalsDemand; //--- Return new signal demand } //--- Evaluate open signals without acting TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) { TradingModuleDemand openDemands[]; //--- Declare open demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions int level; //--- Declare level if (requestedEvaluationLevel == 0) { //--- Check if level unspecified level = _moneyManager.GetNextLevel(wallet); //--- Set level based on orders } else { level = requestedEvaluationLevel; //--- Use specified level } for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions if (tradeActionsToEvaluate[i] == CloseBuyAction || tradeActionsToEvaluate[i] == CloseSellAction) { //--- Skip close actions continue; //--- Move to next } if (requestedEvaluationLevel == 0) { //--- Check if level unspecified level = GetTopLevel(tradeActionsToEvaluate[i], level); //--- Cap level if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit level += 1; //--- Increment level } } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], level)) { //--- Check if action advised if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy action int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenBuyDemand; //--- Add Buy demand } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell action int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenSellDemand; //--- Add Sell demand } } } TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, level - 1); //--- Adjust for previous demand multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands return multiOrderOpenSignal; //--- Return final open signal } //--- Retrieve trade actions (custom for multiple orders) void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) { if (wallet.GetOpenOrders().Count() > 0) { //--- Check if open orders exist Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order if (firstOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = CloseBuyAction; //--- Add close Buy action } else if (firstOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenSellAction; //--- Add open Sell action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = CloseSellAction; //--- Add close Sell action } else { Alert("Unsupported ordertype"); //--- Log error } } else { ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = OpenSellAction; //--- Add open Sell action } } //--- Evaluate close signals TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand) { TradingModuleDemand closeDemands[]; //--- Declare close demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions if (tradeActionsToEvaluate[i] != CloseBuyAction && tradeActionsToEvaluate[i] != CloseSellAction) { //--- Skip non-close actions continue; //--- Move to next } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], 1)) { //--- Check if action advised (level 1) if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseBuyDemand; //--- Add Buy close demand } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand } } } TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands return combinedCloseSignalDemand; //--- Return combined demand } private: //--- Evaluate open and close signals TradingModuleDemand EvaluateSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) { TradingModuleDemand openDemands[]; //--- Declare open demands array TradingModuleDemand closeDemands[]; //--- Declare close demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions int moneyManagementLevel; //--- Declare level if (requestedEvaluationLevel == 0) { //--- Check if level unspecified moneyManagementLevel = _moneyManager.GetNextLevel(wallet); //--- Set level based on orders } else { moneyManagementLevel = requestedEvaluationLevel; //--- Use specified level } for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions int tradeActionEvaluationLevel = GetTopLevel(tradeActionsToEvaluate[i], moneyManagementLevel); //--- Cap level if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit tradeActionEvaluationLevel += 1; //--- Increment level } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], tradeActionEvaluationLevel)) { //--- Check if action advised if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy open int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenBuyDemand; //--- Add Buy demand } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell open int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenSellDemand; //--- Add Sell demand } else if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseBuyDemand; //--- Add Buy close demand } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand } } } TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands if (combinedCloseSignalDemand != NoneDemand) { //--- Check if close demand exists return combinedCloseSignalDemand; //--- Return close demand } TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, GetNumberOfOpenOrders(wallet)); //--- Adjust for previous demand previousSignalDemand = combinedOpenSignalDemand; //--- Update previous demand multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands return multiOrderOpenSignal; //--- Return final signal } //--- Evaluate open conditions and add orders void EvaluateOpenConditions(Wallet* wallet, TradingModuleDemand signalDemand) { if (signalDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand return; //--- Exit (hedging not supported) } else { double currentFreeMargin = AccountFreeMargin_LibFunc(); //--- Retrieve free margin double requiredMargin; //--- Declare required margin if (signalDemand == OpenBuyDemand) { //--- Check Buy demand if (!MarginRequired(ORDER_TYPE_BUY, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin return; //--- Exit if margin check fails } if (currentFreeMargin < requiredMargin) { //--- Check sufficient margin HandleErrors("Not enough free margin to open buy order with requested volume."); //--- Log error return; //--- Exit } wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_BUY, false)); //--- Add Buy order } else if (signalDemand == OpenSellDemand) { //--- Check Sell demand if (!MarginRequired(ORDER_TYPE_SELL, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin return; //--- Exit if margin check fails } if (currentFreeMargin < requiredMargin) { //--- Check sufficient margin HandleErrors("Not enough free margin to open sell order with requested volume."); //--- Log error return; //--- Exit } wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_SELL, false)); //--- Add Sell order } } } //--- Adjust open demand based on previous demand TradingModuleDemand GetOpenDemandBasedOnPreviousOpenDemand(TradingModuleDemand openDemand, int numberOfOpenOrders) { if (numberOfOpenOrders == 0 || previousSignalDemand == NoneDemand) { //--- Check no orders or no previous demand return openDemand; //--- Return current demand } if (previousSignalDemand == OpenBuyDemand && openDemand == OpenSellDemand) { //--- Check Buy to Sell switch return openDemand; //--- Allow Sell demand } else if (previousSignalDemand == OpenSellDemand && openDemand == OpenBuyDemand) { //--- Check Sell to Buy switch return openDemand; //--- Allow Buy demand } return NoneDemand; //--- Block same-direction or mixed demands } private: //--- Cap evaluation level int GetTopLevel(TradeAction tradeAction, int level) { int numberOfExpressions = _advisorStrategy.GetNumberOfExpressions(tradeAction); //--- Retrieve expression count if (level > numberOfExpressions) { //--- Check if level exceeds expressions level = numberOfExpressions; //--- Cap level } return level; //--- Return capped level } //--- Add previous demand action if missing void AddPreviousDemandTradeActionIfMissing(TradeAction& result[]) { if (previousSignalDemand == NoneDemand) { //--- Check if no previous demand return; //--- Exit } bool foundPreviousDemand = false; //--- Initialize found flag if (previousSignalDemand == OpenBuyDemand) { //--- Check Buy demand AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action } else if (previousSignalDemand == OpenSellDemand) { //--- Check Sell demand AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action } else if (previousSignalDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action } } //--- Add specific previous demand action void AddPreviousDemandTradeAction(TradeAction& result[], TradingModuleDemand demand, TradeAction action) { bool foundPreviousDemand = false; //--- Initialize found flag if (previousSignalDemand == demand) { //--- Check matching demand for (int i = 0; i < ArraySize(result); i++) { //--- Iterate actions if (action == result[i]) { //--- Check if action exists foundPreviousDemand = true; //--- Set found flag } } if (!foundPreviousDemand) { //--- Check if action missing ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[ArraySize(result) - 1] = action; //--- Add action } } } //--- Filter prevent-open demands TradingModuleDemand FilterPreventOpenDemand(TradingModuleDemand multiOrderOpendDemand, TradingModuleDemand preventOpenDemand) { if (multiOrderOpendDemand == NoneDemand) { //--- Check no demand return multiOrderOpendDemand; //--- Return no demand } else if (multiOrderOpendDemand == OpenBuyDemand && (preventOpenDemand == NoBuyDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Buy return NoneDemand; //--- Block Buy demand } else if (multiOrderOpendDemand == OpenSellDemand && (preventOpenDemand == NoSellDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Sell return NoneDemand; //--- Block Sell demand } else if (multiOrderOpendDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand if (preventOpenDemand == NoBuyDemand) { //--- Check no Buy return OpenSellDemand; //--- Allow Sell demand } else if (preventOpenDemand == NoSellDemand) { //--- Check no Sell return OpenBuyDemand; //--- Allow Buy demand } else if (preventOpenDemand == NoOpenDemand) { //--- Check no open return NoneDemand; //--- Block all demands } } return multiOrderOpendDemand; //--- Return unfiltered demand } };
ここでは、複数の取引を同時に開くモジュールをMultipleOpenModule_1クラスを使って開発します。このクラスはOpenModuleBaseを継承しています。MultipleOpenModule_1コンストラクタで初期化し、「_advisorStrategy」をリセット後のみシグナルを発動させるようにSetFireOnlyWhenResetで設定し、前回の要求をpreviousSignalDemandに保存します。Evaluate関数では、EvaluateSignalsでシグナルを評価し、有効な要求に対してEvaluateOpenConditionsでアクションを実行し、最終的に要求を返します。
EvaluateOpenSignals関数では、GetTradeActionsでアクションを取得し、AddPreviousDemandTradeActionIfMissingで前回の要求を追加し、レベルは「_moneyManager」のGetNextLevelまたは指定値で設定します。アクションを反復処理し、CloseBuyActionやCloseSellActionはスキップし、GetTopLevelでレベルを上限に抑え、GetOpenOrdersとMaxNumberOfOpenOrders1で注文制限を確認します。「_advisorStrategy」のGetAdviceからの有効なシグナルはOpenBuyDemandまたはOpenSellDemandとして配列に追加され、GetCombinedOpenDemandで統合され、GetOpenDemandBasedOnPreviousOpenDemandで調整され、FilterPreventOpenDemandでフィルタリングされます。
EvaluateCloseSignals関数も同様にCloseBuyActionとCloseSellActionを評価し、CloseBuyDemandやCloseSellDemandを追加してGetCombinedCloseDemandで統合します。
EvaluateOpenConditions関数では、AccountFreeMargin_LibFuncとMarginRequiredで証拠金を確認し、十分であればOpenOrderで買いまたは売り注文を追加し、不足していればHandleErrorsでエラーを記録します。オープン注文のクローズ、テイクプロフィットやストップロスの設定も同様のロジックでおこない、対応する入力も用意されます。これにより、実行時に次の入力を利用できる状態になります。
これまでに実装したすべての取引実行と管理を統括する最終的なクラスを作成できます。そのために、以下のロジックを使用します。
//--- Define interface for trader interface ITrader { void HandleTick(); //--- Handle tick event void Init(); //--- Initialize trader Wallet* GetWallet(); //--- Retrieve wallet }; //--- Declare global trader pointer ITrader *_ea; //--- Store EA instance //--- Define main Expert Advisor class class EA : public ITrader { private: bool _firstTick; //--- Track first tick TradeStrategy* _tradeStrategy; //--- Store trade strategy AdvisorStrategy* _advisorStrategy; //--- Store advisor strategy IMoneyManager* _moneyManager; //--- Store money manager Wallet* _wallet; //--- Store wallet public: //--- Initialize EA void EA() { _firstTick = true; //--- Set first tick flag _wallet = new Wallet(); //--- Create wallet _wallet.SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe); //--- Set closed orders timeframe _advisorStrategy = new AdvisorStrategy(); //--- Create advisor strategy _advisorStrategy.RegisterOpenBuy(new ASOpenBuyLevel1(), 1); //--- Register Buy signal _advisorStrategy.RegisterOpenSell(new ASOpenSellLevel1(), 1); //--- Register Sell signal _moneyManager = new MoneyManager(_wallet); //--- Create money manager _tradeStrategy = new TradeStrategy(new MultipleOpenModule_1(_advisorStrategy, _moneyManager)); //--- Create trade strategy _tradeStrategy.RegisterCloseModule(new TakeProfitCloseModule_1()); //--- Register TP module _tradeStrategy.RegisterCloseModule(new StopLossCloseModule_1()); //--- Register SL module } //--- Destructor to clean up EA void ~EA() { delete(_tradeStrategy); //--- Delete trade strategy delete(_moneyManager); //--- Delete money manager delete(_advisorStrategy); //--- Delete advisor strategy delete(_wallet); //--- Delete wallet } //--- Initialize EA components void Init() { IsDemoLiveOrVisualMode = !MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE); //--- Set mode flag UnitsOneLot = MarketInfo_LibFunc(Symbol(), MODE_LOTSIZE); //--- Set lot size SetOrderGrouping(); //--- Configure order grouping _wallet.LoadOrdersFromBroker(); //--- Load orders from broker } //--- Handle tick event void HandleTick() { if (MQLInfoInteger(MQL_TESTER) == 0) { //--- Check if not in tester SyncOrders(); //--- Synchronize orders } if (AllowManualTPSLChanges) { //--- Check if manual TP/SL allowed SyncManualTPSLChanges(); //--- Synchronize manual TP/SL } AskFunc.Evaluate(); //--- Update Ask price BidFunc.Evaluate(); //--- Update Bid price UpdateOrders(); //--- Update order profits if (!StopEA) { //--- Check if EA not stopped _wallet.HandleTick(); //--- Handle wallet tick _advisorStrategy.HandleTick(); //--- Handle strategy tick if (_wallet.GetPendingOpenOrders().Count() == 0 && _wallet.GetPendingCloseOrders().Count() == 0) { //--- Check no pending orders _tradeStrategy.Evaluate(_wallet); //--- Evaluate strategy } if (ExecutePendingCloseOrders()) { //--- Execute close orders if (!ExecutePendingOpenOrders()) { //--- Execute open orders HandleErrors(StringFormat("Open (all) order(s) failed. Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } else { HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } else { if (ExecutePendingCloseOrders()) { //--- Execute close orders _wallet.SetAllOpenOrdersToPendingClose(); //--- Move open orders to pending close } else { HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } if (_firstTick) { //--- Check if first tick _firstTick = false; //--- Clear first tick flag } } //--- Retrieve wallet Wallet* GetWallet() { return _wallet; //--- Return wallet } private: //--- Configure order grouping void SetOrderGrouping() { int size = ArraySize(_tradeStrategy.CloseModules); //--- Get close modules size ORDER_GROUP_TYPE groups[]; //--- Declare groups array ArrayResize(groups, size); //--- Resize array for (int i = 0; i < ArraySize(_tradeStrategy.CloseModules); i++) { //--- Iterate modules groups[i] = _tradeStrategy.CloseModules[i].GetOrderGroupingType(); //--- Set grouping type } _wallet.ActivateOrderGroups(groups); //--- Activate groups } //--- Synchronize orders with broker void SyncOrders() { OrderCollection* currentOpenOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); //--- Retrieve open orders if (currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count())) { //--- Check order mismatch Print("(Manual) orderchanges detected" + " (found in MT: " + IntegerToString(currentOpenOrders.Count()) + " and in wallet: " + IntegerToString(_wallet.GetOpenOrders().Count()) + "), resetting EA, loading open orders."); //--- Log mismatch _wallet.ResetOpenOrders(); //--- Reset open orders _wallet.ResetPendingOrders(); //--- Reset pending orders _wallet.LoadOrdersFromBroker(); //--- Reload orders } delete(currentOpenOrders); //--- Delete orders collection } //--- Synchronize manual TP/SL changes void SyncManualTPSLChanges() { _wallet.GetOpenOrders().Rewind(); //--- Reset orders iterator while (_wallet.GetOpenOrders().HasNext()) { //--- Iterate orders Order* order = _wallet.GetOpenOrders().Next(); //--- Get order uint lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Find SL line if (lineFindResult != UINT_MAX) { //--- Check if SL line exists double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_SL", OBJPROP_PRICE); //--- Get SL position if ((order.StopLossManual == 0 && currentPosition != order.GetClosestSL()) || //--- Check manual SL change (order.StopLossManual != 0 && currentPosition != order.StopLossManual)) { //--- Check manual SL mismatch order.StopLossManual = currentPosition; //--- Update manual SL } } lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Find TP line if (lineFindResult != UINT_MAX) { //--- Check if TP line exists double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_TP", OBJPROP_PRICE); //--- Get TP position if ((order.TakeProfitManual == 0 && currentPosition != order.GetClosestTP()) || //--- Check manual TP change (order.TakeProfitManual != 0 && currentPosition != order.TakeProfitManual)) { //--- Check manual TP mismatch order.TakeProfitManual = currentPosition; //--- Update manual TP } } } } //--- Update order profits void UpdateOrders() { _wallet.GetOpenOrders().Rewind(); //--- Reset orders iterator while (_wallet.GetOpenOrders().HasNext()) { //--- Iterate orders Order* order = _wallet.GetOpenOrders().Next(); //--- Get order double pipsProfit = order.CalculateProfitPips(); //--- Calculate profit order.CurrentProfitPips = pipsProfit; //--- Update current profit if (pipsProfit < order.LowestProfitPips) { //--- Check if lowest profit order.LowestProfitPips = pipsProfit; //--- Update lowest profit } else if (pipsProfit > order.HighestProfitPips) { //--- Check if highest profit order.HighestProfitPips = pipsProfit; //--- Update highest profit } } } //--- Execute pending close orders bool ExecutePendingCloseOrders() { OrderCollection* pendingCloseOrders = _wallet.GetPendingCloseOrders(); //--- Retrieve pending close orders int ordersToCloseCount = pendingCloseOrders.Count(); //--- Get count if (ordersToCloseCount == 0) { //--- Check if no orders return true; //--- Return true } if (_wallet.AreOrdersBeingOpened()) { //--- Check if orders being opened return true; //--- Return true } int ordersCloseSuccessCount = 0; //--- Initialize success count for (int i = ordersToCloseCount - 1; i >= 0; i--) { //--- Iterate orders Order* pendingCloseOrder = pendingCloseOrders.Get(i); //--- Get order if (pendingCloseOrder.IsAwaitingDealExecution) { //--- Check if awaiting execution ordersCloseSuccessCount++; //--- Increment success count continue; //--- Move to next } bool success; //--- Declare success flag if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { //--- Check netting mode Order* reversedOrder = new Order(pendingCloseOrder, false); //--- Create reversed order reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set opposite type success = OrderRepository::OpenOrder(reversedOrder); //--- Open reversed order if (success) { //--- Check if successful pendingCloseOrder.Ticket = reversedOrder.Ticket; //--- Update ticket } delete(reversedOrder); //--- Delete reversed order } else { success = OrderRepository::ClosePosition(pendingCloseOrder); //--- Close position } if (success) { //--- Check if successful ordersCloseSuccessCount++; //--- Increment success count } } return ordersCloseSuccessCount == ordersToCloseCount; //--- Return true if all successful } //--- Execute pending open orders bool ExecutePendingOpenOrders() { OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); //--- Retrieve pending open orders int ordersToOpenCount = pendingOpenOrders.Count(); //--- Get count if (ordersToOpenCount == 0) { //--- Check if no orders return true; //--- Return true } int ordersOpenSuccessCount = 0; //--- Initialize success count for (int i = ordersToOpenCount - 1; i >= 0; i--) { //--- Iterate orders Order* order = pendingOpenOrders.Get(i); //--- Get order if (order.IsAwaitingDealExecution) { //--- Check if awaiting execution ordersOpenSuccessCount++; //--- Increment success count continue; //--- Move to next } bool isTradeContextFree = false; //--- Initialize trade context flag double StartWaitingTime = GetTickCount(); //--- Start timer while (true) { //--- Wait for trade context if (MQL5InfoInteger(MQL5_TRADE_ALLOWED)) { //--- Check if trade allowed isTradeContextFree = true; //--- Set trade context free break; //--- Exit loop } int MaxWaiting_sec = 10; //--- Set max wait time if (IsStopped()) { //--- Check if EA stopped HandleErrors("The expert was stopped by a user action."); //--- Log error break; //--- Exit loop } if (GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { //--- Check if timeout HandleErrors(StringFormat("The (%d seconds) waiting time exceeded. Trade not allowed: EA disabled, market closed or trade context still not free.", MaxWaiting_sec)); //--- Log error break; //--- Exit loop } Sleep(100); //--- Wait briefly } if (!isTradeContextFree) { //--- Check if trade context not free if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error } continue; //--- Move to next } bool success = OrderRepository::OpenOrder(order); //--- Open order if (success) { //--- Check if successful ordersOpenSuccessCount++; //--- Increment success count } else { if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error } } } return ordersOpenSuccessCount == ordersToOpenCount; //--- Return true if all successful } };
最後に、EAクラスを使ってコアのロジック制御を確立します。このクラスはITraderインターフェースを実装し、HandleTick、Init、GetWallet関数を持っています。これらの関数については、すでに十分に理解しているはずです。private変数として「_firstTick」、「_tradeStrategy」、「_advisorStrategy」、「_moneyManager」、「_wallet」を定義し、状態や各コンポーネントを管理します。EAコンストラクタでは、「_firstTick」をtrueに設定し、「_wallet」を新しいWalletオブジェクトで初期化します。SetLastClosedOrdersByTimeframeで時間軸を設定し、「_advisorStrategy」を新しいAdvisorStrategyオブジェクトで作成し、ASOpenBuyLevel1およびASOpenSellLevel1シグナルをRegisterOpenBuyとRegisterOpenSellで登録します。
さらに、「_moneyManager」をMoneyManagerオブジェクトで初期化し、「_tradeStrategy」をTradeStrategyオブジェクトで作成します。このTradeStrategyにはMultipleOpenModule_1を含め、TakeProfitCloseModule_1とStopLossCloseModule_1をRegisterCloseModuleで登録します。デストラクタでは、すべてのコンポーネントをクリーンアップします。
Init関数では、MQLInfoIntegerでモードフラグを設定し、MarketInfo_LibFuncでロットサイズを構成し、SetOrderGroupingでGetOrderGroupingTypeを使って注文グループを有効化し、LoadOrdersFromBrokerで注文を読み込みます。HandleTick関数ではティックを管理し、テスターモードでなければSyncOrdersで注文を同期します。手動のTP/SL変更が許可されていればSyncManualTPSLChangesで更新し、AskFuncとBidFuncのEvaluate関数で価格を更新します。
UpdateOrdersで利益を更新し、ウォレットと戦略のティックをHandleTickで処理します。保留中の注文がなければ「_tradeStrategy」をEvaluateで評価し、ExecutePendingCloseOrdersとExecutePendingOpenOrdersで保留中の決済やオープンを実行します。失敗した場合はHandleErrorsでエラーを記録します。EAが停止した場合は、SetAllOpenOrdersToPendingCloseですべての注文を決済保留に設定します。これでOnTickイベントハンドラ上でこのEAを呼び出し、取引を管理できる状態になります。
//--- Handle tick event datetime LastActionTime = 0; //--- Track last action time void OnTick() { string AccountServer = AccountInfoString(ACCOUNT_SERVER); //--- Retrieve account server string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY); //--- Retrieve account currency string AccountName = AccountInfoString(ACCOUNT_NAME); //--- Retrieve account name long AccountTradeMode = AccountInfoInteger(ACCOUNT_TRADE_MODE); //--- Retrieve trade mode string ReadableAccountTrademode = ""; //--- Initialize readable trade mode if (AccountTradeMode == 0) ReadableAccountTrademode = "DEMO ACCOUNT"; //--- Set demo mode if (AccountTradeMode == 1) ReadableAccountTrademode = "CONTEST ACCOUNT"; //--- Set contest mode if (AccountTradeMode == 2) ReadableAccountTrademode = "REAL ACCOUNT"; //--- Set real mode long AccountLogin = AccountInfoInteger(ACCOUNT_LOGIN); //--- Retrieve account login string AccountCompany = AccountInfoString(ACCOUNT_COMPANY); //--- Retrieve account company long AccountLeverage = AccountInfoInteger(ACCOUNT_LEVERAGE); //--- Retrieve account leverage long AccountLimitOrders = AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); //--- Retrieve order limit double AccountMarginFree = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Retrieve free margin bool AccountTradeAllowed = AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); //--- Retrieve trade allowed bool AccountTradeExpert = AccountInfoInteger(ACCOUNT_TRADE_EXPERT); //--- Retrieve expert allowed string ReadableAccountMarginMode = ""; //--- Initialize readable margin mode if (AccountMarginMode == 0) ReadableAccountMarginMode = "NETTING MODE"; //--- Set netting mode if (AccountMarginMode == 1) ReadableAccountMarginMode = "EXCHANGE MODE"; //--- Set exchange mode if (AccountMarginMode == 2) ReadableAccountMarginMode = "HEDGING MODE"; //--- Set hedging mode if (OneQuotePerBar) { //--- Check one quote per bar datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time if (LastActionTime == currentTime) { //--- Check if same bar return; //--- Exit } else { LastActionTime = currentTime; //--- Update last action time } } Error = NULL; //--- Clear current error _ea.HandleTick(); //--- Handle tick if (IsDemoLiveOrVisualMode) { //--- Check visual mode MqlDateTime mql_datetime; //--- Declare datetime TimeCurrent(mql_datetime); //--- Get current time string comment = "\n" + (string)mql_datetime.year + "." + (string)mql_datetime.mon + "." + (string)mql_datetime.day + " " + TimeToString(TimeCurrent(), TIME_SECONDS) + OrderInfoComment; //--- Build comment if (DisplayOnChartError) { //--- Check if error display enabled if (Error != NULL) comment += "\n :: Current error : " + Error; //--- Add current error if (ErrorPreviousQuote != NULL) comment += "\n :: Last error : " + ErrorPreviousQuote; //--- Add previous error } comment += ""; //--- Append empty line Comment("ACCOUNT SERVER: ", AccountServer, "\n", //--- Display account info "ACCOUNT CURRENCY: ", AccountCurrency, "\n", "ACCOUNT NAME: ", AccountName, "\n", "ACCOUNT TRADEMODE: ", ReadableAccountTrademode, "\n", "ACCOUNT LOGIN: ", AccountLogin, "\n", "ACCOUNT COMPANY: ", AccountCompany, "\n", "ACCOUNT LEVERAGE: ", AccountLeverage, "\n", "ACCOUNT LIMIT ORDERS: ", AccountLimitOrders, "\n", "ACCOUNT MARGIN FREE: ", AccountMarginFree, "\n", "ACCOUNT TRADING ALLOWED: ", AccountTradeAllowed, "\n", "ACCOUNT EXPERT ALLOWED: ", AccountTradeExpert, "\n", "ACCOUNT MARGIN ALLOWED: ", ReadableAccountMarginMode); } }
ここでは、OnTickイベントハンドラを通じてプログラムのリアルタイム市場更新を管理します。OnTickは新しい価格ティックごとに処理されます。まず、AccountInfoStringでサーバー、通貨、口座名、会社名などの情報を、AccountInfoIntegerで取引モード、ログイン、レバレッジ、注文制限を、AccountInfoDoubleで余剰証拠金を取得して口座情報を収集します。取引モードは「DEMO ACCOUNT」「CONTEST ACCOUNT」「REAL ACCOUNT」のように読みやすい文字列に変換し、AccountMarginModeに格納された証拠金モードは「NETTING MODE」「EXCHANGE MODE」「HEDGING MODE」として表示します。
ティック処理を制御するために、OneQuotePerBarフラグを確認します。有効になっている場合は、iTime関数で現在バーの開始時刻を取得し、LastActionTimeと比較します。両者が一致すれば重複処理を避けるために処理を終了し、一致しなければLastActionTimeを更新します。既存のエラーはErrorをnullにしてクリアし、「_ea」のHandleTick関数を呼び出して戦略のロジックを実行します。
表示用には、IsDemoLiveOrVisualModeを確認し、TimeCurrentとMqlDateTimeでタイムスタンプを整形します。OrderInfoCommentでコメント文字列を作成し、DisplayOnChartErrorが有効な場合はErrorとErrorPreviousQuoteからのエラー情報を追加します。最後にComment関数で口座情報をチャート上に表示し、取引システムのリアルタイム監視とフィードバックを実現します。プログラムを実行すると、次の出力が得られます。
画像から、取引を生成して実行できることが確認できます。この場合は買い取引です。次におこなう必要があるのは、取引の管理です。そのためには、取引の履歴を追跡する必要があり、OnTradeTransactionイベントハンドラを使用して、正常にオープンされた取引を監視し、次のように管理します。
//--- Handle trade transactions void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { switch (trans.type) { //--- Handle transaction type case TRADE_TRANSACTION_DEAL_ADD: { //--- Handle deal addition datetime end = TimeCurrent(); //--- Get current server time datetime start = end - PeriodSeconds(PERIOD_D1); //--- Set start time (1 day ago) HistorySelect(start, end + PeriodSeconds(PERIOD_D1)); //--- Select history int dealsTotal = HistoryDealsTotal(); //--- Get total deals if (dealsTotal == 0) { //--- Check if no deals Print("No deals found"); //--- Log message return; //--- Exit } ulong orderTicketId = HistoryDealGetInteger(trans.deal, DEAL_ORDER); //--- Get order ticket CDealInfo dealInfo; //--- Declare deal info dealInfo.Ticket(trans.deal); //--- Set deal ticket ENUM_DEAL_ENTRY deal_entry = dealInfo.Entry(); //--- Get deal entry type bool found = false; //--- Initialize found flag if (deal_entry == DEAL_ENTRY_IN) { //--- Handle deal entry OrderCollection* pendingOpenOrders = _ea.GetWallet().GetPendingOpenOrders(); //--- Retrieve pending open orders for (int i = 0; i < pendingOpenOrders.Count(); i++) { //--- Iterate orders Order* order = pendingOpenOrders.Get(i); //--- Get order if (order.Ticket == orderTicketId) { //--- Check matching ticket found = true; //--- Set found flag order.OpenTime = dealInfo.Time(); //--- Set open time order.OpenPrice = trans.price; //--- Set open price order.TradePrice = order.OpenPrice; //--- Set trade price if (OrderFillingType == ORDER_FILLING_FOK) { //--- Check FOK filling order.OrderFilledLots += trans.volume; //--- Add volume if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled order.IsAwaitingDealExecution = false; //--- Clear execution flag order.Lots = order.OrderFilledLots; //--- Update lots order.TradeVolume = order.Lots; //--- Update trade volume _ea.GetWallet().SetPendingOpenOrderToOpen(order); //--- Move to open Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } else { order.IsAwaitingDealExecution = false; //--- Clear execution flag bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference order.OrderFilledLots += trans.volume; //--- Add volume order.Lots = order.OrderFilledLots; //--- Update lots order.TradeVolume = order.Lots; //--- Update trade volume if (actualVolumeDiffers) { //--- Check if volume differs Print("Broker executed volume differs from requested volume. Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission } _ea.GetWallet().SetPendingOpenOrderToOpen(order); //--- Move to open Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } } } else if (deal_entry == DEAL_ENTRY_OUT) { //--- Handle deal exit OrderCollection* pendingCloseOrders = _ea.GetWallet().GetPendingCloseOrders(); //--- Retrieve pending close orders for (int i = 0; i < pendingCloseOrders.Count(); i++) { //--- Iterate orders Order* order = pendingCloseOrders.Get(i); //--- Get order if (order.Ticket == orderTicketId) { //--- Check matching ticket found = true; //--- Set found flag if (OrderFillingType == ORDER_FILLING_FOK) { //--- Check FOK filling order.OrderFilledLots += trans.volume; //--- Add volume if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled order.IsAwaitingDealExecution = false; //--- Clear execution flag order.CloseTime = dealInfo.Time(); //--- Set close time order.ClosePrice = trans.price; //--- Set close price if (order.MagicNumber == MagicNumber) { //--- Check EA order TotalCommission += order.Commission; //--- Add commission } if (IsDemoLiveOrVisualMode) { //--- Check visual mode AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line } if (order.ParentOrder != NULL) { //--- Check if parent order order.ParentOrder.Paint(); //--- Redraw parent } _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } else { bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference if (actualVolumeDiffers) { //--- Handle partial close Print("Broker executed volume differs from requested volume.Requested volume: " + DoubleToStr(order.Lots) + ".Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference Order* remainderOrder = new Order(order, false); //--- Create remainder order remainderOrder.Ticket = 0; //--- Clear ticket remainderOrder.Lots = order.Lots - trans.volume; //--- Set remaining volume remainderOrder.TradeVolume = remainderOrder.Lots; //--- Update trade volume OrderRepository::CalculateAndSetCommision(remainderOrder); //--- Recalculate commission _ea.GetWallet().GetPendingCloseOrders().Add(remainderOrder); //--- Add remainder order.Lots = trans.volume; //--- Update original volume order.TradeVolume = order.Lots; //--- Update trade volume OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission } else { Print("Broker executed volume: " + DoubleToStr(trans.volume)); //--- Log volume } order.IsAwaitingDealExecution = false; //--- Clear execution flag order.CloseTime = dealInfo.Time(); //--- Set close time order.ClosePrice = trans.price; //--- Set close price if (order.MagicNumber == MagicNumber) { //--- Check EA order TotalCommission += order.Commission; //--- Add commission } if (IsDemoLiveOrVisualMode) { //--- Check visual mode AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line } _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } } } if (found) { //--- Check if deal found Print("Updated order with deal info."); //--- Log update } else if (trans.symbol == Symbol() && dealInfo.Magic() == MagicNumber) { //--- Check EA deal Print("Couldn't find deal info for place/done order"); //--- Log missing deal } break; } } }
OnTradeTransactionイベントハンドラではMqlTradeTransactionイベントを処理します。ここではTRADE_TRANSACTION_DEAL_ADDトランザクションに注目し、TimeCurrentとPeriodSecondsを使ってHistorySelectで1日分の履歴ウィンドウを取得します。HistoryDealsTotalで取引が見つからなければ、Printでメッセージを記録して処理を終了します。各取引については、HistoryDealGetIntegerで注文チケットを取得し、Ticketを使ってCDealInfoオブジェクトを作成し、Entryでエントリータイプを確認します。
DEAL_ENTRY_IN(新規取引)の場合、「_ea」のWalletからGetPendingOpenOrdersを反復処理し、チケットを照合してtransからTimeとpriceを使って注文を更新します。OrderFillingTypeがORDER_FILLING_FOKの場合、OrderFilledLotsを更新し、注文が執行されていればIsAwaitingDealExecutionをクリアしてSetPendingOpenOrderToOpenで注文をオープン状態に移動します。それ以外の場合は部分執行を処理し、Printで出来高の差分を記録し、CalculateAndSetCommisionで更新します。
DEAL_ENTRY_OUT(決済)の場合は、GetPendingCloseOrdersを処理し、照合した注文を同様に更新します。IsDemoLiveOrVisualModeであればAnyChartObjectDeleteでチャート上のラインを削除し、TotalCommissionに手数料を加算し、SetPendingCloseOrderToClosedで注文をクローズ状態に移動します。部分決済の場合は、残りの出来高に対してCalculateAndSetCommisionで新しい注文を作成します。Printで更新状況やエラーを記録し、正確な取引追跡を保証します。コンパイルすると、次の出力が得られます。
画像から、取引の実行を管理し、メタデータをウィンドウ上に表示していることが確認できます。残る作業は、プログラムが正しく動作することを確認するためのバックテストであり、これは次のセクションで扱います。
バックテストと最適化
プログラムをテストしたところ、見落としていた点があることに気付きました。それは、チャートからプログラムを削除した際に、プログラムおよびそのコンポーネントを適切に解放していなかったことです。問題は次のとおりです。
画像から、問題の状況が確認できます。この問題に対応するために、OnDeinitイベントハンドラで以下のロジックを実装しました。
//--- Deinitialize Expert Advisor void OnDeinit(const int reason) { Comment(""); delete(_ea); //--- Delete EA instance delete(AskFunc); //--- Delete Ask function delete(BidFunc); //--- Delete Bid function }
OnDeinitイベントハンドラを使って、プログラムのクリーンアップ処理をおこないました。OnDeinitは、エキスパートアドバイザーが削除されたときやターミナルが終了したときに発動します。まず、Comment関数を使ってチャート上のコメントをクリアし、チャート表示を整えました。次に、delete演算子を使ってメインのEAインスタンスである「_ea」オブジェクトを削除し、メモリを解放しました。また、価格データ取得を担当するAskFuncおよびBidFuncオブジェクトも削除し、リソースを確保しました。
この簡潔な処理により、適切なデイニシャライズがおこなわれ、メモリリークを防ぎ、プログラム停止時でもシステムの効率を維持できました。これで問題は解決され、デフォルト設定のまま、または取引モジュールやリスクモジュールの設定を1%、5、30、10、60の入力値に変更して最適化とバックテストをおこなった結果、次のような成果が得られました。
バックテストグラフ
バックテストレポート
結論
本記事では、Envelopes Trend Bounce Scalping戦略のMQL5実装を開発しました。この実装により、エンベロープ、移動平均、相対力指数(RSI: Relative Strength Index)といったインジケーターから得られる精密なシグナルに基づく自動取引が可能になり、ポジションサイズの制御や損失保護を統合したリスク管理も組み込まれています。モジュール化されたフレームワークは、動的なシグナル評価、取引実行、リアルタイムの注文監視を備え、スキャルピングのための拡張性の高い基盤を提供します。シグナルの閾値を調整したり、リスクパラメータを最適化したり、追加のインジケーターを組み込むことで、取引目標に合わせてさらに精緻化することも可能です。
免責条項:この実装は教育目的のみに提供されるものであり、取引には重大な財務リスクが伴います。市場の変動により大きな損失が生じる可能性があり、実際の取引で使用する前には十分なバックテストと慎重なリスク管理が不可欠です。
これらの手法を習得することで、プログラムの適応性や堅牢性を高めたり、同様の構造を活用して他の取引戦略を開発したりすることができ、取引アルゴリズムの進化に役立てることができます。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18298
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索