知っておくべきMQL5ウィザードのテクニック(第17回):多通貨取引
序文
この記事では、MQL5ウィザードがトレーダーのアイデアを素早くテストし、プロトタイプを作成するのに理想的であることを紹介します。EAや取引システムを開発する多くの人々にとって、機械学習だけでなく、取引やリスク管理全般のトレンドを常に学び、把握しておくことは重要で、必要です。そこでこの連載では、MQL5 IDEが時間を節約するだけでなくコーディングミスを最小限に抑えることで、この点でどのように役立つかを考察します。
この記事では、ニューラルアーキテクチャサーチ(NAS)(英語)を取り上げるつもりでしたが、まだ取り上げていない、考慮する価値のある基本的な原則がいくつかあることに気づきました。その中でも最も重要なのは、ウィザードが組み立てたEAで複数の証券を取引することです。そこで今回は、新しい取引設定を見ることから少し離れて、基本的なことをいくつか考えてみましょう。NASについては次回以降に検討します。
はじめに
単一通貨取引と異なり、多通貨取引ではリスクの集中を抑えることができます。各口座にはレバレッジレベルが設定されており、したがって自由証拠金の額も決まっているため、複数の銘柄を取引しなければならない状況に直面した場合、割り当てられる自由証拠金の額を利用可能なすべての通貨で分けなければなりません。これらの通貨に相関関係がないか、あるいは逆相関関係があれば、一通貨だけに過度に依存するリスクを大幅に軽減できます。
また、リスクを最小化するために複数通貨のポジションを並行して建てるのではなく、各通貨ペアを個別のタイミングで検討する逐次的な取引システムであれば、クロスマーケットの機会を活用することができます。建てたポジションがドローダウンしている場合は、すでに建てたポジションと逆相関する通貨ペアの連続またはフォローオンポジションを建て、すでに建てたポジションに浮動利益がある場合にはその逆をおこなうのです。どの通貨ペアでも、証拠金または利益となる通貨がペアになっているため、このプロセスは、相関関係だけを考慮する以外に、クロスマーケットの機会を選択する場合に特に興味深いものとなります。
各通貨ペアの差益ペアは、ヘッジという多通貨取引の利点も示しています。裁定取引に近い行為は、ほとんどのブローカーに嫌われるだけでなく、実際に実行するのも非常に難しいものですが、証拠金や利益通貨に基づいて複数の通貨ペアでヘッジすることは、トレーダーが影響力の大きいニュースイベントや週末にポジションを保持しようとしている状況では特に、未決注文でおこなうことができます。
ただし、これらの戦略以上に、多通貨取引によって、トレーダーやEAは、気配値表示で取引可能なペアの多くにまたがる通貨固有のトレンドを見て、それを利用することができます。例えば、特定の通貨が有利な通貨金利を獲得している場合は、カバーされていない金利裁定取引(英語) の下で、その通貨のレートが優れている通貨ペアをいくつか購入して保有し、価格変動だけでなく金利からも利益を得ることができます。複数通貨ペアの分析をおこない、これらの複数通貨間の設定を利用した取引をおこなう能力は、EAが多通貨取引のために構築されている場合にのみ実現できるため、私たちが見てきたウィザードで組み立てられたEAでは実現できません。
そこで今回は、MQL5ウィザードで使用されるクラスを修正するテンプレートを作成し、2つのシナリオでEAを組み立ててみることにします。まず、リスクエクスポージャーを軽減する目的で、複数通貨ペアを単純に分析し、並行してポジションを建てるEAをウィザードで組み立ててみます。ウィザードによる組み立てで使用される主要なファイルを変更するため、このアプローチでは最もカスタマイズが必要になるでしょう。最後に、2つ目のアプローチでは、相対的な通貨ペアの相関関係を考慮し、独立した分析に基づいて順番にポジションを建てることができるEAをウィザードで組み立ててみます。
MQL5ウィザードのテンプレート(オプション1):
ウィザードで組み立てられたEAには3つの主要なアンカークラスがあり、それぞれ別のファイルに収められています。これらはExpertBase、ExpertTrade、Expertです。これら3つのアンカークラスの他に、ExpertSignal、ExpertMoney、ExpertTraillingという3つの補助クラスがあり、それぞれEAのシグナルクラス、マネーマネジメントクラス、トレーリングクラスを構築する際に継承されます。これらすべてのクラスにおいて、デフォルトで取引されている銘柄の市場情報にアクセスする際に使用される銘柄クラスのオブジェクトm_symbolを定義しているのはExpertBaseクラスです。上述したように、すべてのウィザードで組み立てられたEAは、デフォルトでは1つの銘柄しか取引しないため、ExpertBase内で初期化された銘柄クラスは、1つの銘柄だけを扱うためのものです。
複数銘柄にするには、このクラスのインスタンスを配列に変換することが考えられます。これは可能ですが、この銘柄クラスのインスタンスにアクセスするクラスは、そのために多くの関数を使用し、この接続は常に非配列を期待しているため、多くの実現不可能な変更をコードに加えなければなりません。つまり、これはウィザードで組み立てるEAを多通貨対応にするための良い解決策ではありません。
より現実的で似たようなアプローチとして、Expertクラスのインスタンスを、テストする取引銘柄の数と同じサイズの配列にすることを検討します。このクラスのインスタンスは、ウィザードで組み立てられたEAの*.mq5ファイルで常に宣言されており、これを配列に変換するだけで、要するに下流のカスタマイズをすべておこなうことになります。
//+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert[__FOLIO]; CExpertSignal *signals[__FOLIO];
EAクラスのインスタンスを配列に変換するには、取引する通貨ペアの配列をあらかじめ定義しておく必要があります。これは以下のコードで実行します。
//+------------------------------------------------------------------+ //| 'Commodity' Currency folio | //+------------------------------------------------------------------+ #define __FOLIO 9 string __F[__FOLIO] = { "AUDNZD","AUDUSD","AUDCAD","AUDJPY", "NZDUSD","NZDCAD","NZDJPY", "USDCAD", "CADJPY" }; input string __prefix=""; input string __suffix="";
上記のコードは、Expertクラスのカスタマイズバージョンに修正されます。このクラスを修正するので、典型的な組み立てのデフォルト設定を維持するために、そのインスタンスを新しい名前で保存しなければなりません。私たちが使用する名前はExpertFolio.mqhです(元の名前Expert.mqhから)。また、名前の変更とヘッダー内のコードの修正に加えて、EAがアタッチされているチャートの銘柄と一致しない銘柄にも対応できるように、Init関数のリストも変更する必要があります。これは以下のコードで実行します。
//+------------------------------------------------------------------+ //| Initialization and checking for input parameters | //+------------------------------------------------------------------+ bool CExpert::Init(string symbol,ENUM_TIMEFRAMES period,bool every_tick,ulong magic) { //--- returns false if the EA is initialized on a timeframe different from the current one if(period!=::Period()) { PrintFormat(__FUNCTION__+": wrong timeframe (must be: %s)",EnumToString(period)); return(false); } if(m_on_timer_process && !EventSetTimer(PeriodSeconds(period))) { PrintFormat(__FUNCTION__+": cannot set timer at: ",EnumToString(period)); return(false); } //--- initialize common information if(m_symbol==NULL) { if((m_symbol=new CSymbolInfo)==NULL) return(false); } if(!m_symbol.Name(symbol)) return(false); .... .... //--- ok return(true); }
上記の変更は、EAクラスファイルの複製を作成し、そのファイル名を上記のように変更することもできますし、Expert.mqhのEAクラスを継承した新しいクラスを作成し、この新しいクラスに「オーバーライド」Init関数を追加することもできます。この新しいクラスとファイルは、メインのEAファイルで参照されます。以下はそのコードです。
#include "Expert.mqh" //+------------------------------------------------------------------+ //| Class CExfolio. | //| Purpose: Base class expert advisor. | //| Derives from class CExpertBase. | //+------------------------------------------------------------------+ class CExfolio : public CExpert { protected: public: CExfolio(void); ~CExfolio(void); //--- initialization virtual bool Init(string symbol,ENUM_TIMEFRAMES period,bool every_tick,ulong magic=0) override; //... };
上のコードからわかるように、overrideはインターフェイス宣言の最後に追加されていますが、Init関数はデフォルトではvirtualではないため、これを追加することで元のEAクラスのInit関数を修正する必要があります。その行は次のようになります。
<code: EAクラスのInit関数に仮想指定子を追加/>。
作成されるクラスファイルがかなり小さくなるため、最初のケースよりも重複が少なくなり、確かに「よりすっきりした」オプションになるでしょうが、内蔵のEAファイルを修正する必要があります。つまり、端末を更新するたびにinit関数に仮想関数指定子を追加しなければなりません。
これらのEAクラスの変更に加えて、ウィザードによって生成されるEAファイルは、OnInit、OnTick、OnTimer、OnDeInit、Trade関数のそれぞれを変更する必要があります。追加されるのは、カスタマイズされたEAクラスで事前に宣言されたすべての通貨を繰り返し処理するforループです。これらの宣言には、接頭辞と接尾辞の文字列入力パラメータが付属しており、これらの名前が適切にフォーマットされ、気配値表示に適合していることを保証します。銘柄に明示的に名前をつけたり、接頭辞付き/接尾辞付きの名前を管理したりするのではない、利用可能な銘柄にアクセスするための適切なアプローチはこちらで強調表示されていますが、その場合でも、気配値表示で利用可能な銘柄の非常に長いリストを適切にフィルタリングするためには、興味のある銘柄の事前定義リストが必要です。
カスタムEAクラスで選択される銘柄は、特にかつての冷戦時代には「コモディティ(商品)」と呼ばれていたものです。それらの勢いは商品価格に過度に影響されていたからです。そこで、このリストではAUD、NZD、CADを主な商品としています。米ドルと日本円の追加はボラティリティエクスポージャーのためですが、これらは「コモディティ」グループのメンバーではありませんでした。
ウィザードのEAに使用するシグナルクラスは、内蔵クラスのいずれでもかまいません。最初の実装では、リスクエクスポージャーを最小化するために銘柄が並列に実行されるからです。取引判断は他の銘柄の動向に影響されないため、選択したシグナルの設定はすべての銘柄に適用されます。テストから、これは、異なる銘柄が同じ設定を共有し、したがって曲線あてはめが少ないので、テスト実行がフォワードウォークや交差検証でうまくいく可能性が高いことを意味します。RSIシグナルクラスは、指標期間と適用価格という比較的少ない入力パラメー タしか必要としません。
MQL5ウィザードのテンプレート(オプション2)
多通貨取引の2つ目の実装では、ウィザードで組み立てたEAファイルにSelect関数を追加して修正します。この関数は、取引ポートフォリオ内の銘柄の相関関係に依存することで、このような機会が存在する場合にのみ複数の銘柄を取引することで、クロスマーケットの機会を活用しようとするものです。これは基本的に、上記の最初の選択肢で考えたアプローチのフィルタです。
そのためには、まずオンタイマー取引を有効にしなければなりません。これをOnInit関数の中の1行でおこないます。
// ExtExpert[f].OnTimerProcess(true);
これで、2 つの主要なセクションのみを備えたSelect関数に集中することができます。1つ目は、ポジションが建てられておらず、ポートフォリオ内のすべての取引銘柄から相関を確認するシグナルを選択する必要がある状況に対応します。このシグナルが確認できるのは、すべての銘柄の主要なシグナルとして、シグナルクラスのRSIシグナルに依然として依存しているからです。つまり、この場合の確認は、各銘柄の終値バッファの自動相関を介してなされます。ポートフォリオから最大のプラス値を持つ銘柄が選択されます。
トレンドのある銘柄や、ポートフォリオから最も強いトレンドを持つ銘柄をターゲットにしているため、単純な大きさではなく、最大の正値を使用しています。この値が決まったら、直近の値と直近の値の間の終値バッファの価格変化を見て、トレンドの方向を決定します。プラスの変化は強気、マイナスの変化は弱気となります。これは以下のコードで処理されます。
//+------------------------------------------------------------------+ //| Symbol Selector via Correlation | //+------------------------------------------------------------------+ bool Select(double Direction, int &Index, ENUM_POSITION_TYPE &Type) { if(PositionsTotal() == 0) { double _max = 0.0; int _index = -1; Type = INVALID_HANDLE; for(int f = 0; f < __FOLIO; f++) { vector _v_0, _v_1; _v_0.CopyRates(__F[f], Period(), 8, 0, 30); _v_1.CopyRates(__F[f], Period(), 8, 30, 30); double _corr = _v_0.CorrCoef(_v_1); if(_max < _corr && ((Direction > 0.0 && _v_0[0] > _v_0[29]) || (Direction < 0.0 && _v_0[0] < _v_0[29]))) { _max = _corr; _index = f; if(_v_0[0] > _v_0[29]) { Type = POSITION_TYPE_BUY; } else if(_v_0[0] < _v_0[29]) { Type = POSITION_TYPE_SELL; } } } Index = _index; return(true); } else if(PositionsTotal() == 1) { //... //... } return(false); }
つまり、上記のコードはSelect関数のデフォルトの部分であり、まだポジションが建てられていない状況を処理します。ポジションが建てられたら、建てられたポジションのパフォーマンスに応じてクロスマーケットの機会を探します。最初に建てられたポジションがドローダウンしている場合、残りのポートフォリオ銘柄をふるいにかけ、効果的な「逆」相関を持つものを見つけようとします。「逆」というのは、大きさだけに関心があるという意味で、すでに建てられているポジションと比較して、現在建てている銘柄との相関の大きさが最も高い限り、この銘柄のトレンドに従います。これを処理するコードは以下の通りです。
//+------------------------------------------------------------------+ //| Symbol Selector via Correlation | //+------------------------------------------------------------------+ bool Select(double Direction, int &Index, ENUM_POSITION_TYPE &Type) { if(PositionsTotal() == 0) { //... //... } else if(PositionsTotal() == 1) { ulong _ticket = PositionGetTicket(0); if(PositionSelectByTicket(_ticket)) { double _float = PositionGetDouble(POSITION_PROFIT); ENUM_POSITION_TYPE _type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); int _index = ArrayBsearch(__F, PositionGetString(POSITION_SYMBOL)); double _max = 0.0; Type = INVALID_HANDLE; for(int f = 0; f < __FOLIO; f++) { if(f == _index) { continue; } else { vector _v_0, _v_1; _v_0.CopyRates(__F[_index], Period(), 8, 0, 30); _v_1.CopyRates(__F[f], Period(), 8, 0, 30); double _corr = fabs(_v_0.CorrCoef(_v_1)); if(_float < 0.0 && _max < _corr) { _max = _corr; Index = f; if(_v_1[0] > _v_1[29]) { Type = POSITION_TYPE_BUY; } else if(_v_1[0] < _v_1[29]) { Type = POSITION_TYPE_SELL; } } } } } return(true); } return(false); }
ウィザードで組み立てたEAの環境内でこのSelect関数を使用するには、EAクラスにもう少し変更を加えなければなりません。そのため、EAクラスのインスタンスを複製し、名前を変更する方が、かさばるようだが現実的です。最初におこなうべき変更は、Refresh関数に一般からアクセスできるようにすることです。クラスインターフェイスのProtectedセクションからPublicセクションに移します。同様に、Process関数も公開されるべきです。この変更は以下のようになります。
//+------------------------------------------------------------------+ //| Class CExpert. | //| Purpose: Base class expert advisor. | //| Derives from class CExpertBase. | //+------------------------------------------------------------------+ class CExpert : public CExpertBase { protected: //... //... public: CExpert(void); ~CExpert(void); //... //--- refreshing virtual bool Refresh(void); //--- processing (main method) virtual bool Processing(void); protected: //... //... };
これら2つの重要な関数は、変更したEA内で手動で呼び出す必要があるため、publicに移す必要があります。通常、これらの関数は内部的にOnTickまたはOnTimer関数によって呼び出されます。そして、呼び出されると、呼び出された銘柄のシグナル、トレール、マネーマネジメントを完全に処理します。私たちの場合、a) 相対的な相関関係、b) すでにポジションを建てているかどうかに応じて、特定の銘柄だけを処理します。そのためには、それらがいつ、どのように招集されるのか、私たちが「指揮を執る」必要があるのは明らかです。これは、以下のようにOnTimer関数でおこないます。
//+------------------------------------------------------------------+ //| "Timer" event handler function | //+------------------------------------------------------------------+ void OnTimer() { for(int f = 0; f < __FOLIO; f++) { ExtExpert[f].Refresh(); } for(int f = 0; f < __FOLIO; f++) { int _index = -1; ENUM_POSITION_TYPE _type = INVALID_HANDLE; ExtExpert[f].Magic(f); if(Select(signals[f].Direction(), _index, _type)) { ExtExpert[f].OnTimer(); ExtExpert[f].Processing(); } } }
取引する銘柄を選択する際、ソースポートフォリオ配列内のインデックスをマジックナンバーとして割り当てることで、その銘柄を追跡します。つまり、OnTick、OnTrade、OnTimerのすべてのデフォルト処理関数内の各forループで、処理前に取引クラスが使用するマジックナンバーを更新する必要があるということです。加えて、今回のテストではカスタム銘柄を使用したため、データ品質上の理由から、ポートフォリオ配列の銘柄名とその接尾辞(該当する場合は接頭辞)を含める必要がありました。初期化時に接頭辞と接尾辞を追加している場合、すべてのデータがテスト用コンピューターに存在しているにもかかわらず、なぜかストラテジーテスターはカスタム銘柄を同期できません。
また、デフォルトのOnTickではなく、OnTimerで取引します。理論的には、OnProcessTimerとOnProcessTickパラメータを簡単に調整することで設定できるはずですが、これらを変更すると、取引がおこなわれないか、ティックで取引されることになります。このため、EAクラスのOnTick関数とOnTimer関数内で、OnProcess関数がOnTick内で無効にされ、上記で公開されたOnProcess関数が、上記のOnTimerコードに示されているように、EAのOnTimer関数内で独立して呼び出されるように、より侵襲的な変更が必要になりました。これがまた必要なのは、この原稿を書いている時点では原因不明ですが、ExpertFolioクラスのOnTimer内のProcess関数が実行されないからです。すべての銘柄で価格と指標値を更新することはできますが、Process関数を個別に呼び出す必要があります。また、タイマーは通常のEAのように手動で宣言し、終了させる必要があります。
テストと最適化
2023年から2024年まで、「コモディティ」クロスAUDJPYを4時間足でテストします。私たちのテストEAはストップロスを使用しません。なぜなら、上記の2番目のオプションでは、負けポジションはクロスマーケットの機会によって管理されるからです。RSIはシグナルクラスの指標であるため、RSIの期間と、オープン&クローズのしきい値、指値注文の有効期限、指値注文のエントリギャップのみを最適化します。資金管理は、最小ロットに固定しました。両オプションのレポートとエクイティ曲線を以下に示します。
上記のレポートからわかるように、予想通り、単一通貨の設定よりも多くの取引がおこなわれています。興味深いことに、ドローダウンを合理的な範囲に管理しながら、これらの複数の取引を実行するために使用される設定は、拡張アプリケーションと可能性を指摘しています。また、a) エントリシグナルのセカンダリーフィルタがないこと、b) ドローダウンを最小化し、リスクを管理するために、クロスマーケット(ヘッジ)ルールが使用されていることによって、予想通り、2番目のオプションでは最初のオプションよりも取引が少なくなりました。
クロスマーケットの機会に加えて、冒頭で述べたように、カバーされていない金利裁定取引の機会も探ることができます。この場合、経済ニュースデータは MQL5経済指標カレンダーから抽出する必要があります。これらの値を使用した戦略のテストはまだ限られていますが、最近、MQL5 IDEを使用しながら経済ニュースのデータをデータベースに保存するという興味深い記事がこちらに投稿されました。これがご自分の探求したい道であるならば、読む価値があります。
結論
MQL5ウィザードを介して組み立てられたEAに多通貨取引を導入する方法を探りました。これらはウィザードなしでも簡単にコーディングできます。しかし、MQL5ウィザードを使えば、繰り返しの少ないEAの迅速な開発が可能になるだけでなく、重み付けシステムによって1つ以上のアイデアを同時にテストすることもできます。いつものように、新しい読者はウィザードの使い方について、こちらと こちらのガイドを参照することができます。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14806
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索