English Русский Deutsch
preview
多通貨エキスパートアドバイザーの開発(第2回):取引戦略の仮想ポジションへの移行

多通貨エキスパートアドバイザーの開発(第2回):取引戦略の仮想ポジションへの移行

MetaTrader 5トレーディング | 26 6月 2024, 14:26
44 0
Yuriy Bykov
Yuriy Bykov

はじめに

前回の記事では、様々な取引戦略と同時に機能する多通貨EAの開発に着手しました。最初の段階では2種類の戦略しかありませんでした。これらは同じ取引アイデアの実装を表し、同じ取引商品(銘柄)とチャート期間(時間枠)で動作しました。両者の違いはパラメータの数値のみでした。

また、希望する最大ドローダウンレベル(入金の10%)に基づいて、ポジションの最適なサイズを決定しました。それぞれの戦略についてこれを別々におこないました。この2つの戦略を組み合わせると、所定のドローダウンレベルを維持するために、建てたポジションのサイズを小さくしなければなりませんでした。減少幅は2つの戦略については小さいものでしたが、何十、何百もの戦略インスタンスを組み合わせたい場合はどうするのでしょうか。戦略によっては、ブローカーが許容するポジションの最小サイズより小さい値にポジションサイズを縮小しなければならないことも十分にあり得ます。この場合、これらの戦略は取引に参加できません。どうすればうまくいくのでしょうか。

そのためには、戦略から、独自にポジションを建てたり、予約注文を出したりする権利を奪います。戦略は仮想取引をおこなうだけでよいのです。つまり、ある大きさのポジションをどのレベルで建てるべきかを記憶し、要求に応じて今どの程度の数量を建てるべきかを報告します。すべての戦略を調査し、所定のドローダウンを維持するためのスケーリングを考慮して、必要な総量を計算した後にのみ、実際のマーケットポジションを建てます。

今は、この方法の適合性をテストすることにしか興味がなく、その実装の効率性には興味がありません。したがって、この記事の枠組みの中で、この方法の少なくともいくつかの実用的な実装を開発しようと試みます。間違いを避ける方法に関する知識がすでにあるため、後で建築の観点からより美しいものを構築するのに役立ちます。

これを実装してみましょう。


過去の業績の再検討

CAdvisor EAクラスを開発しました。このクラスは、取引戦略のインスタンスの配列(より正確には、インスタンスへのポインタ)を格納します。これにより、メインプログラムにEAのインスタンスを1つ作成し、戦略クラスのインスタンスを複数追加することができます。この配列はCStrategy基本クラスのオブジェクトへのポインタを格納するので、CStrategyから継承された子孫クラスのオブジェクトへのポインタを格納することができます。私たちの場合、CSimpleVolumesStrategyという子孫クラスを1つ作成し、その2つのオブジェクトをEAのこの配列に追加しました。

示しやすくするために、便利な名前に同意しましょう。

  • EAは最終的なmq5ファイルで、コンパイル後、テスターや端末で実行するのに適した実行可能なex5ファイルになる
  • EAはプログラムで宣言されたCAdvisorクラスのオブジェクトで、1つのプログラムで1つのEAインスタンスだけを使用する
  • 戦略は、戦略の基本クラスであるCStrategyから継承された子クラスのオブジェクト 

また、オブジェクト(戦略やその他のクラス)へのポインタは、以前に作成されたオブジェクト(簡略化されたもの)のメモリ上の位置に関する情報であることを思い出してみましょう。これにより、オブジェクトを関数に渡したり、新しい変数や配列要素を代入したりする際に、同じオブジェクトを別のメモリロケーションに再作成することを避けることができます。

そのため、EAでは戦略オブジェクトへのポインタを配列に格納し、この配列を埋める際に戦略オブジェクトのコピーが作成されないようにしています。そして、戦略配列の要素にアクセスするときは、元の戦略オブジェクトにアクセスします。

EAの作業は以下の段階から構成されていました。

  • 静的メモリ領域のEAが作成される
  • プログラムが初期化されると、2つの戦略が動的メモリに作成され、それらへのポインタがEAに格納される
  • プログラムの実行中、EAはCStrategy::Tick()メソッドを呼び出すことで、必要な取引アクションを実行するために各戦略を連続的に呼び出す
  • プログラムの初期化を解除する際に、EAが動的メモリから戦略オブジェクトを削除する

始める前に、EAとEAクラスを少し修正しておきましょう。EAでは、エキスパートが動的メモリ領域に作成されるようにします。

CAdvisor     expert;          // EA object
CAdvisor     *expert;         // Pointer to the EA object

int OnInit() {
   expert = new CAdvisor();   // Create EA object
   
   // The rest of the code from OnInit() ...
}

EAクラスでは、デストラクタ(EAオブジェクトがメモリから削除されたときに自動的に呼び出される関数)を作成します。デストラクタは、CAdvisor::Deinit()メソッドのストラテジオブジェクトを動的メモリから削除する操作を受け取ります。このメソッドは必要ありません。削除しましょう。戦略の数を格納するクラス変数m_strategiesCountも削除します。必要に応じてArraySize()を使用することができます。

class CAdvisor {
protected:
   CStrategy         *m_strategies[];  // Array of trading strategies
public:
   ~CAdvisor();                        // Destructor

   // ...
};

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
void CAdvisor::~CAdvisor() {
// Delete all strategy objects
   for(int i = 0; i < ArraySize(m_strategies); i++) {
      delete m_strategies[i];
   }
}

