![かずかずのインスツルメントで取引を行うExpert Advisorの作成](https://c.mql5.com/i/articles/overlay.png)
かずかずのインスツルメントで取引を行うExpert Advisorの作成
はじめに
同時に異なるファイナンシャルアセットの取引を行うことを可能にする、一つのチャートに実装された単独のExpert Advisorに整然とプログラムコードを実装する技術的側面、これはMQL4でさえ問題ではありませんでした。しかし、ただMetaTrader 5クライアント端末が出現したことで、トレーダーはついにストラテジーテスタを用いそういった自動化作業のフル分析を行う機会を得ました。
複数通貨自動化はかつてない人気を得るでしょう。そのようなトレーディングシステムの構築にわきあがる興味を抱くことになるのです。しかし、そのようなロボットの導入にあたり主な問題は、プログラムコードにおけるディメンションの広がりであり、せいぜい算術的進歩では、一般的なプログラマにとって受け入れやすいものではありません。
本稿では、シンプルな複数通貨対Expert Advisorを書いていきます。そこでは構造体フローは、存在しないものとして、あっても最小化されています。
1. シンプルなトレンド追跡システム
内蔵テクニカルインディケータの「トリプル指数移動平均」を基本としてトレンドを追うことで、実際最大限シンプル化されたトレーディングシステムを用いて始めることはできました。これはたいへん簡単なアルゴリズムです。特別なコメント記述を必要としませんし、プそれはログラムコードに組み込まれます。
しかしまず最初にそして最大限に、Expert Advisorについてごく一般的な結論に至りたいと思います。グローバルレベルで宣言されている、入ってくるExpert Advisorパラメータのブロックから始めるのは道理的であると思います。
そこでまず、作業を行うファイナンシャルアセットの選択を行う必要があります。これはアセットシンボルが保存されている入力変数を用いて行うことができます。各ファイナンシャルアセットに、アセットによってトレード処理を無効にできるトレードバンを設けることができればなお良いでしょう。
当然、各アセットは個別のストップロス、テイクプロフィット、オープンポジションのボリューム、スリッページというトレーディングパラメータと連携している必要があります。そして、わかりやすい理由として、各トレーディングチップに対する「トリプル指数移動平均」は独立している必要があります。
ここにはただ一つのチップに対する一つの入力変数があり、これら引数と連携して実行されます。その他のブロックはExpert Advisorの入力パラメータ名にある番号によってのみ区別されます。今回の例では、12個のファイナンシャルアセットに限定しました。理想的にはそのようなブロック数にソフトウェアとして制限を設けるべきでないのは承知の上です。
ここではただ何か取引をする対象が必要なだけなので!そしてもっとも重要なのが、われわれのPCにはこの問題を解決するための十分なリソースが必要だということです。
input string Symb0 = "EURUSD"; input bool Trade0 = true; input int Per0 = 15; input ENUM_APPLIED_PRICE ApPrice0 = PRICE_CLOSE; input int StLoss0 = 1000; input int TkProfit0 = 2000; input double Lots0 = 0.1; input int Slippage0 = 30;
グローバルレベル変数が明らかになったところで、OnTick()関数内コードの構築に進みます。ここでの最も基本的選択肢はトレーディングシグナルを受け取るアルゴリズムとExpert Advisorのトレーディングパートを2つのカスタム関数へ分割することです。
Expert Advisorは同時に12のファイナンシャルアセットで作業するので、 OnTick()ブロック内でこれら関数を12回呼ぶ必要があります。
当然、これら関数の最初の入力パラメータはユニークナンバーでなければならず、その下にはこれらトレーディングアセットがリスト化されます。二番目の入力パラメータは明らかな理由により、トレーディングのファイナンシャルアセット名です。
三番目のパラメータの役割としてトレードを解決するロジカル変数を設定します。次にトレーディングシグナルを判断するアルゴリズムについて入力インディケータ シグナルを追います。トレーディング関数については、指値注文への距離、ポジションボリューム、スリッページ(オープンポジション価格のスリッページを許可します)を追います。
ある関数から別の関数にトレーディング シグナルを変換するには、関数のパラメータとして静的配列を設定する必要があります。それはそれらの値を参照によって派生させるものです。これはOnTick()関数に提案されたコードの最終版です。
void OnTick() { //--- declare variables arrays for trade signals static bool UpSignal[12], DnSignal[12], UpStop[12], DnStop[12]; //--- get trade signals TradeSignalCounter( 0, Symb0, Trade0, Per0, ApPrice0, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 1, Symb1, Trade1, Per1, ApPrice1, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 2, Symb2, Trade2, Per2, ApPrice2, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 3, Symb3, Trade3, Per3, ApPrice3, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 4, Symb4, Trade4, Per4, ApPrice4, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 5, Symb5, Trade5, Per5, ApPrice5, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 6, Symb6, Trade6, Per6, ApPrice6, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 7, Symb7, Trade7, Per7, ApPrice7, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 8, Symb8, Trade8, Per8, ApPrice8, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 9, Symb9, Trade9, Per9, ApPrice9, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(10, Symb10, Trade10, Per10, ApPrice10, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(11, Symb11, Trade11, Per11, ApPrice11, UpSignal, DnSignal, UpStop, DnStop); //--- perform trade operations TradePerformer( 0, Symb0, Trade0, StLoss0, TkProfit0, Lots0, Slippage0, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 1, Symb1, Trade1, StLoss1, TkProfit1, Lots1, Slippage1, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 2, Symb2, Trade2, StLoss2, TkProfit2, Lots2, Slippage2, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 3, Symb3, Trade3, StLoss3, TkProfit3, Lots3, Slippage3, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 4, Symb4, Trade4, StLoss4, TkProfit4, Lots4, Slippage4, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 5, Symb5, Trade5, StLoss5, TkProfit5, Lots5, Slippage5, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 6, Symb6, Trade6, StLoss6, TkProfit6, Lots6, Slippage6, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 7, Symb7, Trade7, StLoss7, TkProfit7, Lots7, Slippage7, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 8, Symb8, Trade8, StLoss8, TkProfit8, Lots8, Slippage8, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 9, Symb9, Trade9, StLoss9, TkProfit9, Lots9, Slippage9, UpSignal, DnSignal, UpStop, DnStop); TradePerformer(10, Symb10, Trade10, StLoss10, TkProfit10, Lots10, Slippage10, UpSignal, DnSignal, UpStop, DnStop); TradePerformer(11, Symb11, Trade11, StLoss11, TkProfit11, Lots11, Slippage11, UpSignal, DnSignal, UpStop, DnStop); //--- }
TradeSignalCounter()関数内部では、各チップ開始時にテクニカルインディケータである「トリプル指数移動平均」のハンドル取得 が必要なだけで、その後トレーディングシグナルを計算するためのバーが変更するたびに取得が必要です。
このコード実装をする比較的シンプルなスキームは、重要でない詳細であふれ始めています。
bool TradeSignalCounter(int Number, string Symbol_, bool Trade, int period, ENUM_APPLIED_PRICE ApPrice, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- declare variable to store final size of variables arrays static int Size_=0; //--- declare array to store handles of indicators as static variable static int Handle[]; static int Recount[],MinBars[]; double TEMA[4],dtema1,dtema2; //--- initialization if(Number+1>Size_) // Entering the initialization block only on first start { Size_=Number+1; // For this number entering the block is prohibited //--- change size of variables arrays ArrayResize(Handle,Size_); ArrayResize(Recount,Size_); ArrayResize(MinBars,Size_); //--- determine minimum number of bars, sufficient for calculation MinBars[Number]=3*period; //--- setting array elements to 0 DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- use array as timeseries ArraySetAsSeries(TEMA,true); //--- get indicator's handle Handle[Number]=iTEMA(Symbol_,0,period,0,ApPrice); } //--- check if number of bars is sufficient for calculation if(Bars(Symbol_,0)<MinBars[Number])return(true); //--- get trade signals if(IsNewBar(Number,Symbol_,0) || Recount[Number]) // Entering the block on bar change or on failed copying of data { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- using indicator's handles, copy values of indicator's //--- buffers into static array, specially prepared for this purpose if(CopyBuffer(Handle[Number],0,0,4,TEMA)<0) { Recount[Number]=true; // As data were not received, we should return // into this block (where trade signals are received) on next tick! return(false); // Exiting the TradeSignalCounter() function without receiving trade signals } //--- all copy operations from indicator buffer are successfully completed Recount[Number]=false; // We may not return to this block until next change of bar int Digits_ = int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+4); dtema2 = NormalizeDouble(TEMA[2] - TEMA[3], Digits_); dtema1 = NormalizeDouble(TEMA[1] - TEMA[2], Digits_); //---- determining the input signals if(dtema2 > 0 && dtema1 < 0) DnSignal[Number] = true; if(dtema2 < 0 && dtema1 > 0) UpSignal[Number] = true; //---- determining the output signals if(dtema1 > 0) DnStop[Number] = true; if(dtema1 < 0) UpStop[Number] = true; } //----+ return(true); }
ここでは、TradePerformer()関数のコードはかなりシンプルであることが判ります。
bool TradePerformer(int Number, string Symbol_, bool Trade, int StLoss, int TkProfit, double Lots, int Slippage, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- close opened positions if(UpStop[Number])BuyPositionClose(Symbol_,Slippage); if(DnStop[Number])SellPositionClose(Symbol_,Slippage); //--- open new positions if(UpSignal[Number]) if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) UpSignal[Number]=false; //This trade signal will be no more on this bar! //--- if(DnSignal[Number]) if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) DnSignal[Number]=false; //This trade signal will be no more on this bar! //--- return(true); }しかし、これは実際のトレード処理コマンドはさらに4つの関数にパックされているからにすぎません。
BuyPositionClose(); SellPositionClose(); BuyPositionOpen(); SellPositionOpen();
4つの関数すべては完全に類似的に動作し、そのためそのうち1つだけを検証することにします。
bool BuyPositionClose(const string symbol,ulong deviation) { //--- declare structures of trade request and result of trade request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); //--- check if there is BUY position if(PositionSelect(symbol)) { if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) return(false); } else return(false); //--- initializing structure of the MqlTradeRequest to close BUY position request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(symbol, SYMBOL_BID); request.action = TRADE_ACTION_DEAL; request.symbol = symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.sl = 0.0; request.tp = 0.0; request.deviation=(deviation==ULONG_MAX) ? deviation : deviation; request.type_filling=ORDER_FILLING_FOK; //--- string word=""; StringConcatenate(word, "<<< ============ BuyPositionClose(): Close Buy position at ", symbol," ============ >>>"); Print(word); //--- send order to close position to trade server if(!OrderSend(request,result)) { Print(ResultRetcodeDescription(result.retcode)); return(false); } //----+ return(true); }
基本的に、複数通貨対応のExpert Advisor (Exp_TEMA.mq5)は手に余るものです!
考察した関数以外にも2つのユーザー関数があります。
bool IsNewBar(int Number, string symbol, ENUM_TIMEFRAMES timeframe); string ResultRetcodeDescription(int retcode);
そのうち最初の関数は、選択されたシンボル、時間枠に基づきバー変更の際、true値を返します。 二番目の関数はトレード要求ストラクチャ MqlTradeResultのフィールドretcodeから派生したトレード処理の結果コードによって行を返します。
これでExpert Advisorは準備できました。検証を始める時間です。 複数通貨対応Expert Advisorの検証は、単通貨対応Expert Advisorの検証と大きく変わるところはありません。
「ストラテジーテスタ」の「パラメータ」タブ上のコンフィギュレーションを決定します。
図1 「ストラテジーテスタ」の『設定』タブ
必要であれば、「入力パラメータ」タブの入力パラメータ値を調整します。
図2 「ストラテジーテスタ」の『パラメータ』タブ
そして『設定』タブ上「ストラテジーテスタ」の『開始』ボタンをクリックします。
図3 Expert Advisor検証の実行
12個すべてのシンボルについての履歴をロードする関係上、 Expert Advisorの最初の検証で経過する時間はたいへん長くなる可能性があります。ストラテジーテスタでの検証が完了すると『結果』タブを開きます。
図4 検証結果
そして『チャート』タブのコンテンツを利用してデータ分析を行います。
図5 バランスの動きとequityのチャート
そして『ジャーナル』です。
図6 ストラテジーテスタジャーナル
ごく当然のことですが、このExpert Advisorのアルゴリズムのマーケットへの入りと出の基本の基本はシンプルすぎて、最初のランダムなパラメータを使うときは大きな結果は期待できないでしょう。しかしここでの目標はできるだけシンプルな方法で複数通貨対応Expert Advisorを構築する考え方の基礎を示すことです。
このExpert Advisorの最適化によって多くの入力パラメータによる不都合が生じる可能性があります。 最適化の遺伝的アルゴリズムはこれらパラメータがもっと小さいことを要求します。そのためExpert Advisorはチップごとに個別に最適化される必要があり、入力パラメータの TradeNの残りのパラメータチップを無効化します。.
手法の基本がだいたいわかったところで、複数通貨対応ロボットについて判断を行うもっと興味深いアルゴリズムについてみていきましょう。
2. ファイナンシャルマーケットにおける共振とトレーディングシステムでの適用
異なるファイナンシャルアセット間の連携を考慮する考え方は一般的に新しいものではありません。また正確にそういったトレンドの分析に基づくアルゴリズムを実装することは興味を引かれるものかもしれません。本稿では、複数通貨対応自動化を実装します。それは、2001年5月4日発行のジャーナル"Currency Speculator"(ロシア語)掲載、Vasily Yakimkin氏の記事"Resonances - a New Class of Technical Indicators"を基にしたものです。
この手法の基本の基本を端的に言うと以下のようなことです。たとえば、EUR / USDについて調査する場合、ファイナンシャルアセットのインディケータ結果だけを使うのではなく、EUR/USDアセットに関連する同様のインディケータ結果も使います。 EUR/JPYやUSD/JPYです。インディケータ、変更の同じ範囲内でシンプル化のため、また計測と計算を簡単にするため、普通化される値を使うのは最良の方法です。
これら要求を考慮すると、このありがちなものによくあてはまるのは確率論的インディケータです。しかし、実際は他のインディケータを使ってもなんら違いはありません。トレンド方向として、確率値 Stoh とそのシグナル行 Sign の間の異なるサインを考察します。
図7 トレンド方向の判断
変数シンボル dStoh について、可能性ある組合せの表と、その現在トレンド方向への解釈があります。
図8 変数シンボルdStohとトレンド方向の組合せ
アセットEUR / JPYとUSD / JPYの2つのシグナルが逆の値を示す場所で、それらの合計とこの合計がゼロより大きいことを判断する必要があります。どちらのシグナルも正で、それ以外では負と考えます。
オープニングのロングには、トレンドが成長の場合の状況を採用します。出には下降トレンド、または主要アセットのEUR / USDインディケータシグナルが負というトレンドを採用します。また、主要アセットにシグナルがない場合または、残りのアセットについての変数dStohの合計がゼロ以下の時 はロングを出ます。ショートに関しては、すべてが絶対的に類似しています。状況が逆なだけです。
最も根本的な解決法はExpert Advisorの分析的部分の全体を複数通貨インディケータに配置することです。そしてインディケータバッファからのExpert Advisorについてはトレードコントロールの準備シグナルを取得するだけです。このインディケータタイプのバージョンは、マーケット条件の視覚的分析をまじえてMultiStochastic.mq5で提供します。
図9 MultiStochasticインディケータ
グリーンのバーはロングの、赤はショートの開始と保持をそれぞれ示します。チャートの上端にあるピンクと薄いグリーンの点はロングとショートポジションの退出シグナルを表します。
このインディケータは直接Expert Advisorでシグナルを受け取るのに使われますが、その作業を簡単にするのが望ましく、視覚化のバッファやエレメントのうち不要なものはすべて削除し、トレーディングシグナルを提供するのに直接関わりのあるものだけを残します。これはまさにMultiStochastic_Exp.mq5インディケータで行われていることです。
ここでのExpert Advisorでは、3つのチップのみ取引しました。よってOnTick()関数のコードはかなりシンプルなものとなっています。
void OnTick() { //--- declare variables arrays for trade signals static bool UpSignal[], DnSignal[], UpStop[], DnStop[]; //--- get trade signals TradeSignalCounter(0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop); //--- perform trade operations TradePerformer( 0, SymbolA0, Trade0, StopLoss0, 0, Lots0, Slippage0, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 1, SymbolA1, Trade1, StopLoss1, 0, Lots1, Slippage1, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 2, SymbolA2, Trade2, StopLoss2, 0, Lots2, Slippage2, UpSignal, DnSignal, UpStop, DnStop); //--- }
ただし、TradeSignalCounter()のコードはすこし複雑です。というのも複数通貨対応インディケータは異なるファイナンシャルアセットの3つのタイムシリーズに直接動作します。そのため、Rates_Total()関数を使って、3つのタイムシリーズにおけるそれらの最小数値のadequacy のためにバーの検証をより精密に行います。
また、タイムシリーズの同期における追加の検証も行います。SynchroCheck()を用いてすべてのタイムシリーズで同時にバー変更が起こる時間を正確に判断できるようにします。
bool TradeSignalCounter(int Number, bool Trade, int Kperiod, int Dperiod, int slowing, ENUM_MA_METHOD ma_method, ENUM_STO_PRICE price_, string SymbolA, string SymbolB, string SymbolC, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- declare variable to store sizes of variables arrays static int Size_=0; //--- declare arrays to store handles of indicators as static variables static int Handle[]; static int Recount[],MinBars[]; //--- double dUpSignal_[1],dDnSignal_[1],dUpStop_[1],dDnStop_[1]; //--- change size of variables arrays if(Number+1>Size_) { uint size=Number+1; //---- if(ArrayResize(Handle,size)==-1 || ArrayResize(Recount,size)==-1 || ArrayResize(UpSignal, size) == -1 || ArrayResize(DnSignal, size) == -1 || ArrayResize(UpStop, size) == -1 || ArrayResize(DnStop, size) == -1 || ArrayResize(MinBars,size) == -1) { string word=""; StringConcatenate(word,"TradeSignalCounter( ",Number, " ): Error!!! Unable to change sizes of variables arrays!!!"); int error=GetLastError(); ResetLastError(); //--- if(error>4000) { StringConcatenate(word,"TradeSignalCounter( ",Number," ): Error code ",error); Print(word); } Size_=-2; return(false); } Size_=int(size); Recount[Number] = false; MinBars[Number] = Kperiod + Dperiod + slowing; //--- get indicator's handle Handle[Number]=iCustom(SymbolA,0,"MultiStochastic_Exp", Kperiod,Dperiod,slowing,ma_method,price_, SymbolA,SymbolB,SymbolC); } //--- check if number of bars is sufficient for calculation if(Rates_Total(SymbolA,SymbolB,SymbolC)<MinBars[Number])return(true); //--- check timeseries synchronization if(!SynchroCheck(SymbolA,SymbolB,SymbolC))return(true); //--- get trade signals if(IsNewBar(Number,SymbolA,0) || Recount[Number]) { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- using indicators' handles, copy values of indicator's //--- buffers into static arrays, specially prepared for this purpose if(CopyBuffer(Handle[Number], 1, 1, 1, dDnSignal_) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 2, 1, 1, dUpSignal_) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 3, 1, 1, dDnStop_ ) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 4, 1, 1, dUpStop_ ) < 0){Recount[Number] = true; return(false);} //--- convert obtained values into values of logic variables of trade commands if(dDnSignal_[0] == 300)DnSignal[Number] = true; if(dUpSignal_[0] == 300)UpSignal[Number] = true; if(dDnStop_ [0] == 300)DnStop [Number] = true; if(dUpStop_ [0] == 300)UpStop [Number] = true; //--- all copy operations from indicator's buffers completed successfully //--- unnecessary to return into this block until next bar change Recount[Number]=false; } //----+ return(true); }
このExpert Advisor (Exp_ResonanceHunter.mq5)のコードにその他根本的なイデオロギーの相違は ありません。というのも、同じ関数構成部位を基本にコンパイルされているからです。よって内部構造について時間を割く必要はないと考えます。
おわりに
私の意見では、MQL5の複数通貨対応Expert Advisorは絶対的に標準的なExpert Advisorのコードに類似していると考えます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/105
![MQL5における移動平均計算パフォーマンス検証](https://c.mql5.com/2/0/moving_averages_performance_MQL5__1.png)
![ろうそく足パターンの分析](https://c.mql5.com/2/0/candlestick_research_MQL5__1.png)
![インディケータの経済的計算原理](https://c.mql5.com/2/0/optimize_MQL5.png)
![DelphiでDLLをMQL5向けに書くためのガイド](https://c.mql5.com/2/0/delphi_DLL_MQL5__1.png)
![MQL5 - MetaTrader 5クライアントターミナルにビルトインされたトレードストラテジーの言語](https://c.mql5.com/i/registerlandings/logo-2.png)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索