
MetaTraderのMultibot:1つのチャートから複数のロボットを起動させる
内容
- はじめに
- 問題提起と適用可能な限界
- マルチボットの利用におけるMetaTrader 4とMetaTrader 5のターミナルの違いについて
- 汎用テンプレート構築のニュアンス
- 汎用テンプレートの書き方
- 結論
はじめに
金融市場の世界では、自動売買システムが意思決定の重要な要素となっています。これらのシステムは、あらかじめ定義されたルールやアルゴリズムを用いて、市場の分析、エントリやエグジットの判断、取引の実行を行うように構成することができます。ただし、複数のチャートにロボットを設定し、実行するのは時間のかかる作業です。各ロボットはチャートごとに個別に設定する必要があり、手間がかかります。
今回は、MetaTrader 4と5の複数のチャートに対応する汎用ロボットを作成できる簡単なテンプレートの私の実装を紹介します。このテンプレートでは、ロボットを1つのチャートに接続し、残りのチャートはEA内部で処理することが可能です。このように、私たちのテンプレートは、複数のチャート上でロボットを設定・実行するプロセスを大幅に簡素化し、トレーダーの時間と労力を節約することができます。今回は、そんなロボットをMQL5で作成する際のアイデアからテストまでの流れを詳しく考えてみたいと思います。
問題提起と適用可能な限界
このアイデアは、私が少し前に思いついたものですが、私は以前からプロの売り手たちの同様の決断を観察していました。つまり、この分野でアイデアを出すのは、私が最初でも最後でもなく、いつものように、プログラマーがそのような判断に至り始めるには、何らかの条件が必要です。このようなエキスパートアドバイザー(EA)をMQL5ストアで開発する最大の理由は、ユーザーの快適さを追求するためです。ただし、私の場合は少し違う動機がありました。まず、複数の商品について複数のストラテジーを同時にテストするか、同じストラテジーでも多通貨の特性を見るためにテストする必要があったということです。
さらに、テスターでストラテジーをテストする際、特にマルチカレンシーモードでは、収益性の一般的な曲線が非常に重要な要素となります。 これは履歴データでバックテストする際に自動売買システムのあらゆる評価の基礎となるものです。取引システムを1つの商品で別々にテストする場合、後からそのレポートを組み合わせるのはなかなか難しくなります。少なくともMetaTrader 5では、そのようなツールは知りません。MetaTrader 4のターミナルに関しては、そのような操作のための非公式なツールが1つあります。少なくとも1つの記事で使いましたが、もちろんそのようなやり方は好ましいものではありません。
テストプロセスだけでなく、自動売買そのものや、それぞれのチャートで独立して動作する類似のEAを同期させるプロセスも同様に重要です。このようなチャートの数が多すぎると、コンピュータのリソースが増え、取引パフォーマンスが低下や悪化したり、予期せぬエラーやその他の不快な事件が発生し、最終的な取引結果に悪影響を及ぼす可能性があります。そのようなEAごとに、ユニークな注文IDを考えたり、高頻度のサーバーリクエストに対応したり、一見しただけではわからない様々な工夫をする必要があります。
EAのグラフィック部分の処理は、別途、非常にデリケートな問題です。現在、多かれ少なかれ、すべてのEAの作者は、EAが接続されているチャート上に、少なくとも何らかの表示のミニマムバージョンを作っています。そして最後に、ほとんどの場合、チャート上の情報をユーザーに表示することで、EAの取引プロセスをより効果的にコントロールできるようになることがあります。また、必要に応じて手動制御のための要素を追加することが可能です。これらすべてをユーザーインターフェイスと呼びます。このようなEAをチャートで配信する場合、インターフェイスのグラフィック、テキスト、数値のいずれの情報も更新する負荷が飛躍的に増加します。もちろん、マルチテンプレートを使用する場合は、ターミナルをフロに最小限のリソースを必要とする1つのインターフェイスを用意しています。
もちろん、このようなテンプレートがすべての問題を解決するわけではありませんが、それでも、私のプロジェクトでは大いに役立っています。私はさまざまなロボットを使っています。一般的にはどのようなアプローチも存在する権利がありますが、多くの初心者プログラマーはこのパターンを参考にすることができるのではないでしょうか。完全に複製する必要はありません。ご希望であれば、簡単に調整することができます。私の目標は、何か特別なものを提供することではなく、このような問題を解決するための選択肢の1つを示し、説明することです。
マルチボットの利用におけるMetaTrader 4とMetaTrader 5のターミナルの違いについて
最新のMetaTrader 5で気に入っているのは、テスターの機能で、上記のEA開発のアプローチを使えば、複数の商品で同時にテストができることです。テスターは、時間ごとにクウォートを自動同期し、時間スケールで明確に同期した収益曲線を提供します。MetaTrader 4にはそのような機能はありません。これが最大の欠点だと思います。とはいえ、MetaQuotesはMetaQuotes 4ターミナルを全力でサポートしており、その人気は依然として高いことは特筆すべきことです。MetaTrader 4のアクティブユーザーとして、これらの欠点は見た目ほど大きなものではないと言えるでしょう。
MQL4言語は最近MQL5に更新されました。つまり、私たちのような似たようなテンプレートを書く場合、コードに最小限の差異が生じることになります。私の伝統として、両ターミナルに対応したものを実装するようにしているので、両ターミナル用のテンプレートを提供しています。こうした旧ターミナルが改良されたのは、とりわけ、私たちが本当に必要としている次のような機能を使えるようにするためです。
- CopyClose:バーの終値のリクエスト
- CopyOpen:バーの始値のリクエスト
- CopyHigh:バーの高値のリクエスト
- CopyLow:バーの安値のリクエスト
- CopyTime:バーが開いた時間のリクエスト
- SymbolInfoTick:リクエストされた銘柄の最後の受信ティックのリクエスト
- SymbolInfoInteger: 整数や番号リストで記述可能な銘柄データのリクエスト
- SymbolInfo*******:その他必要な機能
これらの機能は、MQL4とMQL 5の両方に搭載されています。これらの機能により、任意の銘柄と期間のバーデータを取得することができます。したがって、Metatrader 4とMetatrader 5のテスターの間の唯一の不快な違いは、Metatrader 4ターミナルのこれらの機能は、テストが実施されている現在のチャートに対してのみ機能し、残りのリクエストは、MetaTrader 4テスターの特殊性のためにデータがないことを単に通知するという事実です。したがって、私たちのテンプレートをテストする場合、選択した銘柄での取引と、1台のロボットの利益曲線のうち1つだけを取得することになります。
MetaTrader 5のターミナルでは、すでにリクエストされたすべての銘柄の取引と、共通の採算ラインを受け取ることができます。取引への応用としては、両ターミナルでこのようなロボットと直接取引する場合このようなテンプレートの性能をフルに発揮することができます。つまり、その差はテスターにしかないのです。しかし、そのような場合でも、EAを作成する際には、MetaTrader 5用のバージョンから作成した方が良いということで済ませることができます。必要なテストが終わったら、すぐにMetaTrader 4用のバージョンを作成できます。
もちろん、私が取り上げていない違いも数多くあります。このようなテンプレートのために精巧な構造を構築する際には、これらのニュアンスを知る必要があるため、いくつかの重要性を強調したいのです。MetaTrader 5は前作より確実に良くなっていますが、それでもMetaTrader 4のターミナルを捨てたいとは思いません。なぜなら、多くの場面でMetaTrader 5のターミナルと比較して、計算資源に対する要求はそれほど大きくないからです。どちらのツールもやはり良いものです。
汎用テンプレート構築のニュアンス
このようなテンプレートを作るには、ターミナルの仕組み、EAとは何か、MetaTraderのチャートとは何かを理解する必要があります。また、各チャートは独立したオブジェクトであることを理解する必要があります。このようなチャートには、それぞれ複数の指標と1つのEAのみを関連付けることができます。同じチャートが複数ある場合もあります。通常、1つの銘柄期間で複数の異なるEAを実行するため、または異なる設定で1つのEAの複数のコピーを実行するために、複数のチャートが作成されます。このような微妙な違いを理解した上で、複数のチャートをテンプレートで実現するためには、テンプレートの中にすべてを実装する必要があるという結論に達するはずです。これを図のような形で表現することができます。
それとは別に、ティックについても何か言うべきでしょう。この方法の欠点は、各チャートの新しいティックの出現のためのハンドラを購読することができないことです。ロボットが動いているチャートからティックを適用するか、タイマーを使う必要があります。最終的には、ティックロボットにとって不愉快な思いをすることになるのですが、それは以下のようなものです。
- OnTickハンドラをカスタムで作成する必要がある
- これらのハンドラは、OnTimerの派生型として実装する必要がある
- OnTimerは遅延を伴って動作するため、ティックは完璧ではない(遅延の値は重要ではないが、その存在は重要)
- ティックを取得するには、SymbolInfoTick関数が必要
ミリ秒単位でカウントする人にとっては、特にアービトラージが好きな人にとってはたまらない瞬間になるのではないでしょうか。しかし、私のテンプレートではこれを強調することはありません。長年にわたってさまざまなシステムを構築する中で、私はバー取引というパラダイムにたどり着きました。つまり、取引操作やその他の計算の大部分は、新しいバーが表示されたときにおこないます。この方法には、明らかに多くの利点があります。
- 新しいバーの開始を判断する精度の低さは、取引に大きな影響を与えない
- バーの期間が長くなればなるほど、この影響は小さくなる
- バーの形の離散化により、テスト速度の桁上げが可能
- 本物のティックでテストする場合も、人工ティックでテストする場合も、同じ品質でテストできる
このアプローチは、EAを構築するためのある種のパラダイムを教えるものです。このパラダイムは、ティックEAに関連する多くの問題を解消し、テストプロセスをスピードアップし、主な障害である利益に対する数学的な期待値を高くし、さらに多くの時間と計算能力を節約することができます。他にもいろいろな利点があると思いますが、今回はこの程度で十分だと思います。
このテンプレートを実装するためには、取引ターミナルのワークスペースの構造全体をテンプレート内に実装する必要はなく、各ロボットのチャートを個別に実装すれば十分です。これは最も最適な構造ではありませんが、個々の商品が商品リストの中に一度だけ存在することに同意すれば、この最適化は必要ありません。次のようになります。
チャートの実装に最もシンプルな構造を実現しました。ここで、このようなテンプレートの入力について考えてみましょう。さらに重要なのは、MQL5言語の許容範囲内で、状況に応じてチャートやEAの動的な数を考慮する方法です。この問題を解決する唯一の方法は、文字列入力変数を使用することです。文字列は、非常に大きなデータを保存することができます。実際、このようなテンプレートに必要なパラメータをすべて記述するためには、入力データに動的配列が必要になります。もちろん、そのような機会を利用する人が少ないからというだけで、誰もそのようなことを実施することはありません。文字列は動的配列で、ここに好きなものを入れることができます。では、使ってみましょう。最もシンプルなテンプレートでは、このように3つの変数を導入することにしました。
- Charts:私たちのチャート(リスト)
- Chart Lots: 取引のロット(リスト)
- Chart Timeframes: チャート期間(リスト)
一般的には、これらのデータを1つの文字列にまとめることができますが、そうすると構造が複雑になり、潜在的なユーザーがデータを正しく表現する方法を見つけ出すことが難しくなります。さらに、このデータを文字列から取り出す変換関数が信じられないほど複雑であることは言うまでもありませんが、それを使用する際に、入力ミスをすることは非常に簡単で、非常に不快なことがたくさん起こる可能性があります。売り手の間で同じような解決策を見ましたが、一般的にそれらはすべて正しくおこなわれていました。すべてのデータはカンマで区切って羅列するだけです。EAの開始時に、特殊な関数を使って文字列からデータを取り出し、対応する動的配列に記入し、コード内で使用します。私たちもこの道を歩んでいきます。同一の列挙規則を持つ類似の文字列をさらに追加することができます。:を区切り文字として使用することにしました。カンマを使用する場合、Chart Lotsのような二重配列の扱いが不明となります。このような文字列変数をさらに追加することも可能であり、一般的には、さらに完全で多用途なテンプレートを構築することができます。しかし、ここでの私の仕事は、これを実装する方法を示し、迅速かつ簡単に変更できるテンプレートの最初のバージョンを提供することだけです。
このような配列を実装するだけでは不十分で、例えば共通の変数を実装することも必要です。
- Work Timeframe For Unsigned:指定されていないチャート期間
- Fix Lot For Unsigned: 指定されていないロット
Chartsリストは埋まっているはずです。Chart Lots、Chart Timeframesでも同様の動作は任意です。例えば、すべてのチャートでシングルロットを取り、すべてのチャートで同じ期間を取ることもあります。同様の機能をテンプレートに実装する予定です。テンプレートに基づいて構築されたEAの入力パラメータを設定する際に、簡潔かつ明確にするために、可能な限りこのような実装規則を適用することが望ましくなります。
このようなパターンの最小限の実装のために、さらにいくつかの重要な変数を定義してみましょう。
- Last Bars Count:チャートごとに保存される、各チャートの最後のバーの数
- Deposit For Lot:指定されたロットの使用に対する預金
- First Magic:別のEA取引のためのユニークなID
1つ目の変数はかなり明確だと思います。2つ目の変数は、もっと把握しにくいです。これは、私のEAで自動ロットを規制する方法です。「0」に設定すると、対応する文字列または上記で考えた共有変数で指定された固定ロットのみを取引する必要があることをアルゴリズムに通知することになります。それ以外は、設定で指定したロットが適用できるように、必要な預金を設定しています。預金が少なくても多くても、このロットは次の式に従って値が変化することが理解できます。
- ロット = 入力ロット * (現在の預金/ロット用預金)
これですべてが明確になったはずです。固定ロットにしたい場合はゼロを設定し、それ以外の場合は入力設定の預金をリスクに合わせて調整しています。思うに、安くて快適です。必要であれば、オートロットのリスクアセスメントのアプローチを変更することもできますが、個人的にはこのオプションが好きです。
同期について、特にエキスパートマジックナンバーの設定について触れておくとよいでしょう。EAを使用する場合、あるいは混合形式で取引する場合、自尊心のあるプログラマーは皆、この特定の変数に特別な注意を払います。複数のEAを使用する場合、そのようなEAがそれぞれユニークなIDを持つようにすることが非常に重要であるということです。そうでなければ、注文、取引、ポジションを扱う際に、完全に混乱し、ストラテジーが正しく機能しなくなり、ほとんどの場合、完全に機能しなくなります。その理由は、説明するまでもないでしょう。EAをチャート上に配置するたびに、これらのIDを設定し、繰り返さないようにする必要があります。たった1つのミスでも、悲惨な結果につながるかもしれません。また、誤ってEAでチャートを閉じてしまった場合、新たに設定し直す必要があります。その結果、ミスが発生する確率が非常に高くなります。そのほか、さまざまな面で非常に不快です。例えば、チャートを閉じて、そこでどのIDが使われていたかを忘れてしまった場合、取引履歴を掘り下げて探す必要があります。IDがないと、新しく再起動したEAが正しく動作しないなど、さまざまな不都合が生じる可能性があります。
私のようなテンプレートを使えば、EAの設定で開始IDだけを設定すればよく、残りのIDはインクリメントで自動的に生成され、対応するEAに割り当てられるので、そのような制御から解放され、エラーを最小限に抑えることができます。この処理は、再起動のたびに自動的におこなわれます。とにかく、初めのIDを1つだけ覚えておくことは、途中から適当にIDを覚えるよりずっと楽です。
汎用テンプレートの書き方
テンプレートを実装する時がきました。このテンプレートが必要な方は、ダウンロードしてソースコードで残りを見ることができます。ここでは、私たちの考えに直接関係するものだけを紹介します。ストップレベルやその他のパラメータは、ユーザーが定義します。実装はソースコードで確認することができます。 まず、必ず必要になる入力変数を定義しましょう。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes input int LastBars=10;//Last Bars Count input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned input double RepurchaseLotE=0.01;//Fix Lot For Unsigned input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix) input int MagicE=156;//First Magic
ここでは、共有変数の例と同様に、動的配列に文字列変数を反映させる例を紹介します。ちなみに、このコードはMQL 4でもMQL 5でも同じように見えるでしょう。なるべく全部似せるようにしました。
では、文字列データをどのように取得するのかを決めていきましょう。これは対応する関数によっておこなわれますが、まず、関数が文字列から得たデータを追加するための配列を作成することにします。
//+------------------------------------------------------------------+ //|Arrays | //+------------------------------------------------------------------+ string S[];// Symbols array double L[];//Lots array ENUM_TIMEFRAMES T[];//Timeframes array
次の関数は、これらの配列を埋めるものです。
//+------------------------------------------------------------------+ //| Fill arrays | //+------------------------------------------------------------------+ void ConstructArrays() { int SCount=1; for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools { if (SymbolsE[i] == ':') { SCount++; } } ArrayResize(S,SCount);//set the size of the character array ArrayResize(CN,SCount);//set the size of the array to use bars for each character int Hc=0;//found instrument index for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools { if (i == 0)//if we just started { int LastIndex=-1; for (int j = i; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i,LastIndex); Hc++; } else { S[Hc]=SymbolsE; Hc++; } } if (SymbolsE[i] == ':') { int LastIndex=-1; for (int j = i+1; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1)); Hc++; } else { S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1)); Hc++; } } } for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars { CN[i]=LastBars; } ConstructLots(); ConstructTimeframe(); }
つまり、ここでは区切り文字のおかげで、文字列のデータ量が計算されるのです。最初の配列を基準に、他の配列のサイズを銘柄配列と同様に設定し、銘柄を先に埋めてから、ConstructLotsやConstructTimeframeなどの関数を実行します。それらの実装は、この関数の実装と似ていますが、いくつかの違いがあります。ソースコードでその実装を見ることができます。重複したコードを提示しないように、記事には追加していません。
あとは、仮想チャートとそれに連動する仮想ロボットの適切なクラスを適宜作成する必要があります。まず、仮想チャートとEAが配列に格納されることを定義しておきましょう。
//+------------------------------------------------------------------+
//| Charts & experts pointers |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];
まずはチャートクラスから見ていきましょう。
//+------------------------------------------------------------------+ //| Chart class | //+------------------------------------------------------------------+ class Chart { public: datetime TimeI[]; double CloseI[]; double OpenI[]; double HighI[]; double LowI[]; string BasicSymbol;//the base instrument that was extracted from the substring double ChartPoint;//point size of the current chart double ChartAsk;//Ask double ChartBid;//Bid datetime tTimeI[];//auxiliary array to control the appearance of a new bar static int TCN;//tcn string CurrentSymbol;//symbol ENUM_TIMEFRAMES Timeframe;//timeframe int copied;//how much data is copied int lastcopied;//last amount of data copied datetime LastCloseTime;//last bar time MqlTick LastTick;//last tick fos this instrument Chart() { ArrayResize(tTimeI,2); } void ChartTick()//this chart tick { SymbolInfoTick(CurrentSymbol,LastTick); ArraySetAsSeries(tTimeI,false); copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI); ArraySetAsSeries(tTimeI,true); if ( copied == 2 && tTimeI[1] > LastCloseTime ) { ArraySetAsSeries(CloseI,false); ArraySetAsSeries(OpenI,false); ArraySetAsSeries(HighI,false); ArraySetAsSeries(LowI,false); ArraySetAsSeries(TimeI,false); lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI); lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI); lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI); lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI); lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI); ArraySetAsSeries(CloseI,true); ArraySetAsSeries(OpenI,true); ArraySetAsSeries(HighI,true); ArraySetAsSeries(LowI,true); ArraySetAsSeries(TimeI,true); LastCloseTime=tTimeI[1]; } ChartBid=LastTick.bid; ChartAsk=LastTick.ask; ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT); } }; int Chart::TCN = 0;
このクラスには、ティックとバーの更新を制御する関数と、特定のチャートの必要なパラメータを特定するための必要なフィールドが1つだけあります。そこでは、いくつかのパラメータが欠落しています。必要であれば、例えばChartPointを更新するように、その更新を追加することで、足りないものを追加することができます。バーの配列はMQL4スタイルで作られています。やはりMQL4では、あらかじめ決められた配列を操作するのがとても便利です。ゼロバーが現在のバーであることがわかると、とても便利です。とにかく、これはあくまで私のイメージです。ご自分の好きな方をご自由にお使いください。
今度は、別の仮想EAのクラスを記述します。
//+------------------------------------------------------------------+ //| Bot instance class | //+------------------------------------------------------------------+ class BotInstance//expert advisor object { public: CPositionInfo m_position;// trade position object CTrade m_trade;// trading object ///-------------------this robot settings---------------------- int MagicF;//Magic string CurrentSymbol;//Symbol double CurrentLot;//Start Lot int chartindex;//Chart Index ///------------------------------------------------------------ ///constructor BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index { chartindex=chartindex0; MagicF=MagicE+index; CurrentSymbol=Charts[chartindex].CurrentSymbol; CurrentLot=L[index]; m_trade.SetExpertMagicNumber(MagicF); } /// void InstanceTick()//bot tick { if ( bNewBar() ) Trade(); } private: datetime Time0; bool bNewBar()//new bar { if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 ) { if (Time0 != 0) { Time0=Charts[chartindex].TimeI[1]; return true; } else { Time0=Charts[chartindex].TimeI[1]; return false; } } else return false; } //////************************************Main Logic******************************************************************** void Trade()//main trade function { //Close[0] --> Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart //Open[0] --> Charts[chartindex].OpenI[0] ----------------------------------------------------------------------- //High[0] --> Charts[chartindex].HighI[0] ----------------------------------------------------------------------- //Low[0] --> Charts[chartindex].LowI[0] ------------------------------------------------------------------------- //Time[0] --> Charts[chartindex].TimeI[0] ----------------------------------------------------------------------- if ( true ) { CloseBuyF(); //CloseSellF(); } if ( true ) { BuyF(); //SellF(); } } double OptimalLot()//optimal lot calculation { if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE); else return CurrentLot; } //here you can add functionality or variables if the trading function turns out to be too complicated //////******************************************************************************************************************* ///trade functions int OrdersG()//the number of open positions / orders of this virtual robot { ulong ticket; bool ord; int OrdersG=0; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol ) { OrdersG++; } } return OrdersG; } /////////********/////////********//////////***********/////////trade function code block void BuyF()//buy market { double DtA; double CorrectedLot; DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime if ( (DtA > 0 || DtA < 0) ) { CorrectedLot=OptimalLot(Charts[chartindex]); if ( CorrectedLot > 0.0 ) { //try buy logic } } } void SellF()//sell market { //Same logic } void CloseSellF()//close sell position { ulong ticket; bool ord; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol ) { //Close Sell logic } } } void CloseBuyF()//close buy position { //same logic } bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot { int MagicT[]; ArrayResize(MagicT,magiccount); for ( int i=0; i<magiccount; i++ ) { MagicT[i]=MagicE+i; } for ( int i=0; i<ArraySize(MagicT); i++ ) { if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true; } return false; } /////////********/////////********//////////***********/////////end trade function code block };
コード量を減らすために、繰り返しのロジックをいくつか削除しています。これは、EAの全アルゴリズムを実装するクラスです。このクラスで存在する主な関数は次の通りです。
- Trade():対応するチャートのバーハンドラで呼び出されるメインの取引関数
- BuyF():成行注文による買い
- SellF():成行注文による売り
- CloseBuyF() :成行注文による買いポジションを決済する関数
- CloseSellF():成行注文による売りポジションを決済する関数
バーによる取引を実演するための関数の最低限のセットです。このデモでは、任意のポジションを開き、次のバーで決済するだけです。本記事の枠内ではこれで十分です。このクラスからは、理解を補完するような関数が追加されているはずです。
- OrdersG():チャートにリンクされた特定の銘柄でオープンしているポジションをカウントする
- OptimalLot():取引機能に送信する前にロットを準備する(固定ロットを選択するか、自動ロットを計算する)
- bOurMagic():履歴から、許可されたリストに準拠した取引をチェックする(カスタム履歴を整理するためだけ)
これらの関数は、取引ロジックを実装するために必要な場合があります。また、新しいバーハンドラについて想起させるのも合理的でしょう。
- InstanceTick():別々のEAインスタンスでティックシミュレーションをおこなう
- bNewBar():新しいバーの出現を確認するための述語(InstanceTickの内部で使用)
述語が新しいバーを示した場合、Trade関数がトリガーされます。主な取引ロジックを設定する関数です。対応するチャートとの接続は、インスタンス作成時に割り当てられたchartindex 変数を使用しておこなわれます。そのため、各EAインスタンスは、クウォートすべきチャートを知っています。
では、仮想チャートやEAそのものを作るプロセスを考えてみましょう。仮想チャートは最初に作成します:
//+------------------------------------------------------------------+ //| Creation of graph objects | //+------------------------------------------------------------------+ void CreateCharts() { bool bAlready; int num=0; string TempSymbols[]; string Symbols[]; ConstructArrays();//array preparation int tempcnum=CN[0]; Chart::TCN=tempcnum;//required number of stored bars for all instruments for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph { Charts[j] = new Chart(); Charts[j].lastcopied=0; ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays ArrayResize(Charts[j].OpenI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].HighI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].LowI,tempcnum+2);//----------------------------------- ArrayResize(Charts[j].TimeI,tempcnum+2);//---------------------------------- Charts[j].CurrentSymbol = S[j];//symbol Charts[j].Timeframe = T[j];//timeframe } ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots }
チャートを作成し、仮想EAを持つ配列のサイズを設定した後、EA自体のインスタンスを作成し、仮想EAとチャートの接続を実装する必要があります。
//+------------------------------------------------------------------+ //| create and hang all virtual robots on charts | //+------------------------------------------------------------------+ void CreateInstances() { for (int i = 0; i < ArraySize(S); i++) { for (int j = 0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); break; } } } }
接続は、仮想EAの各インスタンスの作成時にに設定された「j」インデックスを用いておこなわれます。上に示した対応する変数は、そこで強調表示されています。もちろん、このようなことはいろいろな方法で、もっとエレガントにできるのですが、要は一般的な考え方が明確であることが重要だと思います。
あとは、各チャートとそれに関連するEAでティックをどのようにシミュレートしているかを示すだけです。
//+------------------------------------------------------------------+ //| All bcharts & all bots tick imitation | //+------------------------------------------------------------------+ void AllChartsTick() { for (int i = 0; i < ArraySize(Charts); i++) { Charts[i].ChartTick(); } } void AllBotsTick() { for (int i = 0; i < ArraySize(S); i++) { if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick(); } }
ただ、このテンプレートは、もっと本格的な用途を想定した複雑なテンプレートを手直しして得たものなので、あちこちに余計な要素が含まれているかもしれません。お望みならば、それらを簡単に削除し、コードをよりきれいにすることができると思います。
テンプレートだけでなく、シンプルなインターフェイスもあり、例えばフリーランスで注文を受けたときなどにも重宝するのではないかと思います。
このインターフェイスには余白を残しておいたので、万が一スペースが足りない場合は3回分のエントリが可能です。必要に応じて、簡単に拡張したり、構造を完全に変更したりすることができます。この特定の例で、不足している3つのフィールドを追加したい場合、コード内の次の場所を見つける必要があります:
//+------------------------------------------------------------------+ //| Reserved elements | //+------------------------------------------------------------------+ "template-UNSIGNED1",//UNSIGNED1 "template-UNSIGNED2",//UNSIGNED2 "template-UNSIGNED3",//UNSIGNED3 //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1 //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2 //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3 //////////////////////////// //TempText="UNSIGNED1 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText); //TempText="UNSIGNED2 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText); //TempText="UNSIGNED3 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText); ///////////////////////////
最初の3項目はインターフェイス上の新しい要素の名前を割り当て、2番目の3項目はEA開始時にインターフェイスを作成する際に使用され、最後の3項目はインターフェイス上の情報を更新する機能の中で使用されるものです。さて、いよいよ両テンプレートのパフォーマンスをテストしてみましょう。テスターのビジュアライザーで十分に確認できます。MetaTrader 5のビジュアライザーがより優れているため、MetaTrader 5のオプションのみを表示します。その上、作業結果には、効率を確認するために必要なすべてのことが明確に示されます。
ご覧のように、主要なFXペアの全7チャートをアップロードしています。可視化ログでは、リストアップされたすべての銘柄について取引が進行中であることがわかります。取引は必要に応じて単独でおこないます。つまり、EAはそれぞれ自分のチャートで取引し、まったく相互作用しないのです。
結論
今回は、MetaTrader 4とMetaTrader 5のターミナル用の汎用テンプレートを構築する際の主なニュアンスを確認し、シンプルながら動作するテンプレートを作成しました。その作業の最も重要なポイントを分析し、さらにMetaTrader 5テスタービジュアライザーを使用してその実行性を確認しました。このようなテンプレートがそれほど複雑でないことは、もうお分かりだと思います。一般に、このようなテンプレートは様々な実装が可能ですが、適用可能でありながら全く異なるテンプレートになり得ることは明らかです。そのような構造物を作るための基本的なニュアンスを理解することが主なポイントです。必要であれば、個人用のテンプレートを作り直すことも可能です.
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/12434





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