OnDeinit()関数の中で、CAdvisor::Deinit()メソッドをEAオブジェクトの削除に置き換えます。

void OnDeinit(const int reason) {
   expert.Deinit();
   delete expert;
}


ロードマップ

取引戦略が自らマーケットポジションを建てることができなくなったのであれば、次のようになります。 

  • 戦略の仮想ポジションに関する情報を格納するオブジェクトが必要
  • 仮想ポジションの情報を実際のマーケットポジションに変換するオブジェクトが必要

仮想ポジションのためのオブジェクトは戦略の不可欠な一部であるべきで、いくつかあるべきです。したがって、最初の新しいクラスCVirtualOrderを呼び出し、これらのオブジェクトの配列をCStrategy戦略クラスに追加しましょう。CStrategyはまた、未決済の仮想ポジションの変化を表すプロパティと、その値を取得設定するためのメソッドを取得します。このプロパティは、戦略が現在どちらの状態にあるかを決定します。

  • 変化なし:オープン仮想ボリューム全体が市場にリリースされた
  • 変化あり:仮想ボリュームが市場のボリュームと一致しないため、実際の市場ポジションのボリュームを調整する必要がある
今のところ、この2つの状態で十分だと思われるので、このモデルに限定することにします。

現在、誰かが実際のポジションを建てる責任を負っているので、m_magicマジックナンバープロパティを戦略基本クラスから削除することができます。将来的には、最も基本的な戦略のクラスをさらにクリーンアップする予定ですが、今のところは部分的なクリーンアップにとどめます。

これで、基となる戦略クラスは次のようになります。

#include "VirtualOrder.mqh"

//+------------------------------------------------------------------+
//| Base class of the trading strategy                               |
//+------------------------------------------------------------------+
class CStrategy {
protected:
   string            m_symbol;         // Symbol (trading instrument)
   ENUM_TIMEFRAMES   m_timeframe;      // Chart period (timeframe)
   double            m_fixedLot;       // Size of opened positions (fixed)

   CVirtualOrder     m_orders[];       // Array of virtual positions (orders)
   int               m_ordersTotal;    // Total number of open positions and orders
   double            m_volumeTotal;    // Total volume of open positions and orders

   bool              m_isChanged;      // Sign of changes in open virtual positions
   void              CountOrders();    // Calculate the number and volumes of open positions and orders

public:
   // Constructor
   CStrategy(string p_symbol = "",
             ENUM_TIMEFRAMES p_timeframe = PERIOD_CURRENT,
             double p_fixedLot = 0.01);

   virtual void      Tick();           // Main method - handling OnTick events
   virtual double    Volume();         // Total volume of virtual positions
   virtual string    Symbol();         // Strategy symbol (only one for a single strategy so far)
   virtual bool      IsChanged();      // Are there any changes in open virtual positions?
   virtual void      ResetChanges();   // Reset the sign of changes in open virtual positions
};

すでにSymbol()IsChanged()ResetChanges()メソッドを実装することができます。

//+------------------------------------------------------------------+
//| Strategy symbol                                                  |
//+------------------------------------------------------------------+
string CStrategy::Symbol() {
   return m_symbol;
}

//+------------------------------------------------------------------+
//| Are there any changes to open virtual positions?                 |
//+------------------------------------------------------------------+
bool CStrategy::IsChanged() {
   return m_isChanged;
}

//+------------------------------------------------------------------+
//| Reset the flag for changes in virtual positions                  |
//+------------------------------------------------------------------+
void CStrategy::ResetChanges() {
   m_isChanged = false;
}

残りのメソッド(Tick()Volume()CountOrders())は、後で基本クラスの子孫かクラス自体に実装します。

2つ目の新しいクラスは、戦略の仮想ポジションを市場にもたらすことに関与するオブジェクトで、CReceiverと呼ばれます。このオブジェクトが機能するためには、すべてのEA戦略にアクセスし、どの銘柄とどの出来高を使用して実際のポジションを建てるべきかを見つけることができなければなりません。このようなオブジェクトは、1つのEAに1つあれば十分です。CReceiverオブジェクトは、建てたマーケットポジションに設定されるマジックナンバーを持つべきです。

#include "Strategy.mqh"

//+------------------------------------------------------------------+
//| Base class for converting open volumes into market positions     |
//+------------------------------------------------------------------+
class CReceiver {
protected:
   CStrategy         *m_strategies[];  // Array of strategies
   ulong             m_magic;          // Magic

public:
   CReceiver(ulong p_magic = 0);                // Constructor
   virtual void      Add(CStrategy *strategy);  // Adding strategy
   virtual bool      Correct();                 // Adjustment of open volumes
};

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CReceiver::CReceiver(ulong p_magic) : m_magic(p_magic) {
   ArrayResize(m_strategies, 0, 128);
}

//+------------------------------------------------------------------+
//| Add strategy                                                     |
//+------------------------------------------------------------------+
void CReceiver::Add(CStrategy *strategy) {
   APPEND(m_strategies, strategy);
}

//+------------------------------------------------------------------+
//| Adjust open volumes                                              |
//+------------------------------------------------------------------+
bool CReceiver::Correct() {
   return true;
}

この基本クラスは、特定のボリューム調整メカニズムの実装を含みません。したがって、このクラスの異なる子孫で、調整の異なる実装を作成することができるでしょう。このクラスオブジェクトは、今のところ、マーケットポジションを建てる戦略のスタブとして機能します。調整メカニズムをデバッグするにはこれが必要になります。つまり、戦略自身が実際の取引をおこなうEAがどのポジションを建てるのか、戦略が仮想取引のみをおこなうEAがどのポジションを建てるのかを比較する必要があります。

