MetaTraderのMultibot(第2回):動的テンプレートの改良
内容
- はじめに
- 動的テンプレートの概念
- 動的テンプレートの基本設定
- 設定と適用されたディレクティブを含むファイル名の規則
- ファイルの読み込みと作成の方法
- 仮想チャートとEAの作成
- 仮想EAの動的な読み込みと再構成
- マジックナンバーの自動生成
- 数量正規化システム
- 自動ロット
- APIとの同期
- 取引ロジックの一般的メソッド
- グラフィカルインターフェイス
- 結論
- リンク
はじめに
前回の記事では、マーケットで最も人気のあるソリューションのいくつかに触発され、そのようなテンプレートの私自身のバージョンを作成することができました。しかし、私が取り組んでいるいくつかのプロジェクトを考慮すると、このソリューションは最適ではないことがわかりました。加えて、このようなテンプレートの全体的なアーキテクチャに関連する多くの制限や不都合がまだあります。このようなテンプレートは、大多数の平均的なソリューションには十分かもしれませんが、私のソリューションには適していません。2つ目の非常に重要なポイントは、EAを購入する可能性のあるユーザーやエンドユーザーの観点から、私個人としては、最大限のシンプルさと最小限の設定数を望むという事実です。理想的には、そのようなEAは、ユーザー側での再最適化やその他の操作を必要としないはずです。結局のところ、私がお金を払うのは、機能するソリューションのためだけではありません。もっと重要なのは、できるだけフレンドリーで誰にでも理解できるインターフェイスです。
動的テンプレートの概念
前回のEAを踏まえて、なぜこのようなテンプレートが作成されたのかを思い出してみましょう。主な目標は、「商品-期間」ペアごとに異なる設定で1つのEAを起動できるようにすることでした。これは、各チャートに別々にEAを起動させないために必要でした。これにより、このようなシステムの管理と構成が簡素化されます。同じEAのコピーが1つの端末にあればあるほど、潜在的なユーザーミスの可能性が高くなるからです。加えて、例えばマジックナンバーの順番や、人的要因に基づくその他の理由によって、そのようなEA間で競合が発生する可能性もあります。まず、最初のテンプレートの長所と短所を説明しましょう。それから、新しいテンプレートについても同じことをおこない、新しいアプローチの違いと利点がはっきりとわかるようにします。
静的(ベース)テンプレートの長所
- 各商品の個別設定 - 期間(仮想チャート上の仮想EA)
- よりシンプルで高速なコード(実行が速い)
- アプローチの普及(市場に広く浸透しており、その実行可能性が証明されている)
静的(ベース)テンプレートの短所
- 設定の複雑さとエラーの確率の高さ(設定内の長い文字列チェーン)
- 機能拡張の可能性が制限
- 手動で再設定できず、仮想ロボットを追加または削除できない
これらのデータを理解すると、ソルーションは広範囲に及んでいるものの、非常に限定的であることがわかります。もっと面白いソルーションを思いつくことは可能です。さらに、MetaTrader端末の外で独立した動的最適化を実行する私のEAを私のソリューションに統合することで、ソリューション改善に向かいました。新テンプレートの本質を明らかにし、旧バージョンと比較しての利点をより理解するのに役立つ図から始めましょう。
ここで、CLOSINGを含む左下の要素に注目してください。このテンプレートでは、対応する仮想チャート上で最後に開いたポジションの設定を保存することができます。これは、頻繁な設定変更や戦略の破綻に対する特別な保護メカニズムです。あるロジックに従ってポジションを建てたのであれば、同じロジックに従ってポジションを決済することに関心があり、その後に初めて、より新しい設定に変更することができます。そうでなければ、とんでもないことになりかねません。いつもなるとは限りませんが、一般的なロジックレベルでは、そのような瞬間は即座にブロックした方がいいです。
新しいテンプレートは、MetaTrader 4とMetaTrader 5の作業ディレクトリを作成する機能と構造をファイルシステムと共に使用します。言い換えれば、各仮想取引ペアに必要な設定を含むことができるテキストファイルを使用してテンプレートを管理します。これにより、手動で再設定する必要なく、設定が最大限に容易になり、操作中にEAを動的に再構築できます。しかもそれだけではありません。以下に挙げるようなメリットもあります。
動的(新しい)テンプレートの長所
- 取引端末に関係なく、テキストファイルを使用して各商品の期間を設定する
- フォルダからの設定の動的な読み取りと仮想チャートの自動的な開閉は、EAとともに実行される
- Web APIと自動同期できる(ポート443経由が必要)
- 設定は、端末の共通フォルダ(*\Common\Files)を使用しておこなわれる
- 1台のマシン上のすべての端末をAPIと同期させるために有力なEAが必要。また、ローカルネットワーク内の共有フォルダを経由して、複数のマシン上で動作する可能性もある。
- これらのファイルを管理(例:ファイルの作成や削除、設定の変更)する外部プログラムと統合することも可能
- 有料テンプレートと無料テンプレートのペアを作成することが可能。APIへの接続を解除すれば、効果的に動作させるためには有料版が必要なデモ版として設計可能。
動的(新しい)テンプレートの短所
- ファイルシステムでの作業
ここでの欠点も非常に条件的なもので、唯一の選択肢はWeb統合か他の方法(例えばRAM経由)を使った通信ですが、この場合の切り札は、追加のライブラリを使用せず、コードがクロスプラットフォームになることです(MQL4とMQL5の両方のEAに適している)。このおかげで、そのようなEAはマーケットの要件に適合し、希望すれば簡単に売りに出すことができます。もちろん、必要なものを追加したり調整したりする必要はありますが、これは、ここでご紹介するインサイトをもって、非常に簡単におこなうことができるでしょう。
このようなEAの内部構造を見て、上に述べたことを視覚的、図式的にどのように表現できるかを考えてみましょう。次の図を使用してできると思います。
この仕組みは非常にシンプルで、2つの独立したタイマーがそれぞれのタスクを担当します。最初のタイマーはAPIから設定をアップロードし、2番目のタイマーは同じ設定をEAのメモリに読み込みます。さらに、2つの矢印が示されていますが、これは、これらの設定を手動で作成変更することも、サードパーティのアプリケーションやその他のコードを使用してこのプロセスを自動化することも可能であることを象徴しています。理論的には、このテンプレートは、他のMetaTrader 4やMetaTrader 5のEAやスクリプトなど、他のコードから制御することができます。
設定を読み込んだ後、これらの設定が新しいものなのか、それともフォルダの中に新しい設定があるのか、あるいはそのうちのいくつかを削除してしまったのかが判断されます。比較の結果、ファイルに変更がなければ、テンプレートは機能し続けますが、もし何かが変更されていれば、新しい構成でコード全体を再起動し、作業を継続する方がはるかに正しいでしょう。例えば、操作するチャートのみで動作するEAがあった場合、端末内ですべて手動でおこなう必要があります。しかしこの場合、すべての制御はテンプレート内部で完全に自動的におこなわれ、手動による介入は必要ありません。
動的テンプレートの基本設定
さて、コードに移り、その要点を分析しましょう。まず、バーごとのロジックを使用して取引を管理するのに十分であると私が考える、最小限の入力セットをお見せします。ただし、その前に、今後このパターンのコードをよりよく理解するために、このパターンが使用する重要なパラダイムについて触れておく必要があります。
- 取引操作と入力シグナルの計算は、新しいバーが開いたときにおこなわれる(このポイントは、各仮想チャートのティックに基づいて個別に自動的に計算される)
- 一度に1つのチャートに1つのポジションしか建てることができない(EURUSD M1とEURUSD M5は異なるチャートとみなされる)
- 別のチャート上の新しいポジションは、前のポジションが決済されるまで建てられない(私はこの構造が最もシンプルで正しいと考えます。あらゆる種類の追加購入と平均化は、すでにある程度の追加重み付けになっているためです。)
さて、これらのルールと制限を考慮した上で、このコードを平均やマーチンゲールで動作するように変更したり、例えば、予約注文を処理するように変更したりできることを理解する必要があります。必要であれば、ご自分で構造を変更することもできます。では、取引を管理するための最小限のパラメータを見てみましょう。
//+------------------------------------------------------------------+ //| main variables | //+------------------------------------------------------------------+ input bool bToLowerE=false;//To Lower Symbol input string SymbolPrefixE="";//Symbol Prefix input string SymbolPostfixE="";//Symbol Postfix input string SubfolderE="folder1";//Subfolder In Files Folder input bool bCommonReadE = true;//Read From Common Directory input bool bWebSyncE = false;//Sync with API input string SignalDirectoryE = "folder1";//Signal Name(Folder) input string ApiDomen = "https://yourdomen.us";//API DOMEN (add in terminal settings!) input bool bInitLotControl=true;//Auto Lot input double DeltaBarPercent=1.5;//Middle % of Delta Equity Per M1 Bar (For ONE! Bot) input double DepositDeltaEquityE=100.0;//Deposit For ONE! Bot input bool bParallelTradingE=true;//Parallel Trading input int SLE=0;//Stop Loss Points input int TPE=0;//Take Profit Points
この例では、似たような特性に基づいて変数を異なるブロックに分けました。見ての通り、変数は多くありません。最初のブロックは、ブローカーごとに異なるスタイルの商品名を扱うように設計されています。例えば、お使いのブローカーが「EURUSD」を使用しているのであれば、このブロックのすべての変数は現在のままにできますが、他のオプションも可能です。
- eurusd
- EURUSDt
- tEURUSD
- _EURUSD
- eurusd_
などです。これ以上続けるつもりはありません。ご自分で解決できると思います。もちろん、優れたテンプレートであれば、こうしたバリエーションのほとんどに対応できるはずです。
2番目のサブブロックは、どのフォルダからファイルを読み込むかを示します。それに、APIから同じフォルダにデータを読み込んでいます。正しいフォルダ名を指定すると、MetaTraderは対応するサブディレクトリを作成し、それを使用します。指定しなければ、すべてのファイルはルートに配置されます。共有端末フォルダによって、興味深い機能が提供されます。このオプションを有効にすると、MetaTrader 5内で実行されている複数のEAだけでなく、MetaTrader 4またはMetaTrader 5で実行されているかに関係なく、すべての端末を組み合わせることができます。こうすることで、両方のテンプレートが同じ設定を使用して同じように機能し、最大限の統合と同期が保証されます。
もうお分かりかもしれませんが、3番目のサブブロックは、APIとの同期タイマーがあれば、それを有効/無効にします。APIとの通信は、MQL4とMQL5の両方で動作するWebRequest関数のみを使用して実行されることに注意することが重要です。ここでの唯一の制限は、APIが443番ポートで実行されることです。しかし、MQL5ではこの方法が拡張され、別のポートから接続できるようになりました。しかし、テンプレートとその上に構築されたソリューションがクロスプラットフォームであることを保証するために、私のAPIではこの考えを放棄しました。
シグナル名がファイルディレクトリでもあるようにAPIを構築しました。こうすることで、シグナル名を知ることで、さまざまなシグナルに接続することができます。いつでも古いシグナルを切断し、新しいシグナルに接続することができます。もちろん、この方法ではファイルそのものをダウンロードすることはできません。ファイルの内容をJSONで取得し、テンプレート自体のコードから自分で作成します。この場合、JSON文字列からデータを抽出するためのメソッドを追加する必要がありますが、MQL5ではこれを簡単におこなうことができるため、個人的には何の問題も生じませんでした。もちろん、APIを使用する前に、任意のドメインが必要であり、MetaTraderの設定で許可された接続のリストに追加します。
4番目のブロックは、リスクとエントリ量をコントロールするため、非常に重要です。もちろん、同じテキストファイルを使用して、各商品期間の数量を個別に設定することもできますが、使用する取引ペアのボラティリティデータを使用して自動設定をおこなうことにしました。また、このアプローチでは、残高の増加に伴って自動的に比例して数量が増加します。これを自動ロットと呼びます。
5番目のブロックは1つの変数のみからなります。デフォルトでは、テンプレート内で動作するすべての仮想EAは独立して取引し、他の仮想チャート上にあるEAが建てたポジションに注意を払いません。この変数は、1つのEAで1つのポジションしか建てられないようにします。他のEAはそのポジションがクローズするまで待ち、クローズしてから初めて自分のポジションを建てられます。場合によっては、この取引スタイルは非常に有効です。
最後(5番目)のブロックには、ストップの設定のみが含まれています。ゼロに設定すれば、それなしで取引することになります。まだ存在しない6番目のブロックには、必要に応じてパラメータを追加することができます。
設定と適用されたディレクティブを含むファイル名の規則
まず、ファイルから設定をアップロードする主なメソッドとその読み込みメソッド、そしてこれらのメソッドがどのように必要なファイルを見つけるかについて説明したいと思います。このようなファイルが正しく読み込まれるようにするには、まず、シンプルで理解しやすいファイル名のルールを導入する必要があります。まず、テンプレート内でこのディレクティブを見つける必要があります。
//+------------------------------------------------------------------+ //| your bot unique name | //+------------------------------------------------------------------+ #define BotNick "dynamictemplate" //bot
このディレクティブはボットのニックネームを設定します。このニックネームの下では、あらゆることが起こります。
- 新しく作成したファイルに名前を付ける
- ファイルの読み込み
- 端末のグローバル変数の作成
- 端末のグローバル変数の読み込み
- その他
このテンプレートの設定は、「**** dynamictemplate.txt」ファイルしか受け付けません。つまり、設定ファイルの名前付けに関する最初のルールを定義しました。拡張子の前のファイル名は常に 「dynamictemplate」で終わる必要があります。この名前は自由に変更できます。したがって、異なるエイリアスを持つ2つのEAを作成した場合、それらのEAは「兄弟」の設定を安全に無視し、自分のファイルでのみ動作します。
同じことを実現する類似の指令がもう1つあります。
//+------------------------------------------------------------------+ //| unique shift for difference of EA | //+------------------------------------------------------------------+ #define MagicHelp 0 //bot magic shift
唯一の違いは、突然1つの取引口座で複数のEAを使用することになった場合、2つ以上のEAが他のEAの注文をクローズしないように、このディレクティブは、ファイルの場合と同様に、注文マジックレベルで異なるEA間の区別を保証することです。同じように、次のEAを作成する際には、エイリアスの変更とともにこの番号も変更しなければなりません。このテンプレートに基づいて新しいEAを作成するたびに、この数字に1ずつ足していくのがよいでしょう。
次に、EAがどのチャートを対象としているかを示すファイル名の部分に移ります。その前に、この配列を見てみましょう。
//+------------------------------------------------------------------+ //| applied symbols | //+------------------------------------------------------------------+ string III[] = { "EURUSD", "GBPUSD", "USDJPY", "USDCHF", "USDCAD", "AUDUSD", "NZDUSD", "EURGBP", "EURJPY", "EURCHF", "EURCAD", "EURAUD", "EURNZD", "GBPJPY", "GBPCHF", "GBPCAD", "GBPAUD", "GBPNZD", "CHFJPY", "CADJPY", "AUDJPY", "NZDJPY", "CADCHF", "AUDCHF", "NZDCHF", "AUDCAD", "NZDCAD", "AUDNZD", "USDPLN", "EURPLN", "USDMXN", "USDZAR", "USDCNH", "XAUUSD", "XAGUSD", "XAUEUR" };
この配列には、非常に重要なファイルフィルタリング機能があります。言い換えれば、テンプレートはこの銘柄リストに存在するチャートと対応する設定のみを読み込みます。ここでは、設定ファイルの命名にも、指定リストの調整にも等しく機能する、さらにいくつかのルールを修正する必要があります。
- 商品名はすべて大文字に変換されます。
- すべての商品の接頭辞と接尾辞が取り除かれ、真の商品名だけが残ります。
では、例を見てみましょう。EURUSD銘柄の名前が、お使いのブローカーで「eurusd_」と表示されているとします。この場合、設定ファイル名を「EURUSD」としたまま、さらに設定で以下のことをおこないます。
//+------------------------------------------------------------------+ //| symbol correction | //+------------------------------------------------------------------+ input bool bToLowerE=true;//To Lower Symbol input string SymbolPrefixE="";//Symbol Prefix input string SymbolPostfixE="_";//Symbol Postfix
言い換えれば、EA内部で名前を小文字に変換し、適切な接尾辞を追加することで、元の名前に変換されることをテンプレートに知らせるのです。ブローカーは一般的に、接頭辞や接尾辞を一切使わない古典的な大文字の命名法にこだわるため、このようなことはあまり起こりませんが、それでもこの選択肢は排除されません。
今のところ、テンプレートが必要なBotNick設定であることを理解できるようなファイル名の付け方と、正しい取引商品とのマッチング方法だけがわかっています。やはり、設定を特定のチャート期間にマッチする必要があります。そのために、以下のルールを導入しました。
- ファイル名の後にスペースを入れ、この期間に相当する時間を分単位で記入します。
- 許容されるチャート期間の範囲はM1...H4です。
この範囲にある期間を具体的に挙げることが重要だと思います。これらすべての期間がMetaTrader 4とMetaTrader 5の両方で表現されていることは、私にとって非常に重要でした。これが期間を選択した主な理由です。もう1つの非常に重要な理由は、H4を超えた非常に上位の期間は、一般的に自動バー取引では使用されないことです。いずれにせよ、私はそのような例を見たことがないので、選択肢は以下の期間に絞られました。
- M1 - 1分
- M5 - 5分
- M15 - 15分
- M30 - 30分
- H1 - 60分
- H4 - 240分
これらの期間で十分です。それに、それぞれの期間について、分単位で計算するのは非常に簡単で、この数字は非常に覚えやすいでしょう。ここで、ファイル名の一般的な構造を示すことができます。これに従って、手動またはサードパーティのコードやAPIを使用して自動的に設定を作成します。まず、大まかな概要を見てみましょう。
- 「INSTRUMENT」 + 「 」 + 「PERIOD IN MINUTES」 + 「 」 + BotNick + 「.txt」
さらに、クロージングポジション用のファイルを複製するためにテンプレートが使用する構造を見てみましょう。
- 「CLOSING」 + 「 」 + 「INSTRUMENT" + 「 」 + 「PERIOD IN MINUTES」 + 「 」 + BotNick + 「.txt」
ご覧のように、この2つのファイルの違いは、「CLOSING」の追加と、それに続く区切りスペースだけです。名前内のすべてのシグナル線は、もちろん1つのスペースで区切られます。テンプレートインタープリターがファイル名からこれらのマーカーを認識し、抽出できるようにするためです。したがって、ある設定が特定のチャートに属するかどうかは、その名前によってのみ決定されます。では、このルールに従った設定の例をいくつか見てみましょう。
- EURUSD 15 dynamictemplate.txt
- GBPUSD 240 dynamictemplate.txt
- EURCHF 60 dynamictemplate.txt
- CLOSING GBPUSD 240 dynamictemplate.txt
例としてはこれで十分なのは明らかです。最後の名前に注目してください。このファイルは「GBPUSD 240 dynamictemplate.txt」に基づいてコピーされ、EAがコピーを作成した正確な端末のフォルダに配置されます。これは、複数の端末内で、異なるが同一のEAが同じファイルに複数回書き込むことを防ぐためです。端末の共有フォルダから読み込むオプションを無効にすると、通常のファイルもそこに書き込まれます。これは、対応する端末の特定のEAをそれぞれ独立した数の設定で構成する必要がある場合に必要となります。例として、テンプレートの横にいくつかのファイルを残しておきます。具体的な例でわかりやすくするためであり、また、それらを別のフォルダに移動して試すことができるようにするためです。これで、設定を使用する際の一般的な側面についての考察を終えます。
ファイルの読み込みメソッドと作成メソッド
テンプレートの機能を完全に使いこなすためには、ファイルの読み書きがどのように機能するかを理解することが望ましいです。これによって、最終的には、仮想ロボットを取り付けたい商品期間の目印としてだけでなく、必要に応じて個別にカスタマイズすることもできるようになります。そのためには、まず次のようなメソッドを考えることができます。
//+------------------------------------------------------------------+ //| used for configuration settings | //+------------------------------------------------------------------+ bool QuantityConfiguration() { FilesGrab(); // Determine the names of valid files // Check if there are changes in the configuration settings (either add or delete) if (bNewConfiguration()) { return true; } return false; }
このメソッドは、作業ディレクトリ内のファイル群が更新されているかどうかを判定します。更新されている場合、このメソッドをシグナルとしてすべてのチャートとEAを再起動し、新規追加または不要な商品期間を削除します。では、FilesGrabメソッドを見てみましょう。
//+------------------------------------------------------------------+ //| reads all files and forms a list of instruments and periods | //+------------------------------------------------------------------+ void FilesGrab() { string file; string tempsubfolder= SubfolderE == "" ? "" : SubfolderE + "\\"; // SubfolderE is the path to the specific subfolder // Returns the handle of the first found file with the specified characteristics, based on whether CommonReadE is True or False long total_files = !bCommonReadE? FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file) :FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file,FILE_COMMON); if(total_files > 0) { ArrayResize(SettingsFileNames,0); // Clear the array from previous values if there are files to be read do { int second_space = StringFind(file, " ", StringFind(file, " ") + 1); // Searches for the index of the second space in the file's name if(second_space > 0) { string filename = StringSubstr(file, 0, second_space); // Extracts the string/characters from the filename up to the second space ArrayResize(SettingsFileNames, ArraySize(SettingsFileNames) + 1); // Increases the size of the array by one SettingsFileNames[ArraySize(SettingsFileNames) - 1] = filename; // Adds the new filename into the existing array } } while(FileFindNext(total_files, file)); // Repeat for all the files FileFindClose(total_files); // Close the file handle to free resources } }
このメソッドは、EAに関連するファイル名を「EURUSD 60」のような名前に予備的に収集します。言い換えれば、後に商品と期間のペアに解析される名前の部分だけを残します。ただし、これらのファイルの読み込みは、ここではおこなわれず、各仮想EA内で個別におこなわれます。そのためにはまず、文字列そのものを銘柄と期間に解析する必要があります。これにはいくつかのポイントがあります。そのうちの1つは次のようなものです。
//+------------------------------------------------------------------+ //| symbol validator | //+------------------------------------------------------------------+ bool AdaptDynamicArrays() { bool RR=QuantityConfiguration(); // If a new configuration of files is detected (new files, changed order, etc.) if (RR) { // Read the settings (returns the count) int Readed = ArraySize(SettingsFileNames); int Valid =0; // Only valid symbol name needs to be populated (filenames are taken from already prepared array) ArrayResize(S, Readed); for ( int j = 0; j < Readed; j++ ) { for ( int i = 0; i < ArraySize(III); i++ ) { // check the symbol to valid if ( III[i] == BasicNameToSymbol(SettingsFileNames[j]) ) { S[Valid++]=SettingsFileNames[j]; break; // stop the loop } } } //resize S with the actual valid quantity ArrayResize(S, Valid); return true; } return false; }
このメソッドは、許可された商品のリストに存在しない設定(チャート)を破棄するために非常に重要です。ポイントは、最終的にリストでフィルタリングされたすべてのチャートを「S」配列に追加し、仮想チャートオブジェクトを作成するコードでさらに使用できるように準備することです。
また、重要なポイントは、基本設定を読み込む際に常に発生する設定の予約です。基本設定ポジションが建てられた場合、定期的な設定予約を停止します。「CLOSING」接頭辞が付いたバックアップファイルは、常に現在の端末ディレクトリに保存されます。
//+------------------------------------------------------------------+ //| сopy settings from the main file to a CLOSING file | //+------------------------------------------------------------------+ void SaveCloseSettings() { string FileNameString=Charts[chartindex].BasicName; bool bCopied; string filenametemp; string filename=""; long handlestart; //Checking if SubfolderE doesn't exist, if yes, assign tempsubfolder to be an empty string string tempsubfolder= SubfolderE == "" ? "" : SubfolderE + "\\"; //Find the first file in the subfolder according to bCommonReadE and assign the result to handlestart if (bCommonReadE) handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON); else handlestart=FileFindFirst(tempsubfolder+"*",filenametemp); //Check if the start of our found file name matches FileNameString if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString ) { //if yes, complete the file's path filename=tempsubfolder+filenametemp; } //keep finding the next file while conditions are aligned while ( FileFindNext(handlestart,filenametemp) ) { //if found file's name matches FileNameString then add found file's name to the path if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; break; } } //if handlestart is not INVALID_HANDLE then close the handle to release the resources after the search if (handlestart != INVALID_HANDLE) FileFindClose(handlestart); //Perform file copy operation and notice if it was successful if ( bCommonReadE ) bCopied=FileCopy(filename,FILE_COMMON,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI); else bCopied=FileCopy(filename,0,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI); }
ここで、バックアップ設定は、例えば、テンプレートが再起動されたり、再度読み込まれたりしたときに、そこからデータが読み込まれるように機能することを明確にしておく価値があります。これは、この設定に対応するポジションでのみ可能です。仮想EAの特定のインスタンスにポジションがない場合、テンプレートは一般的な設定と同期されます。
仮想チャートとEAの作成
次に、次のメソッドでそこからすべてのデータを取得し、同時に必要な仮想チャートを作成します。
//+------------------------------------------------------------------+ //| creates chart objects | //+------------------------------------------------------------------+ void CreateCharts() { bool bAlready; int num=0; string TempSymbols[]; string Symbols[]; ArrayResize(TempSymbols,ArraySize(S)); // Resize TempSymbols array to the size of S array for (int i = 0; i < ArraySize(S); i++) // Populate TempSymbols array with empty strings { TempSymbols[i]=""; } for (int i = 0; i < ArraySize(S); i++) // Count the required number of unique trading instruments { bAlready=false; for (int j = 0; j < ArraySize(TempSymbols); j++) { if ( S[i] == TempSymbols[j] ) // If any symbol is already present in TempSymbols from S, then it's not unique { bAlready=true; break; } } if ( !bAlready ) // If the symbol is not found in TempSymbols i.e., it is unique, add it to TempSymbols { for (int j = 0; j < ArraySize(TempSymbols); j++) { if ( TempSymbols[j] == "" ) { TempSymbols[j] = S[i]; break; } } num++; // Increments num if a unique element is added } } ArrayResize(Symbols,num); // Resize the Symbols array to the size of the num for (int j = 0; j < ArraySize(Symbols); j++) // Now that the Symbols array has the appropriate size, populate it { Symbols[j]=TempSymbols[j]; } ArrayResize(Charts,num); // Resize Charts array to the size of num int tempcnum=0; tempcnum=1000; // Sets all charts to a default of 1000 bars Chart::TCN=tempcnum; for (int j = 0; j < ArraySize(Charts); j++) { Charts[j] = new Chart(); Charts[j].lastcopied=0; // Initializes the array position where the last copy of the chart was stored Charts[j].BasicName=Symbols[j]; ArrayResize(Charts[j].CloseI,tempcnum+2); // Resizes the CloseI array to store closing price of each bar ArrayResize(Charts[j].OpenI,tempcnum+2); // Resizes the OpenI array for opening prices ArrayResize(Charts[j].HighI,tempcnum+2); // HighI array for high price points in each bar ArrayResize(Charts[j].LowI,tempcnum+2); // LowI array for low price points of each bar ArrayResize(Charts[j].TimeI,tempcnum+2); // TimeI array is resized to store time of each bar string vv = BasicNameToSymbol(Charts[j].BasicName); StringToLower(vv); // Append prefix and postfix to the basic symbol name to get the specific symbol of the financial instrument Charts[j].CurrentSymbol = SymbolPrefixE + (!bToLowerE ? BasicNameToSymbol(Charts[j].BasicName) : vv) + SymbolPostfixE; Charts[j].Timeframe = BasicNameToTimeframe(Charts[j].BasicName); // Extracts the timeframe from the basic name string } ArrayResize(Bots,ArraySize(S)); // Resize Bots array to the size of S array }
このメソッドは、繰り返しのない商品期間のコレクションを作成することに重点を置き、それに基づいて対応するチャートのオブジェクトを作成します。さらに、各チャートの棒グラフ配列のサイズが1000本強に設定されていることにも注意してください。ほとんどの戦略を実行するには、これで十分すぎるほどだと思います。何かが起これば、この量を必要な量に変更することができます。では、仮想EAオブジェクトを作成するメソッドで素材を統合してみましょう。
//+------------------------------------------------------------------+ //| attaching all virtual robots to charts | //+------------------------------------------------------------------+ void CreateInstances() { // iterating over the S array for (int i = 0; i < ArraySize(S); i++) { // iterating over the Charts array for (int j = 0; j < ArraySize(Charts); j++) { // checking if the BasicName of current Chart matches with the current item in S array if ( Charts[j].BasicName == S[i] ) { // creating a new Bot instance with indices i, j and assigning it to respective position in Bots array Bots[i] = new BotInstance(i,j); break; } } } }
ここで仮想EAが作成され、対応するチャートにアタッチされます。この「j」チャートのIDがEA内部に保存され、将来、仮想EA内でどのチャートからデータを取得すべきかがわかるようになります。この2つのクラスの内部構造については、前回の記事で触れました。多くの点で、新しいコードはいくつかの些細な変更を除けば、それと似ています。
仮想EAの動的な読み込みと再構成
明らかに、このセクションは私たちにとって非常に重要です。なぜなら、これが新しいテンプレートの概念全体の半分を占めているからです。結局のところ、仮想チャートとEAを作成するのは仕事の半分でしかありません。仮想チャートの再現は最低限のレベルではできたようですが、それだけでは不十分です。取引端末に干渉することなく、その場で新しい設定を取得し、即座にEAを再設定する方法を見つけることをお勧めします。この問題を解決するために、冒頭の図にあるように簡単なタイマーを使用します。
//+------------------------------------------------------------------+ //| we will read the settings every 5 minutes + | //+------------------------------------------------------------------+ bool bReadTimer() { if ( TimeCurrent() - LastTime > 5*60 + int((double(MathRand())/32767.0) * 60) ) { LastTime=TimeCurrent(); int orders=OrdersG(); bool bReaded=false; if (orders == 0) bReaded = ReadSettings(false,Charts[chartindex].BasicName);//reading a regular file else bReaded = ReadSettings(true,Charts[chartindex].BasicName);//reading file to close position if (orders == 0 && bReaded) SaveCloseSettings();//save settings for closing position return bReaded; } return false; }
ご覧のように、タイマーは「5」分ごとに作動するため、新しいファイルが即座に選ばれることはありません。しかし、私の考えでは、このタイミングはダイナミクスを確保するのに十分だと思います。これで十分でない場合は、1秒まで短くすることができます。ただ1つ理解しておいてほしいのは、ファイル操作を頻繁に使うことは推奨されておらず、可能であればこの方法は避けるべきだということです。このコードでは、ReadSettingsメソッドに注目してください。必要なファイルを(仮想EAごとに個別に)読み込み、読み込んだ後にEAを再設定します。このメソッドは、一般的な設定を読み込むか(選択した仮想EAにポジションがない場合)、設定の更新を一時停止し、このポジションが作成された設定に従ってポジションがクローズされるまで待つことができるように設計されています。
//+------------------------------------------------------------------+ //| reading settings | //+------------------------------------------------------------------+ bool BotInstance::ReadSettings(bool bClosingFile,string Path) { string FileNameString=Path; int Handle0x; string filenametemp; string filename=""; long handlestart; string tempsubfolder= SubfolderE == "" ? "" : SubfolderE + "\\"; if (!bClosingFile)//reading a regular file { if (!bCommonReadE) { handlestart=FileFindFirst(tempsubfolder+"*",filenametemp); int SearchStart=0; if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; } if (filename != filenametemp || filename == "") { while ( FileFindNext(handlestart,filenametemp) ) { if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; break; } } } if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search if (filename != "") { Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI); if ( Handle0x != INVALID_HANDLE )//if the file exists { FileSeek(Handle0x,0,SEEK_SET); ulong size = FileSize(Handle0x); string str = ""; for(ulong i = 0; i < size; i++) { str += FileReadString(Handle0x); } if (str != "" && str != PrevReaded) { FileSeek(Handle0x,0,SEEK_SET); //read the required parameters ReadFileStrings(Handle0x); // FileClose(Handle0x); LastRead = TimeCurrent(); RestartParams(); } else { FileClose(Handle0x); } return true; } else { return false; } } } else { handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON); int SearchStart=0; if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; } if (filename != filenametemp || filename == "") { while ( FileFindNext(handlestart,filenametemp) ) { if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; break; } } } if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search if (filename != "") { Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI|FILE_COMMON); if ( Handle0x != INVALID_HANDLE )//if the file exists { FileSeek(Handle0x,0,SEEK_SET); ulong size = FileSize(Handle0x); string str = ""; for(ulong i = 0; i < size; i++) { str += FileReadString(Handle0x); } if (str != "" && str != PrevReaded) { FileSeek(Handle0x,0,SEEK_SET); //read the required parameters ReadFileStrings(Handle0x); // FileClose(Handle0x); LastRead = TimeCurrent(); RestartParams(); } else { FileClose(Handle0x); } return true; } else { return false; } } } } else//reading a file to close a position { handlestart=FileFindFirst(tempsubfolder+"*",filenametemp); int SearchStart=8;//when the line starts with "CLOSING " if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; } if (filename != filenametemp || filename == "") { while ( FileFindNext(handlestart,filenametemp) ) { if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString ) { filename=tempsubfolder+filenametemp; break; } } } if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search if (filename != "") { Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI); if ( Handle0x != INVALID_HANDLE )//if the file exists { FileSeek(Handle0x,0,SEEK_SET); ulong size = FileSize(Handle0x); string str = ""; for(ulong i = 0; i < size; i++) { str += FileReadString(Handle0x); } if (str != "" && str != PrevReaded) { PrevReaded=str; FileSeek(Handle0x,0,SEEK_SET); //read the required parameters ReadFileStrings(Handle0x); // FileClose(Handle0x); LastRead = TimeCurrent(); RestartParams(); } else { FileClose(Handle0x); } return true; } else { return false; } } } return false; }
まず強調しておきたいのは、この方法は特に2種類のファイルを読むために設計されているということです。渡されたbClosingFileマーカーによって、一般的な設定か 「for closing」のどちらかが読み込まれます。各ファイルの読み込みはいくつかの段階からなります。
- 前回読み込んだファイルの内容と現在のファイルの内容を比較する
- 内容が異なる場合は、更新された設定を読み込む
- 必要であれば、新しい設定で仮想EAを再起動する
このメソッドは、リソースのクリーニングやその他のアクションがすでに考えられているように構築されています。あとは、前のメソッドで呼び出される次のメソッドを実装するだけです。このようなファイル操作の煩わしさから解放され、必要な読み取りコードを正確に書くことにできるだけ集中できるように、すべてをこのような方法でおこなおうとしました。読み込みここでおこなわれます。
//+------------------------------------------------------------------+ //| read settings from file line by line | //+------------------------------------------------------------------+ void BotInstance::ReadFileStrings(int handle) { //FileReadString(Handle,0); }
ここでファイルを開いたり閉じたりする必要はありません。必要なのは、ファイルを文字列ごとに読み込み、読み込んだ内容を適切な変数に正しく追加することだけです。そのためには、両方の一時変数を使い、すぐにすべてのデータを戦略の設定として使用する変数に書き込めばよいのです。しかし、これらの目的のために作成されたこのメソッドで、すでに設定を埋めておくことをお勧めします。
//+------------------------------------------------------------------+ //| function to prepare new parameters | //+------------------------------------------------------------------+ void BotInstance::RestartParams() { //additional code // MagicF=SmartMagic(BasicNameToSymbol(Charts[chartindex].BasicName), Charts[chartindex].Timeframe); CurrentSymbol=Charts[chartindex].CurrentSymbol; m_trade.SetExpertMagicNumber(MagicF); }
最後の3つの文字列は必須なので触る必要はありません。そこで最も興味深いのは、各仮想EAにマジックナンバーを自動的に割り当てるように設計されたSmartMagicメソッドです。この段階で知っておく必要があるのは、EAの設定を再割り当てするロジックを、もう少し高い位置、つまり空のブロックに書く必要があるということだけです。必要であれば、指標など、まだ残っているかもしれないものをすべて再現することもできます。
マジックナンバーの自動生成
これまでのメソッドから離れることなく、テンプレート内のすべての仮想EAの独立した取引を保証するユニークな注文IDを生成するメソッドをすぐに公開したいと思います。そのために次のメソッドを使用しました。
例えば、最も近いマジックナンバーの間に「10000」のステップを割り当てます。各商品について、まずその予備的なマジックナンバー、たとえば「10000」や「70000」を記録します。しかし、これだけでは十分ではありません。商品には周期もあるからです。そこで、この中間マジックにもう1つ数字を加えます。
最も簡単なのは、ファイルの読み取り構造と同じように、これらの期間に相当する分を追加することです。以下がその方法です。
//+------------------------------------------------------------------+ //| Smart generation of magical numbers | //| (each instrument-period has its own fixed magic number) | //+------------------------------------------------------------------+ int BotInstance::SmartMagic(string InstrumentSymbol,ENUM_TIMEFRAMES InstrumentTimeframe) { // initialization int magicbuild=0; // loop through the array for ( int i=0; i<ArraySize(III); i++ ) { // check the symbol to assign a magic number if ( III[i] == InstrumentSymbol ) { magicbuild=MagicHelp+(i+1)*10000; break; // stop the loop } } // add identifier for time frame magicbuild+=InstrumentTimeframe; return magicbuild; }
ここで、マジックナンバーのシフトを追加することで、端末内の異なるEA間とはいえ、マジックナンバーセットを一意にすることができるようです。
//+------------------------------------------------------------------+ //| unique shift for difference of EA | //+------------------------------------------------------------------+ #define MagicHelp 0 //bot magic shift [0...9999]
一般的には、すべてが非常にシンプルです。マジックナンバー間のステップが大きいため、シフトオプションが非常に多く、必要な数のEAを作成するには十分すぎるほどです。この構造体を使用する唯一の条件は、数字「9999」を超えないことです。さらに、シフトが分単位の時間枠と一致しないことを確認する必要があります。この場合、2つの異なるテンプレートのマジックナンバーが一致する可能性があるからです。そのようなオプションを考えなくてもいいように、例えば「241」、「241*2」、「241*3」、「241*N」のように、「240」よりも少しシフトするだけで大丈夫です。
このアプローチをまとめると、この構造によって、この祖ルーションの暗黙の中間目標の1つであったマジックナンバーの設定から完全に解放されることがわかります。唯一の欠点は、2つ以上の独立した仮想EAを接続することができないことです。なぜなら、それらのマジックナンバーは最終的にこれらの戦略の相互作用につながり、その結果、それらのロジックが崩壊してしまうからです。実際、誰がそのようなエキゾチックな動きを必要とするのかは知りません。それに、これは本来意図された機能ではありません。もし興味のある方がいらっしゃれば、次回の記事に追加するかもしれません。
数量正規化システム
シンプルでカスタマイズしやすいテンプレートがあれば、数量を設定する方法を正しく選択することが非常に重要です。確率論に関する私の記事、特にこの記事から得た最終的な結論が、シンプルで効果的な数量の等化を実行するのに役立ったことは非常に興味深いです。私は、平均期間とポジションの最終的な財務結果の絶対値の大きさが同程度であるべきだと仮定して、数量を均等化することにしました。つまり、このような均等化の唯一の正しいソルーションは、以下の考慮事項に基づくべきであるということです。
最終的な取引チャートのエクイティの平均増減率は、最終的なアセンブリに含まれる各EA(独立した商品-期間)によって均等に提供されるべきです。必要な数値はすべて、仮想チャートのリストに表示されていない商品期間のデータを使用せずに計算する必要があります。数量は、EAの数に比例するだけでなく、入金(自動ロット)にも比例して分配されるべきです。
そのためには、すぐに以下の定義と方程式を導入することが重要です。まず、リスクを調整するために以下のパラメータを導入しました。
- DeltaBarPercent:DepositDeltaEquityの割合
- DepositDeltaEquity:ポジションを持つ1つのM1バーの許容可能なエクイティデルタを計算するための1つのボットの入金
まず用語が不明瞭に思えるかもしれないので、はっきりさせておきましょう。便宜上、別個の仮想EAが機能する入金を指定し、ポジションを建てた場合、そしてトップポイント「M1」バーからボトムへの動きがあった場合、またはその逆の場合、この入金のどの部分が(エクイティの形で)増減するかをパーセンテージで示します。
コードの目的は、私たちの要求を考慮して自動的にエントリ量を選択することです。そのためには、さらに数学的な量とそれに基づく方程式が必要になります。結論を出すつもりはありません。コードを説明するために提供するだけです。
- Mb:EAの作業チャート上で、barsサイズの選択された履歴範囲のポイント単位のバーの平均サイズ
- Mb1:M1に再計算されたEAの作業チャート上のbarsサイズの選択された履歴範囲のポイント単位のバーの平均サイズ
- Kb:現在のチャートの平均バーサイズとM1に相当するバーの接続比率
- T:選択したチャートの期間を分単位に短縮(私たちのファイルにあったのと同じ)
- BasisI:選択した商品チャートのM1ローソク足の平均サイズに対する、預託通貨のエクイティラインの必要平均増減
- Basisb:1ロットの取引で、選択された商品のチャート上のM1ローソク足の平均サイズに対する入金通貨のエクイティラインの実際の平均増減
- Lot:選択されたロット(数量)
さて、計算に使われるすべての量を列挙したので、それを分析し、理解することから始めましょう。必要なロットを計算するには、まず、「M1」に対する上位時間枠のバーサイズの関係がどのようにおこなわれているかを理解する必要があります。ここで役立つのが次の式です。
これはまさに、M1からデータを読み込むことことなく、仮想EAの選択されたインスタンスが動作する仮想チャートの提示された期間上であっても、同じ特性を計算することを可能にする式です。この係数をかけると、M1期間で計算した場合とほぼ同じデータが得られます。これが最初にやるべきことです。この値の計算方法は次のようになります。
//+------------------------------------------------------------------+ //| timeframe to average movement adjustment coefficient | //+------------------------------------------------------------------+ double PeriodK(ENUM_TIMEFRAMES tf) { double ktemp; switch(tf) { case PERIOD_H1: ktemp = MathSqrt(1.0/60.0); break; case PERIOD_H4: ktemp = MathSqrt(1.0/240.0); break; case PERIOD_M1: ktemp = 1.0; break; case PERIOD_M5: ktemp = MathSqrt(1.0/5.0); break; case PERIOD_M15: ktemp = MathSqrt(1.0/15.0); break; case PERIOD_M30: ktemp = MathSqrt(1.0/30.0); break; default: ktemp = 0; } return ktemp; }
さて、もちろん、M1にどのような値を適応させるのかを理解する必要があります。以下です。
言い換えれば、仮想EAが動作するチャート上のポイント単位でローソク足の平均サイズを計算します。値を計算したら、前の値を使用して次のように変換します。
これらの動作は、いずれも以下のメソッドでおこなわれます。
//+------------------------------------------------------------------+ //| average candle size in points for M1 for the current chart | //+------------------------------------------------------------------+ double CalculateAverageBarPoints(Chart &Ch) { double SummPointsSize=0.0; double MaxPointSize=0.0; for (int j = 0; j < ArraySize(Ch.HighI); j++) { if (Ch.HighI[j]-Ch.LowI[j] > MaxPointSize) MaxPointSize= Ch.HighI[j]-Ch.LowI[j]; } for (int j = 0; j < ArraySize(Ch.HighI); j++) { if (Ch.HighI[j]-Ch.LowI[j] > 0) SummPointsSize+=(Ch.HighI[j]-Ch.LowI[j]); else SummPointsSize+=MaxPointSize; } SummPointsSize=(SummPointsSize/ArraySize(Ch.HighI))/Ch.ChartPoint; return PeriodK(Ch.Timeframe)*SummPointsSize;//return the average size of candles reduced to a minute using the PeriodK() adjustment function }
ここで、M1に還元された結果の値を使用して、Basisb変数を計算することができます。このように計算します。
ティックサイズは、価格が「1」ポイント動いたときに、「1」ロットの数量を持つポジションのエクイティの変化量です。分足の平均サイズと掛け合わせれば、動きの大きさが分足の平均サイズと等しくなったことを考慮して、1ロットのポジションの持分変動額を求めることができます。次に、以下のように、BasisIの残りの値を計算します。
その中のパーセンテージと保証金は、まさに私たちが必要とする基礎となるコントロールパラメータです。あとは、ベースが等しくなるように比例比率を選ぶだけで、この比率が最終的なロットとなります。
記載されたすべての操作は、以下の方法でおこなわれます。
//+------------------------------------------------------------------+ //| calculate the optimal balanced lot for the selected chart | //+------------------------------------------------------------------+ double OptimalLot(Chart &Ch) { double BasisDX0 = (DeltaBarPercent/100.0) * DepositDeltaEquityE; double DY0=CalculateAverageBarPoints(Ch)*SymbolInfoDouble(Ch.CurrentSymbol,SYMBOL_TRADE_TICK_VALUE); return BasisDX0/DY0; }
したがって、すべての仮想EAのエクイティへの貢献が等しくなるように、すべての商品の取引量を効果的に等しくしています。これは固定ロットモードのようなもので、EAごとに個別に設定していたようなものですが、今回はその必要性をなくしました。これはまったく異なるリスク管理システムですが、その違いは、多様化された取引に適応していることです。これを使用すると、利益曲線の最適な安定性を実現し、このルーチンを排除したい場合は、とにかくこれを実行する必要があります。実際、複数のEAのバランスを取るのは最も微妙な瞬間です。同じようなことをしてきた人たちは、この決断を評価すると思います。いずれにせよ、このバランシング方法が自分に合わない場合は、いつでも適切なファイルに設定を書き込んで、このシステムを変更することができます。
自動ロット
正規化されたロットは、入金に比例して増やすことができます。その方法を理解するために、以下の表記を紹介しましょう。
- Lot:特定の仮想EAの正規化された(バランスの取れた)ロット
- AutoLot:自動ロットモード用の「Lot」入金に再計算される(自動ロットが有効になっているときに受け取る必要がある)
- DepositPerOneBot:現在の保証金の一部(Depositの一部)で、仮想EAの1つだけがコントロールできる
- DepositDeltaEquity:正規化(バランスロット)を行った保証金
- Deposit:現在の入金
- BotQuantity:マルチボット内で取引されている仮想EAの現在の数
次に、AutoLotが何に等しいかを書くことができます。
- AutoLot = Lot * (DepositPerOneBot / DepositDeltaEquity).
通常の正規化された数量の場合、入金を無視し、次の入金が1つの仮想 EA (DepositDeltaEquity)に割り当てられることを承認します。しかし、自動ロットの場合、この入金は本物ではないため、正規化した数量を比例して変更し、リスクを実際の入金に合わせる必要があります。しかし、1つの仮想EAが入金の一部しか占めていないことを考慮して、適応させる必要があります。
- DepositPerOneBot = Deposit / BotQuantity.
私のテンプレートでは、自動ロットはこのように機能します。このアプローチは非常に便利で、指数関数的成長の急勾配を調整するのに必要だと思います。ソースコードは添付ファイルにあります。これらの値を適切に調整した結果を見てみましょう。
このモードでの利益曲線は、設定が正しく、最初のシグナルが利益を生むものであれば、ほぼこのようになることに注意してください。この曲線は、戦略のプロフィットファクターが高いほど、またテストエリアでの取引回数が多いほど、より滑らかで指数関数的な曲線になります。これこそが、分散投資が非常にうまくいく理由なのです。私たちのテンプレートには、この効果を最大化するための基本がすべて含まれています。さらに、入金の負荷にもご注意ください。入金の滑らかさと均一性は、現在の入金負荷の正しい正規化とその後のスケーリングを間接的に示します。この例は、問題のテンプレートに基づいた私の製品から引用しました。
APIとの同期
この機能はオプションであり、非常に簡単に無効にしたり、テンプレートから完全に削除することができますが、個人的には私の製品に非常に便利だと思いました。前述したように、同期もタイマーを通じてトリガーされます。
//+------------------------------------------------------------------+ //| used to read the settings every 5 minutes + | //+------------------------------------------------------------------+ void DownloadTimer() { // Check if the passed time from the last download time is more than 5 minutes if ( TimeCurrent() - LastDownloadTime > 5*60 + int((double(MathRand())/32767.0) * 60) ) { // Set the last download time to the current time LastDownloadTime=TimeCurrent(); // Download files again DownloadFiles(); } }
では、メインのDownloadFilesメソッドを見てみましょう。
//+------------------------------------------------------------------+ //| used to download control files if they isn't present | //+------------------------------------------------------------------+ void DownloadFiles() { string Files[]; // Initialize the response code by getting files from the signal directory int res_code=GetFiles(SignalDirectoryE,Files); // Check if the list of files is successfully got if (res_code == 200) { // Proceed if there is at least one file in the server if (ArraySize(Files) > 0) { // Download each file individually for (int i = 0; i < ArraySize(Files); i++) { string FileContent[]; // Get the content of the file int resfile = GetFileContent(SignalDirectoryE,Files[i],FileContent); // Check if the file content is successfully got if (resfile == 200) { // Write the file content in our local file WriteData(FileContent,Files[i]); } } } } }
最初のステップは、サーバー上の指定されたフォルダにあるファイルの全リストを見つけるためにAPIにアクセスすることです。設定を配布します。フォルダ名はSignalDirectoryEです。これは私の考えによるシグナルの名前でもあります。ファイルのリストを受け取った後、それぞれのファイルが別々にダウンロードされます。このロジックは非常に便利だと思います。こうすることで、いつでも切り替えられるシグナル(フォルダ)をたくさん作成することができます。これをどうするか、どうアレンジするかは、読者次第です。私の仕事は、簡単に接続できる既製の機能を提供することです。それでは、サーバーからファイル名のリストを取得するメソッドテンプレートを見てみましょう。
//+------------------------------------------------------------------+ //| getting the list of files into an array | //+------------------------------------------------------------------+ int GetFiles(string directory,string &fileArray[]) { //string for getting a list of files in the form of JSON via GET to API string urlList = ApiDomen+"/filelist/"+directory;//URL char message[];//Body of the request string headers = "Password_key: " + key;// We form the headers of the request string resultheaders = "";//returning headers string cookie = "";//cookies int timeout = 1500;//waiting for a response when requesting a file or json char result[]; // We send a GET request to the server to receive JSON with a list of files int res_code = WebRequest("GET", urlList, headers, timeout, message, result, resultheaders); bool rez = extractFiles(CharArrayToString(result),fileArray); if (rez) return res_code; else return 400; }
ここで必要なのは、同じように「URL」文字列を形成することです。ここで最も重要なのは、以下の文字列です。
- filelist
- Password_key
最初の文字列は、APIの関数の1つです。必要なら名前を変えることもできます。このような関数はいくつかあるでしょう。例えば、以下のような操作を提供します。
- 設定ファイルをAPIにアップロードする(個々のアプリケーションまたはプログラムから)
- API上の設定ファイルをクリアする(個々のアプリケーションやプログラムから)
- 既存のディレクトリからファイルのリストをアップロードする(EAから)
- (EAから)ファイルコンテンツをアップロードする
他にも必要な機能があるかもしれませんが、EAで必要となるのは最後の2つだけです。2番目の文字列は「ヘッダー」の1つです。また、APIに渡す内容に基づいてフォームを作成する必要があります。原則として、アクセスキーさえあれば侵入を防ぐことができます。しかし、追加のデータを伝える必要があれば、さらに追加することができます。サーバーから受け取った文字列はここで解析されます。
//+------------------------------------------------------------------+ //| get file list from JSON string | //+------------------------------------------------------------------+ bool extractFiles(string json, string &Files[]) { return false; }
JSONを受け取り、それを名前に解析します。残念ながら、普遍的な解析コードは存在しません。それぞれのケースは個別です。個人的には、解析コードを書くのはそれほど難しいとは思いません。もちろん、適切なライブラリがあるに越したことはありませんが、個人的にはできるだけ自分でコードを書きたいです。次に、ファイルの内容を取得する同様のメソッドを見てみましょう。
//+------------------------------------------------------------------+ //| getting the file content | //+------------------------------------------------------------------+ int GetFileContent(string directory,string filename,string &OutContent[]) { //string for getting a file content in the form of JSON via GET to API string urlList = ApiDomen+"/file_content/"+directory+"/"+filename;// char message[];// Body of the request string headers = "Password_key: " + key;// We form the headers of the request string resultheaders = "";//returning headers string cookie = "";//cookies int timeout = 1500;//waiting for a response when requesting a file or json char result[]; // We send a GET request to the server to receive JSON with a file content int res_code = WebRequest("GET", urlList, headers, timeout, message, result, resultheaders); bool rez = extractContent(CharArrayToString(result),OutContent); if (rez) return res_code; else return 400; }
ファイルのリストではなく、ファイル内の文字列のリストを取得することを除けば、すべて前のものとまったく同じです。もちろん、ファイルの内容を文字列ごとにJSONを解析することは、次のメソッドで別のロジックによっておこなわれます。これは、兄弟であるextractFilesと同じ目的を持ちます。
//+------------------------------------------------------------------+ //| read the contents of the file from JSON each line separately | //+------------------------------------------------------------------+ bool extractContent(string json, string &FileLines[]) { return false; }
もちろん、すべてを私の言うとおりにする必要はありません。私がまさにこれと同じように作られた製品をすでに持っているというだけの話です。具体的な実例を使うことで、より理解しやすくなると思います。ファイルの内容を受け取ったら、次のメソッドを使用して使用する 文字列ごとに安全に書き込むことができます。
//+-----------------------------------------------------------------------+ //| fill the file with its lines, which are all contained in data | //| with a new line separator, and save in the corresponding directory | //+-----------------------------------------------------------------------+ void WriteData(string &data[],string FileName) { int fileHandle; string tempsubfolder= SubfolderE == "" ? "" : SubfolderE + "\\"; if (!bCommonReadE) { fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI); } else { fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON); } if(fileHandle != INVALID_HANDLE) { FileSeek(fileHandle,0,SEEK_SET); for (int i=0; i < ArraySize(data) ; i++) { FileWriteString(fileHandle,data[i]+"\r\n"); } FileClose(fileHandle); } }
このように、ファイル名によってファイルが作成されます。もちろん、その内容も個別の文字列の形で書き込まれます。これは小さなファイルにはまったく十分なソリューションです。作業中に速度が落ちることはありませんでした。
取引ロジックの一般的メソッド
この問題については前回の記事で触れましたが、もう一度強調して、このメソッドは各仮想EAで新しいバーが開始されたときに機能することを思い出してください。OnTickタイプのハンドラと考えることもできますが、私たちの場合はもちろんOnBarになります。ちなみに、MQL5にはそのようなハンドラはありません。私たちが望むものとは少し違った動作をしますが、バーの取引に大きな影響を与えるわけではないので、問題は少ないです。
//+------------------------------------------------------------------+ //| the main trading function of individual robot instance | //+------------------------------------------------------------------+ void BotInstance::Trade() { //data access //Charts[chartindex].CloseI[0]//current bar (zero bar is current like in mql4) //Charts[chartindex].OpenI[0] //Charts[chartindex].HighI[0] //Charts[chartindex].LowI[0] //Charts[chartindex]. ??? //close & open //CloseBuyF(); //CloseSellF(); //BuyF(); //SellF(); // Here we can include operations such as closing the buying position, closing selling position and opening new positions. // Other information from the chart can be used for making our buying/selling decisions. // Here is a simple trading logic example if ( Charts[chartindex].CloseI[1] > Charts[chartindex].OpenI[1] ) { CloseBuyF(); SellF(); } if ( Charts[chartindex].CloseI[1] < Charts[chartindex].OpenI[1] ) { CloseSellF(); BuyF(); } }
テンプレート内にいくつかの基本的なロジックを実装したので、これを例として独自のロジックを構築することができます。混乱しないように、BotInstanceクラスのこのメソッドの隣に独自のロジックと変数を追加することをお勧めします。メインのTradeメソッドで使用するメソッドと変数を使用してロジックを構築することをお勧めします。
グラフィカルインターフェイス
このテンプレートには、前バージョンと同様、シンプルなユーザーインターフェイスの例が含まれており、配色や内容を変更することができます。このインターフェイスは、MetaTrader 4とMetaTrader 5の両方のテンプレートで同じです。
疑問符は、データを追加したり、不要なブロックを削除したりできる場所を示しています。おこなうのはとても簡単です。インターフェイスを操作するメソッドは2つあります。CreateSimpleInterfaceとUpdateStatusです。とてもシンプルです。それらの動作をお見せするつもりはありません。名前の通りです。
このインターフェイスに3つの非常に便利なフィールドを追加しました。最後の3つの文字列を見れば、現在使用している構成に関連する「予約済みマジックナンバー回廊」を見ることができます。設定ファイルを削除したり追加したりすれば、それに応じてこの回廊は狭くなったり広がったりします。さらに、異なるEAを競合から守る必要があります。このフィールドはこれに役立ちます。残りの2つのフィールドは、いずれかの設定が最後に読み込まれた時刻と、APIと最後に同期がおこなわれた時刻を示します。
結論
この記事では、より便利で機能的なテンプレートモデルにたどり着きました。これは、とりわけ、さらなる拡張や変更に適しています。コードはまだ完璧にはほど遠く、最適化したり修正したりしなければならないことがたくさんありますが、それらを考慮しても、そこに何を何のために追加するかについて、いくつかの明確なアイディアがあります。次の記事では、ポジションを仮想化し、このデータに基づいて、各EAを個別に最適化し、戦略の設定ファイルを生成する独自の複数通貨間オプティマイザーを手に入れたいと思います。
複数通貨間オプティマイザーは、すべての仮想商品期間を同時に最適化することを可能にします。これらすべての設定を統合することで、リスクを減らし、より安全で優れた利益曲線を得ることができます。自動分散と利益向上は、さらなる改善のための絶対的な優先事項だと考えています。その結果、エンドユーザーにとって最大限の機能と利便性を維持しながら、戦略から最大限の利益を引き出せるような基本的なアドオンを手に入れたいと思います。これは売買シグナルの外骨格のようなものです。
リンク
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14251
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索