そこで、前回の戦略そのものが実際の取引をおこなうEAを2つ用意します。

最初のEAでは、戦略は1つのインスタンスになります。そのパラメータで、最適化のための単一戦略インスタンスのパラメータを指定することができます。

2番目のEAには、1つ目のEAの最適化の結果として得られた、あらかじめ定義されたパラメータを持つ取引戦略のインスタンスが複数含まれます。


戦略パラメータを最適化するEA

前回は、CStrategyクラスのオブジェクトの形ではなく、戦略の実装を使用して戦略のパラメータを最適化しました。しかし、今ではすでにCSimpleVolumesStrategyという既成のクラスがあるので、EAにこの戦略のインスタンスを1つ組み込む別のプログラムを作成しましょう。このクラスは、戦略自体が市場ポジションを開くことを強調するために、少し異なる名前で呼ばれます。CSimpleVolumesStrategyの代わりにCSimpleVolumesMarketStrategyを使用し、現在のフォルダのSimpleVolumesMarketStrategy.mqhファイルに保存します。

EAファイルでは、戦略のパラメータをEAの入力変数からロードさせ、EAオブジェクトに戦略のインスタンスを1つ追加します。戦略パラメータを最適化できるEAを手に入れます。 

#include "Advisor.mqh"
#include "SimpleVolumesMarketStrategy.mqh"
#include "VolumeReceiver.mqh"

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input string      symbol_              = "EURGBP";    // Trading instrument (symbol)
input ENUM_TIMEFRAMES
timeframe_           = PERIOD_H1;   // Chart period

input group "===  Opening signal parameters"
input int         signalPeriod_        = 130;   // Number of candles for volume averaging
input double      signalDeviation_     = 0.9;   // Relative deviation from the average to open the first order 
input double      signaAddlDeviation_  = 1.4;   // Relative deviation from the average for opening the second and subsequent orders

input group "===  Pending order parameters"
input int         openDistance_        = 0;     // Distance from price to pending order
input double      stopLevel_           = 2000;  // Stop Loss (in points)
input double      takeLevel_           = 475;   // Take Profit (in points)
input int         ordersExpiration_    = 6000;  // Pending order expiration time (in minutes)

input group "===  Money management parameters"
input int         maxCountOfOrders_    = 3;     // Maximum number of simultaneously open orders
input double      fixedLot_            = 0.01;  // Single order volume

input group "===  EA parameters"
input ulong       magic_              = 27181; // Magic

CAdvisor     *expert;         // Pointer to the EA object

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   expert = new CAdvisor();
   expert.Add(new CSimpleVolumesMarketStrategy(
                 magic_, symbol_, timeframe_,
                 fixedLot_,
                 signalPeriod_, signalDeviation_, signaAddlDeviation_,
                 openDistance_, stopLevel_, takeLevel_, ordersExpiration_,
                 maxCountOfOrders_)
             );       // Add one strategy instance

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   expert.Tick();
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   delete expert;
}

SimpleVolumesMarketExpertSingle.mq5ファイルの現在のフォルダに保存しましょう。

では、課題の実施を単純化するために、取引戦略を少し複雑にしてみましょう。未決注文ではなく、マーケットポジションを使用する戦略を仮想取引に移行する方が簡単でしょう。現在の戦略のバージョンは、未決済注文でのみ機能します。openDistance_戦略にパラメータ値分析を追加してみましょう。もしそれがゼロより上なら、戦略はBUY_STOPとSELL_STOPの予約注文を出します。もしそれがゼロより下であれば、戦略はBUY_LIMITとSELL_LIMITの予約注文を出します。この値がゼロの場合、成り行き注文を出します。 

これをおこなうには、CSimpleVolumesMarketStrategy::OpenBuyOrder()とCSimpleVolumesMarketStrategy::OpenSellOrder()メソッドのコードを変更するだけです。

void CSimpleVolumesMarketStrategy::OpenBuyOrder() {
// Previous code in the method ...

// Order volume
   double lot = m_fixedLot;

// Set a pending order
   bool res = false;
   if(openDistance_ > 0) {
      res = trade.BuyStop(lot, ...);
   } else if(openDistance_ < 0) {
      res = trade.BuyLimit(lot, ...);
   } else {
      res = trade.Buy(lot, ...);
   }

   if(!res) {
      Print("Error opening order");
   }
}

戦略に必要なもう1つの変更は、初期化コードをInit()メソッドから戦略のコンストラクタに移すことです。これは、そのコードが戦略コンストラクタ内に配置されていると仮定して、EAが戦略の初期化メソッドを呼び出さないようにするために必要なことです。

新しいEAをコンパイルし、3つの銘柄(EURGBP、GBPUSD、EURUSD)を使用してH1に最適化するように設定してみましょう。

図1:パラメータ[EURGBP, H1, 17, 1.7, 0.5, 0, 16500, 100, 52000, 3, 0.01]のテスト結果

図1:パラメータ[EURGBP, H1, 17, 1.7, 0.5, 0, 16500, 100, 52000, 3, 0.01]のテスト結果

最適化の結果からパラメータの良いオプションをいくつか選択し(例えば、各銘柄について3つのオプション)、選択したパラメータを持つ戦略のインスタンスを9つ作成する2番目のEAを作成してみましょう。それぞれの事例について、1つの戦略のドローダウンが10%を超えない最適なポジションのサイズを計算します。計算メソッドは前回の記事で説明しました。

EAのパフォーマンスの変化を示すために、対象とする戦略を設定できるようにします。そのためにまず、9つの要素からなる配列にすべての戦略インスタンスを配置します。戦略配列の初期インデックスを設定するstartIndex_入力を追加しましょう。totalStrategies_ パラメータは、startIndex_を起点として、配列からいくつの戦略を連続して起動するかを決定します。初期化の最後に、配列から対応する戦略をEAオブジェクトに追加します。

#include "Advisor.mqh"
#include "SimpleVolumesMarketStrategy.mqh"

input int startIndex_      = 0;        // Starting index
input int totalStrategies_ = 1;        // Number of strategies
input double depoPart_     = 1.0;      // Part of the deposit for one strategy
input ulong  magic_        = 27182;    // Magic

CAdvisor     *expert;                  // EA object

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// Check if the parameters are correct
   if(startIndex_ < 0 || startIndex_ + totalStrategies_ > 9) {
      return INIT_PARAMETERS_INCORRECT;
   }

// Create and fill the array of strategy instances
   CStrategy *strategies[9];
   strategies[0] = new CSimpleVolumesMarketStrategy(
      magic_ + 0, "EURGBP", PERIOD_H1,
      NormalizeDouble(0.01 / 0.16 * depoPart_, 2),
      13, 0.3, 1.0, 0, 10500, 465, 1000, 3);
   strategies[1] = new CSimpleVolumesMarketStrategy(
      magic_ + 1, "EURGBP", PERIOD_H1,
      NormalizeDouble(0.01 / 0.09 * depoPart_, 2),
      17, 1.7, 0.5, 0, 16500, 220, 1000, 3);
   strategies[2] = new CSimpleVolumesMarketStrategy(
      magic_ + 2, "EURGBP", PERIOD_H1,
      NormalizeDouble(0.01 / 0.16 * depoPart_, 2),
      51, 0.5, 1.1, 0, 19500, 370, 22000, 3);
   strategies[3] = new CSimpleVolumesMarketStrategy(
      magic_ + 3, "GBPUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.25 * depoPart_, 2),
      80, 1.1, 0.2, 0, 6000, 1190, 1000, 3);
   strategies[4] = new CSimpleVolumesMarketStrategy(
      magic_ + 4, "GBPUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.09 * depoPart_, 2),
      128, 2.0, 0.9, 0, 2000, 1170, 1000, 3);
   strategies[5] = new CSimpleVolumesMarketStrategy(
      magic_ + 5, "GBPUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.14 * depoPart_, 2),
      13, 1.5, 0.8, 0, 2500, 1375, 1000, 3);
   strategies[6] = new CSimpleVolumesMarketStrategy(
      magic_ + 6, "EURUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.23 * depoPart_, 2),
      24, 0.1, 0.3, 0, 7500, 2400, 24000, 3);
   strategies[7] = new CSimpleVolumesMarketStrategy(
      magic_ + 7, "EURUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.20 * depoPart_, 2),
      18, 0.2, 0.4, 0, 19500, 1480, 6000, 3);
   strategies[8] = new CSimpleVolumesMarketStrategy(
      magic_ + 8, "EURUSD", PERIOD_H1,
      NormalizeDouble(0.01 / 0.22 * depoPart_, 2),
      128, 0.7, 0.3, 0, 3000, 170, 42000, 3);

   expert = new CAdvisor();

// Add the necessary strategies to the EA
   for(int i = startIndex_; i < startIndex_ + totalStrategies_; i++) {
      expert.Add(strategies[i]);
   }

   return(INIT_SUCCEEDED);
}

void OnTick() {
   expert.Tick();
}

void OnDeinit(const int reason) {
   delete expert;
}

このおかげで、戦略配列の戦略の初期インデックスを最適化することで、戦略のインスタンスごとに結果を得ることができます。10万ドルの初期入金で開始すると、結果は以下のようになります。


図2:9つの戦略インスタンスの単一実行結果

ドローダウンは、建てるポジションの最適なサイズを選択する際に計画したとおり、初期入金の約1%、すなわち約1000ドルであることは明らかです。平均シャープレシオは1.3です。

では、すべてのインスタンスをオンにして、ドローダウンを1000ドルに維持するために、適切なdepoPart_乗数を選択してみましょう。depoPart_ = 0.38の場合、ドローダウンは許容範囲内にとどまります。


図3:9つの戦略インスタンスの同時運用テスト結果

戦略の単一コピーの結果と、すべてのコピーの同時作業の結果を比較すると、同じドローダウンで利益が約3倍に増加し、シャープレシオも1.3から2.84に増加したことがわかります。

さて、仕事の中核に集中しましょう。


仮想ポジションのクラス(注文)

約束したCVirtualOrderクラスを作成し、ポジションのすべてのプロパティを格納するフィールドを追加します。

class CVirtualOrder {
private:
//--- Order (position) properties
   ulong             m_id;          // Unique ID 

   string            m_symbol;      // Symbol
   double            m_lot;         // Volume
   ENUM_ORDER_TYPE   m_type;        // Type
   double            m_openPrice;   // Open price
   double            m_stopLoss;    // StopLoss level
   double            m_takeProfit;  // TakeProfit level
   string            m_comment;     // Comment

   datetime          m_openTime;    // Open time

//--- Closed order (position) properties
   double            m_closePrice;  // Close price
   datetime          m_closeTime;   // Close time
   string            m_closeReason; // Closure reason

   double            m_point;       // Point value

   bool              m_isStopLoss;  // StopLoss activation property
   bool              m_isTakeProfit;// TakeProfit activation property
};

各仮想ポジションは一意のIDを持つべきです。そこで、s_count クラスのstatic変数を追加して、プログラム中に生成されたすべてのポジションオブジェクトの数をカウントすることにしましょう。新しいポジションオブジェクトが作成されると、このカウンタは1だけインクリメントされ、この値がユニークなポジション番号となります。s_countの初期値を0に設定します。

また、価格情報のためにCsymbolInfoクラスのオブジェクトが必要になります。これもクラスのstaticメンバーにしましょう。 

class CVirtualOrder {
private:
   static int        s_count;
   static
   CSymbolInfo       s_symbolInfo;

//--- Order (position) properties ...

};

int               CVirtualOrder::s_count = 0;
CSymbolInfo       CVirtualOrder::s_symbolInfo;

仮想ポジションオブジェクトの作成と仮想ポジションを「建てる」のは異なる操作であることに注意すべきです。ポジションオブジェクトは事前に作成し、戦略が仮想ポジションをオープンしたい瞬間を待つことができます。この時、ポジションのプロパティには、銘柄、出来高、始値などの現在の値が入力されます。戦略がポジションのクローズを決定すると、オブジェクトはクローズプロパティの値(価格、時間、クローズの理由)を保存します。仮想ポジションを建てる次の操作では、オブジェクトの同じインスタンスを使用し、そのクローズプロパティをクリアして、銘柄、出来高、始値などの新しい値で再度埋めることができます。

このクラスにメソッドを追加してみましょう。仮想ポジションを開いたり閉じたりするpublicメソッドとコンストラクタが必要です。ポジションの状態(開いているのか、どの方向なのか)と、その最も重要なプロパティ(出来高と現在の利益)を確認するメソッドも便利です。

class CVirtualOrder {
//--- Previous code...

public:
                     CVirtualOrder();  // Constructor
                     
//--- Methods for checking the order (position) status
   bool              IsOpen();         // Is the order open?
   bool              IsMarketOrder();  // Is this a market position?
   bool              IsBuyOrder();     // Is this an open BUY position?
   bool              IsSellOrder();    // Is this an open SELL position?
  
//--- Methods for obtaining order (position) properties
   double            Volume();         // Volume with direction 
   double            Profit();         // Current profit 

//--- Methods for handling orders (positions)
   bool              Open(string symbol,
                          ENUM_ORDER_TYPE type,
                          double lot,
                          double sl = 0,
                          double tp = 0,
                          string comment = "",
                          bool inPoints = true);   // Opening an order (position)
   bool              Close();                      // Closing an order (position)
};

これらのメソッドのいくつかは、実装が非常にシンプルで短いので、例えばクラス宣言のすぐ内側に置くことができます。

class CVirtualOrder : public CObject {
// ...

//--- Methods for checking the order (position) status
   bool              IsOpen() {        // Is the order open?
      return(this.m_openTime > 0 && this.m_closeTime == 0);
   };
   bool              IsMarketOrder() { // Is this a market position?
      return IsOpen() && (m_type == ORDER_TYPE_BUY || m_type == ORDER_TYPE_SELL);
   }
   
// ...
};

コンストラクタは、初期化リストを通じて、一意なIDを除く仮想ポジションのすべてのプロパティに空の(明らかに無効という意味での)値を代入します。すでに述べたように、コンストラクタでは、IDは以前に作成されたクラスオブジェクトのカウンタの値から取得した値が割り当てられます。この値は、EA全体を通してそのまま維持されます。割り当てる前に、作成されたオブジェクトのカウンタをインクリメントします。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualOrder::CVirtualOrder() :
// Initialization list
   m_id(++s_count),  // New ID = object counter + 1
   m_symbol(""),
   m_lot(0),
   m_type(-1),
   m_openPrice(0),
   m_stopLoss(0),
   m_takeProfit(0),
   m_openTime(0),
   m_comment(""),
   m_closePrice(0),
   m_closeTime(0),
   m_closeReason(""),
   m_point(0) {
}

CVirtualOrderクラスのメソッドをさらに実装する前に、少し先を見て、このクラスをどのように使用するかを考えてみましょう。実際のマーケットポジションを建てる戦略オブジェクトはあります。さらに、オープンマーケットポジションの最大数(戦略パラメータで設定)もわかっています。仮想ポジションに移行しますが、そうなると、その数も限られてきます。つまり、戦略内に仮想ポジションオブジェクトの配列を作成し、戦略を初期化する際に必要な数の仮想ポジションをその配列に入れ、その後、この配列のみを操作することができます。

新しいポジションを建てる条件が整ったら、まだ建てていない仮想ポジションを配列から取り出し、ポジションに変えます。ポジションを強制的にクローズする条件が整えば、クローズポジションに転換します。

仮想ポジションがある限り、どの戦略もこれらのオブジェクトをティックごとに同じように処理しなければなりません。すべてを順番に調べ、StopLossまたはTakeProfitレベルに達したかどうかを確認し、Yesの場合はポジションをクローズします。この同一性によって、仮想ビヘイビアポジションを処理する実装を独自のクラスに移し、戦略から対応するメソッドだけを呼び出すことができます。

CVirtualOrderクラスはTick()メソッドを受け取ります。Tick()メソッドは、ポジションをクローズするための条件を確認し、もし条件が満たされれば、ポジションはクローズ状態に移行します。ポジションの状態に変化があれば、このメソッドはtrueを返します。

また、複数の仮想ポジションオブジェクトを一度に処理するTick()静的メソッドも追加しましょう。そのようなオブジェクトの配列へのリンクをパラメータとして受け取ります。Tick()メソッドは、各配列オブジェクトに対して呼び出されます。少なくとも1つの仮想ポジションがクローズしていれば、最終的にtrueが返されます。

class CVirtualOrder {
private:
   //...
public:
   //...
   
//--- Methods for handling orders (positions)
   bool              Open(string symbol,
                          ENUM_ORDER_TYPE type,
                          double lot,
                          double sl = 0,
                          double tp = 0,
                          string comment = "",
                          bool inPoints = false
                         );      // Open order (position)

   bool              Tick();     // Handle tick for an order (position)
   bool              Close();    // Close an order (position)

   static bool       Tick(CVirtualOrder &orders[]);   // Handle a tick for the array of virtual orders
};               

//...

//+------------------------------------------------------------------+
//| Handle a tick of a single virtual order (position)               |
//+------------------------------------------------------------------+
bool CVirtualOrder::Tick() {
   if(IsMarketOrder()) {  // If this is a market virtual position
      if(CheckClose()) {  // Check if SL or TP levels have been reached
         Close();         // Close when reached
         return true;     // Return the fact that there are changes in open positions
      }
   }

   return false;
}

//+------------------------------------------------------------------+
//| Handle a tick for the array of virtual orders (positions)        |
//+------------------------------------------------------------------+
bool CVirtualOrder::Tick(CVirtualOrder &orders[]) {
   bool isChanged = false;                      // We assume that there will be no changes
   for(int i = 0; i < ArraySize(orders); i++) { // For all orders (positions)
      isChanged |= orders[i].Tick();            // Check and close if necessary
   }
   return isChanged;
}
//+------------------------------------------------------------------+

このコードを現在のフォルダのVirtualOrder.mqhファイルに保存しましょう。


シンプルな取引戦略クラスの改善

次に、取引戦略クラスに戻り、仮想ポジションを扱えるように変更を加えましょう。すでに合意したように、基となるCStrategyクラスには、仮想ポジションオブジェクトを格納するためのm_orders[]配列がすでにあります。したがって、CSimpleVolumesStrategyクラスでも利用可能です。この戦略にはm_maxCountOfOrdersパラメータがあり、これは同時に建てるポジションの最大数を決定します。そして、コンストラクタの仮想ポジションの配列のサイズを、このパラメータと等しく設定します。

次に、OpenBuyOrder()メソッドとOpenSellOrder()メソッドで実際のポジションを建てるのを、仮想ポジションを建てるのに置き換えるだけです。現在のところ、実際の予約注文の発注を置き換えるものは何もないので、これらの操作をコメントアウトするだけにします。

//+------------------------------------------------------------------+
//| Open BUY order                                                   |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenBuyOrder() {
// ...
   
   if(m_openDistance > 0) {
      /* // Set BUY STOP pending order
         res = trade.BuyStop(lot, ...);  */
   } else if(m_openDistance < 0) {
      /* // Set BUY LIMIT pending order
         res = trade.BuyLimit(lot, ...); */
   } else {
      // Open a virtual BUY position
      for(int i = 0; i < m_maxCountOfOrders; i++) {   // Iterate through all virtual positions
         if(!m_orders[i].IsOpen()) {                  // If we find one that is not open, then open it
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
                                   NormalizeDouble(sl, digits),
                                   NormalizeDouble(tp, digits));
            break;                                    // and exit
         }
      }
   } 

   ... 
}

//+------------------------------------------------------------------+
//| Open SELL order                                                  |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenSellOrder() {
// ...
   
   if(m_openDistance > 0) {
      /* // Set SELL STOP pending order
      res = trade.SellStop(lot, ...);          */
   } else if(m_openDistance < 0) {
      /* // Set SELL LIMIT pending order
      res = trade.SellLimit(lot, ...);         */
   } else {
      // Open a virtual SELL position
      for(int i = 0; i < m_maxCountOfOrders; i++) {   // Iterate through all virtual positions
         if(!m_orders[i].IsOpen()) {                  // If we find one that is not open, then open it
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL, m_fixedLot,
                                   NormalizeDouble(sl, digits),
                                   NormalizeDouble(tp, digits));
            break;                                    // and exit
         }
      }
   }

   ... 
}

現在のフォルダのSimpleVolumesStrategy.mqhファイルに変更を保存します。


オープンボリュームをマーケットポジションに変換するクラスの作成

オープンボリュームをマーケットポジションに変換するオブジェクトの基本クラスはすでに作成しました。これまでのところ、使用されている戦略の配列を入力する以外何もおこなわれません。次に、マーケットにポジションを建てるための特定の実装を含む派生クラスを書く必要があります。CVolumeReceiverクラスを作成しましょう。Correct()メソッドを実装するために、かなり多くのコードを追加する必要があります。これをいくつかのprotectedクラスのメソッドに分解します。

#include "Receiver.mqh"

//+------------------------------------------------------------------+
//| Class for converting open volumes into market positions          |
//+------------------------------------------------------------------+
class CVolumeReceiver : public CReceiver {
protected:
   bool              m_isNetting;      // Is this a netting account?
   string            m_symbols[];      // Array of used symbols

   double            m_minMargin;      // Minimum margin for opening

   CPositionInfo     m_position;
   CSymbolInfo       m_symbolInfo;
   CTrade            m_trade;

   // Filling the array of open market volumes by symbols 
   void              FillSymbolVolumes(double &oldVolumes[]);

   // Correction of open volumes using the array of volumes 
   virtual bool      Correct(double &symbolVolumes[]);

   // Volume correction for this symbol
   bool              CorrectPosition(string symbol, double oldVolume, double diffVolume);

   // Auxiliary methods
   bool              ClearOpen(string symbol, double diffVolume);
   bool              AddBuy(string symbol, double volume);
   bool              AddSell(string symbol, double volume);

   bool              CloseBuyPartial(string symbol, double volume);
   bool              CloseSellPartial(string symbol, double volume);
   bool              CloseHedgingPartial(string symbol, double volume, ENUM_POSITION_TYPE type);
   bool              CloseFull(string symbol = "");

   bool              FreeMarginCheck(string symbol, double volume, ENUM_ORDER_TYPE type);

public:
   CVolumeReceiver(ulong p_magic, double p_minMargin = 100);   // Constructor
   virtual void      Add(CStrategy *strategy) override;        // Add strategy
   virtual bool      Correct() override;                       // Adjustment of open volumes
};

オープンボリューム補正法の一般的なアルゴリズムは以下の通りです。

  • 使用した各銘柄について、すべての戦略を調べ、使用した各銘柄のオープンボリュームの合計を計算します。結果のnewVolumes配列は、次のオーバーロードされたCorrect()メソッドに渡されます。
    //+------------------------------------------------------------------+
    //| Adjustment of open volumes                                       |
    //+------------------------------------------------------------------+
    bool CVolumeReceiver::Correct() {
       int symbolsTotal = ArraySize(m_symbols);
       double newVolumes[];
    
       ArrayResize(newVolumes, symbolsTotal);
       ArrayInitialize(newVolumes, 0);
    
       for(int j = 0; j < symbolsTotal; j++) {  // For each used symbol        
          for(int i = 0; i < ArraySize(m_strategies); i++) { // Iterate through all strategies
             if(m_strategies[i].Symbol() == m_symbols[j]) {  // If the strategy uses this symbol
                newVolumes[j] += m_strategies[i].Volume();   // Add its open volume
             }
          }
       }
       // Call correction of open volumes using the array of volumes
       return Correct(newVolumes);
    }
    
  • 各銘柄について、その銘柄のポジションの数量をどの程度変更するかを定義します。必要に応じて、この銘柄のボリューム補正メソッドを呼び出します。
    //+------------------------------------------------------------------+
    //| Adjusting open volumes using the array of volumes                |
    //+------------------------------------------------------------------+
    bool CVolumeReceiver::Correct(double &newVolumes[]) {
       // ...
       bool res = true;
    
       // For each symbol
       for(int j = 0; j < ArraySize(m_symbols); j++) {
          // ...
          // Define how much the volume of open positions for the symbol should be changed
          double oldVolume = oldVolumes[j];
          double newVolume = newVolumes[j];
          
          // ...
          double diffVolume = newVolume - oldVolume;
          
          // If there is a need to adjust the volume for a given symbol, then do that
          if(MathAbs(diffVolume) > 0.001) {
             res = res && CorrectPosition(m_symbols[j], oldVolume, diffVolume);
          }
       }
    
       return res;
    }
    
  • 1つの銘柄について、前回のオープンボリュームと必要な変化の値に基づいて、実行する必要のある取引操作の種類(追加、クローズ、再オープン)を決定し、対応する補助メソッドを呼び出します。
    //+------------------------------------------------------------------+
    //| Adjust volume by the symbol                                      |
    //+------------------------------------------------------------------+
    bool CVolumeReceiver::CorrectPosition(string symbol, double oldVolume, double diffVolume) {
       bool res = false;
    
       // ...
    
       double volume = MathAbs(diffVolume);
    
       if(oldVolume > 0) { // Have BUY position
          if(diffVolume > 0) { // New BUY position
             res = AddBuy(symbol, volume);
          } else if(diffVolume < 0) { // New SELL position
             if(volume < oldVolume) {
                res = CloseBuyPartial(symbol, volume);
             } else {
                res = CloseFull(symbol);
    
                if(res && volume > oldVolume) {
                   res = AddSell(symbol, volume - oldVolume);
                }
             }
          }
       } else if(oldVolume < 0) { // Have SELL position
          if(diffVolume < 0) { // New SELL position
             res = AddSell(symbol, volume);
          } else if(diffVolume > 0) { // New BUY position
             if(volume < -oldVolume) {
                res = CloseSellPartial(symbol, volume);
             } else {
                res = CloseFull(symbol);
    
                if(res && volume > -oldVolume) {
                   res = AddBuy(symbol, volume + oldVolume);
                }
             }
          }
       } else { // No old position
          res = ClearOpen(symbol, diffVolume);
       }
    
       return res;
    }
    

現在のフォルダのVolumeReceiver.mqhファイルにコードを保存します。


1つの戦略と仮想ポジションを持つEA

SimpleVolumesMarketExpertSingle.mq5ファイルに基づいて、仮想ポジションを持つ取引戦略のインスタンスを1つ使用するEAを作成します。EAのコンストラクタを呼び出す際に、新しいCVolumeReceiverクラスのオブジェクトを渡し、作成された戦略のクラスを置き換えるために、必要なファイルを接続する必要があります。

#include "Advisor.mqh"
#include "SimpleVolumesStrategy.mqh"
#include "VolumeReceiver.mqh"

// Input parameters...

CAdvisor     *expert;         // Pointer to the EA object

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   expert = new CAdvisor(new CVolumeReceiver(magic_));
   expert.Add(new CSimpleVolumesStrategy(
                         symbol_, timeframe_,
                         fixedLot_,
                         signalPeriod_, signalDeviation_, signaAddlDeviation_,
                         openDistance_, stopLevel_, takeLevel_, ordersExpiration_,
                         maxCountOfOrders_)
                     );       // Add one strategy instance

   return(INIT_SUCCEEDED);
}

void OnTick() {
   expert.Tick();
}

void OnDeinit(const int reason) {
   delete expert;
}

このコードを現在のフォルダのSimpleVolumesExpertSingle.mq5ファイルに保存します。


リアル取引と仮想取引の比較

同じ戦略のEAを、同じ戦略パラメータを使用して、短い時間間隔で起動してみましょう。ただし、実際のポジションを建てる方法は、直接建てる方法と仮想ポジション経由の2通りあります。これらの結果をレポートに保存し、両方のEAがおこなった取引のリストを見ます。

図4:2つのEAによる取引(仮想ポジションあり、仮想ポジションなし)

幅を小さくするため、銘柄(常にEURGBP)、出来高(常に0.01)など、すべての行で同じ値を持つ列を表から削除しました。見てわかるように、最初のポジションはどちらのケースでも同じ価格、同じ時点で建てられています。売りポジション(2018.03.02 15:46:47売りイン)を建て、買いポジション(2018.03.06 13:56:04買いイン)を建てた場合、仮想ポジションを介して動作するEAは、単に前の売りポジション(2018.03.06 13:56:04買いアウト)をクローズします。最初のEAでは、異なる方向のポジションに対してスワップを支払い続けましたが、2番目のEAではそうではなかったため、全体的な結果はこれより改善しました。

複数の戦略と仮想ポジションを持つEA

SimpleVolumesMarketExpert.mq5ファイルのEAで同様の操作をおこなってみましょう。必要なファイルをインクルードします。EAのコンストラクタを呼び出す際に、新しいCVolumeReceiverクラスのオブジェクトを与え、作成された戦略のクラスを置き換えます。結果をSimpleVolumesExpert.mq5ファイルに保存し、結果を見ます。

図5:9つの戦略インスタンスと仮想ポジションを使用したEAの結果

これらの結果を、仮想ポジションを使用しない類似のEAの結果と比較すると、いくつかの指標に改善が見られます。利益はわずかに増加し、ドローダウンは減少し、シャープレシオとプロフィットファクターも増加しています。


結論

目標達成に向けてまた一歩前進しました。仮想ポジション戦略の使用に移行することで、多数の取引戦略が互いに干渉することなく連携する能力が高まりました。これによって、戦略の各インスタンスを使用して個別に取引するのに比べ、取引に必要な最低保証金を低くすることも可能になります。もう1つのうれしいボーナスは、ネッティング勘定に携わる機会があることです。

しかし、まだやるべきことはたくさんあります。例えば、これまでのところ、マーケットポジションを建てる戦略のみが実装されているが、予約注文は実装されていません。金銭管理に関する問題も将来に残されます。現時点では、ボリュームを固定し、最適なポジションサイズを手動で選択しています。一度に複数の銘柄を扱う戦略(より単純な単一銘柄戦略に分割できない戦略)は、この操作構造を同様に使用することはできません。

アップデートをお楽しみに。


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14107

MQL5入門(第7回):MQL5でEAを構築し、AI生成コードを活用するための初心者ガイド MQL5入門(第7回):MQL5でEAを構築し、AI生成コードを活用するための初心者ガイド
この記事は、MQL5でエキスパートアドバイザー(EA)を構築するための包括的な、究極の初心者ガイドです。擬似コードを使用してEAを構築し、AIが生成したコードのパワーを活用する方法をステップごとに学びましょう。アルゴリズム取引が初めての方にも、スキルアップを目指す方にも、このガイドは効果的なEAを作成するための明確な道筋を提供します。
ニューラルネットワークが簡単に(第75回):軌道予測モデルのパフォーマンス向上 ニューラルネットワークが簡単に(第75回):軌道予測モデルのパフォーマンス向上
私たちが作成するモデルはより大きく、より複雑になっています。そのため、訓練だけでなく、運用にもコストがかかります。しかし、決断に要する時間はしばしば重要です。この観点から、品質を損なうことなくモデルのパフォーマンスを最適化する手法を考えてみましょう。
Pythonでの見せかけの回帰 Pythonでの見せかけの回帰
見せかけの回帰は、2つの時系列がまったくの偶然で高い相関を示し、回帰分析で誤解を招く結果をもたらす場合に発生します。このような場合、変数が関連しているように見えても、その相関関係は偶然であり、モデルの信頼性は低くなります。
母集団最適化アルゴリズム:人工多社会的検索オブジェクト(MSO) 母集団最適化アルゴリズム:人工多社会的検索オブジェクト(MSO)
前回に引き続き、社会的集団について考えてみたいと思います。この記事では、移動と記憶のアルゴリズムを用いて社会集団の進化を探求しています。その結果は、社会システムの進化を理解し、最適化や解の探索に応用するのに役立つでしょう。