
エキスパートアドバイザやインディケータ、スクリプトの入力パラメータを保存する為のテキストファイル
概論
様々な取引ツールの使用や開発をしていると、externやinputの修飾子を使用した入力パラメータ作成の標準ツールが必ずしも十分ではないことに気づきます。確かに私達は全てのニーズを満たす普遍的なソリューションを持っていますが、このアプローチは時に柔軟性を欠いていて少し煩雑なものでもあります。例として、次の場合を挙げてみましょう。
-
全く変わらないか、ごく稀に変わるパラメータを標準ツールで作成しなければいけないとします。(注文のマジックナンバーやスリッページの値)
-
インディケータやエキスパートアドバイザのプロパティに、RSI、WPRなどのタイプの中から唯一のソースデータを選択する必要がある場合、そのパラメータの一つだけが必要になるのだとしても、それらのパラメータを作成しプロパティ画面で使用できるようにする必要があります。 もう一つのこのタイプの例としては、1週間のエキスパートアドバイザの動作スケジュールのパラメータです。何のために月曜日と火曜日に関することを、または金曜日に行われるであろうことを、水曜日に記録する必要があるのか?しかしこれは必要なものなので全てのスケジュールは入力パラメータに含まれます。言い換えれば、入力パラメータに動的に作成されたオブジェクトを記述するのは十分に難しく効果が少ないものです。
-
ツールのプロパティが表示されるリストに何かしらの説明やコメント、タイトルを配置する必要がある場合、適切なコンテンツを持つ新しい文字列プロパティを作成する必要があります。これは常に便利というわけではありません。
テキストファイル内のパラメータの保存方法
入力パラメータの作成と保存メソッドを持つ機能を追加するのには、最も一般的なテキストファイルを使用することができます。ここには何でも配置することができ、編集や移動も簡単にできます。これらの構造はINIファイルで作成することができます。テキストファイルに保存されたinteger型の配列は以下のようになります。
/*配列サイズ*/ {array_size},5 /*配列自体*/ {array_init},0,1,2,3,4
引用した例では、文字列の始めに『アンカー』または『セクション名』が記述され、コンマの後はこのセクションの内容が記述されています。任意の一意のシンボルの文字列が『アンカー』となりえます。このようなファイルはターミナルの『サンドボックス』で作成され保存されます。次に、インディケータやエキスパートアドバイザまたはスクリプトのコード内の初期化ブロックでCSV形式ファイルとして、このファイルを読み取りの為に開きます。
保存した配列を読み取る時になったら、{array_size}の名前の『アンカー』をファイル内で検索します。ファイルの始めでFileSeek(handle,0,SEEK_SET)を呼びだしてファイルポインタを移動します。{array_init}の名前で次のアンカーを探し、これが見つかったら配列型に応じてそれらを変換する文字列を必要な回数読み取ります(この場合、integer)。ConfigFiles.mqhファイルには、『アンカー』とデータ読み取りの検索プロセスを実装する簡単な関数が含まれています。
言い換えれば、一般的にファイルに保存するオブジェクトは、CSV形式で必要とされるカンマで区切られたデータを含む次の文字列の『アンカー』で記述される必要があります。これらに続く、『アンカー』やデータ文字列は、いくつでも良いですが、1つだけ条件があり、1つの『アンカー』には1つの文字列のみ対応します。
コメント、様々な説明、メモ、説明書は、任意の形式および任意の場所でそのファイルに記述される必要があります。1つ明白な要件があり、テキストは『アンカー』のシーケンス(データ)を乱してはいけません。もう一つの望ましい条件は、大きいテキストはファイルの終わりに配置する必要があります。これは『アンカー』の探索を速くするためです。
上記のエキスパートアドバイザの動作スケジュールの保存は以下のようになります。
…....... {d0},0,0,22 {d1},0,0,22 {d2},1,0,22 …........ {d6},0,0,22
ここでは、『アンカー』という名前は、特定の番号で曜日を表しています(つまり、{d1}は日番号1など)。次にその日にエキスパートアドバイザが取引するかしないかを定義するbool値が続きます(この例ではd1は取引を行わない)。取引が行われない場合、次の値は読み取られませんが、ここに残ります。最後の2つのinteger値は、エキスパートアドバイザの動作の開始/終了時間を表しています。
日足バーの発生で、エキスパートアドバイザは新しい日の動作スケジュールを読み込みます。勿論、動作ロジックに応じて、データ文字列は日ごとに他の必要な値で変更および追加することができます。このように、メモリにはその日のデータのみ保存されます。
『アンカー』名は任意のもので構いませんが、常識を持った名前にすることをお勧めします。『アンカー』に抽象的な名前を付けるのは適切ではありません。名前は、意味を持つ必要があり、かつ一意のものである必要があります。また、データ検索が名前によって行われることを覚えておきましょう。同一の名前を持ついくつかの『アンカー』が1つのファイルにあることはありますが、これは特定の実装の場合です。
動作しているインディケータから取ったコードのフラグメントを調べてみましょう。私達のインディケータの動作にはいくつかの通貨ペアに基づくデータが必要になります。インディケータはタイマーのデータをリクエストし、その後自分のロジックに従いそれを処理します(この場合、このロジックの特徴は重要ではありません)。ブローカーは時々通貨ペア名に様々な接尾辞や接頭辞を追加するということを覚えておく必要があります。例えば、EURUSDの場合#.EURUSD.chとなることがあります。この事実はコード内で他の通貨ペアを正確に参照する為に必ず考慮する必要があります。私達のシーケンスは以下のようになります。
1. 次の内容を含むbroker.cfgテキストファイルを作成します。
[PREFIX_SUFFIX],#.,.ch
2. 次の内容を含む<indicator_name>.cfgテキストファイルを作成します。
/*『アンカー』を持つ文字列内の通貨ペア数 [CHARTNAMES] */ [CHARTCOUNT],7 /*インディケータがそのチャートからデータを読み込む通貨ペア名*/ [CHARTNAMES],USDCAD,AUDCAD,NZDCAD,GBPCAD,EURCAD,CADCHF,CADJPY /*通貨ペアの一番目または二番目に主要通貨(この場合CAD)*/ [DIRECT],0,0,0,0,0,1,13. サンドボックスに両方のファイルを入れます。
4. インディケータのコード内でいくつかの補助関数を定義します(もしくはConfigFiles.mqhファイルを含めます):
// 指定した名前のファイルをCSVファイル形式で開き、そのハンドルを返します int __OpenConfigFile(string name) { int h= FileOpen(name,FILE_READ|FILE_SHARE_READ|FILE_CSV,','); if(h == INVALID_HANDLE) PrintFormat("Invalid filename %s",name); return (h); } //+------------------------------------------------------------------+ //| 関数はlongタイプのiRes値を1つ読み込みます | //| handleファイル内でstrSec『アンカー』の名前を持つセクション内で成功した場合 | //| trueを返し、エラーの場合にはfalseを返します | //+------------------------------------------------------------------+ bool _ReadIntInConfigSection(string strSec,int handle,long &iRes) { if(!FileSeek(handle,0,SEEK_SET) ) return (false); string s; while(!FileIsEnding(handle)) { if(StringCompare(FileReadString(handle),strSec)==0) { iRes=StringToInteger(FileReadString(handle)); return (true); } } return (false); } //+------------------------------------------------------------------+ //| 関数はhandleファイルでstrSec『アンカー』の名前を持つセクションで | //| countサイズでsArray文字列配列を読み込みます 成功した場合 | //| trueを返し、エラーの場合にはfalseを返します | //+------------------------------------------------------------------+ bool _ReadStringArrayInConfigSection(string strSec,int handle,string &sArray[],int count) { if(!FileSeek(handle,0,SEEK_SET) ) return (false); while(!FileIsEnding(handle)) { if(StringCompare(FileReadString(handle),strSec)==0) { ArrayResize(sArray,count); for(int i=0; i<count; i++) sArray[i]=FileReadString(handle); return (true); } } return (false); } //+------------------------------------------------------------------+ //| 関数はhandleファイルでstrSec『アンカー』の名前を持つセクションで | //| countサイズでboolタイプのbArray配列を読み込みます。 成功の場合 | //| trueを返し、エラーの場合にはfalseを返します。| //+------------------------------------------------------------------+ bool _ReadBoolArrayInConfigSection(string strSec,int handle,bool &bArray[],int count) { string sArray[]; if(!_ReadStringArrayInConfigSection(strSec, handle, sArray, count) ) return (false); ArrayResize(bArray,count); for(int i=0; i<count; i++) { bArray[i]=(bool)StringToInteger(sArray[i]); } return (true); }また、インディケータにこのようなコードを入れます:
….. input string strBrokerFname = "broker.cfg"; // ブローカに関するデータを持つファイル名 input string strIndiPreFname = "some_name.cfg"; // インディケータの設定のファイル名 ….. string strName[]; // ([CHARTNAMES])通貨ペアの名前を持つ配列 int iCount; // 通貨ペア数 bool bDir[]; // ([DIRECT])『追跡方法』を持つ配列 ….. int OnInit(void) { string prefix,suffix; // 接頭辞と接尾辞初期化に必要なローカル変数 // ですが一度だけ使用されるもの prefix= ""; suffix = ""; int h = _OpenConfigFile(strBrokerFname); // 設定ファイルから接尾辞と接頭辞についてのデータを読み込みます。エラーが確定した場合 // デフォルト値を残したまま続行します。 if(h!=INVALID_HANDLE) { if(!_GotoConfigSection("[PREFIX_SUFFIX]",h)) { PrintFormat("Error in config file %s",strBrokerFname); } else { prefix = FileReadString(h); suffix = FileReadString(h); } FileClose(h); } …. // インディケータの設定を読み込みます。 if((h=__OpenConfigFile(strIndiPreFname))==INVALID_HANDLE) return (INIT_FAILED); // 通貨ペア数を読み込みます if(!_ReadIntInConfigSection("CHARTCOUNT]",h,iCount)) { FileClose(h); return (INIT_FAILED); } // すでにその数を読み込んだ通貨ペア名を持つ配列を読み込みます if(!_ReadStringArrayInConfigSection("[CHARTNAMES]",h,strName,iCount)) { FileClose(h); return (INIT_FAILED); } // 通貨名を必要な形式にします for(int i=0; i<iCount; i++) { strName[i]=prefix+strName[i]+suffix; } // bool型のパラメータの配列を読み込みます if(!_ReadBoolArrayInConfigSection("[DIRECT]",h,bDir,iCount)) { FileClose(h); return (INIT_FAILED); } …. return(INIT_SUCCEEDED); }
コードの実行結果では二つの動的配列と二つの重要なローカル変数が初期化されています。broker.cfg名を持つ作成されたファイルは、常に手動で接頭辞と接尾辞を入力しなくてもよいように何度も必要になります。
アプリケーションの可能性のある分野。欠点と代替
上記の状況に加えて提案される方法は、様々な通貨ペアや1つの『コントロールセンター』を必要とする同様のタスクで動作する、インディケータまたはエキスパートアドバイザの複数のコピーの設定管理に便利です。これはターミナル自体へのアクセスせずに、リモート設定の為に編集またはテキストファイルの交換をする必要がある時に便利です。ターミナルがインストールされているコンピュータへのアクセスはFTPでのみという場合が多々あるからです。
もう一つの例は、トレーダーが異なる場所(異なる国という場合もある)にある数十のターミナルを管理する場合です。一度設定ファイルを作成し、それを全てのターミナルの『サンドボックス』に送信します。上記の例を思い出すならば、全てのコンピューターでエキスパートアドバイザが同期動作するようにする、週に一回のスケジュールの配信をすることは可能です。コードのフラグメントを上記で紹介したインディケータは、28ペアで動作しテキストで記述した二つのファイルだけを同期管理しています。
もう一つの興味深い実用方法は、プロパティ領域とファイルでの変数の同時格納です。また、読み取りプライオリティロジックを実装することができます。説明の為に、マジックナンバーの値の初期化の例で擬似コードのフラグメントを見ていきましょう。
….. extern int Magic=0; ….. int OnInit(void) { ….. if(Magic==0) { // これはユーザーがMagicを初期化せず、 // デフォルト値が残ったということです。それでは、ファイル内でMagic変数値を探してみましょう …... } if(Magic==0) { // 依然と同様にゼロの為、ファイル内にはこのような変数はありません // 現在の状況を処理し、 // このケースではエラーで終了します return (INIT_FAILED); }
この例で検証しているメソッドのもう一つの利点が明らかになりました。ファイルをインディケータやエキスパートアドバイザの多数のコピーを管理するのに使用する場合、このようなアプローチによってファイルからの集中管理設定を避けて、より細かな設定を各コピーに別々に行うことが可能になります。
公平性の為にメソッドの欠点も挙げてみましょう。
第一に低速さが挙げられます。これはこの方法がインディケータやエキスパートアドバイザの初期化段階でのみ、もしくは新しい日足バーが始まる時など時々使用される場合には、あまり問題にはならないかもしれません。
第二にサポートドキュメントへの徹底的かつ慎重なアプローチが必要な点です。このような方法で設定したツールの管理方法をトレーダーに説明することは非常に困難です。そしてこれが、滅多に変更されないこのような設定ファイル内で保存するもう一つの理由です。勿論、テキストファイル自体の編集や準備に細心の注意を払わなければいけません。この段階でのエラーは、深刻な財政的結果をもたらす可能性をはらみます。
さて、上記のタスクの為の別の解決方法を見ていきましょう。
そのうちの一つは、INIファイルです。これは本当に良いデータ保存方法で、信頼性の高いものです。INIファイルはレジストリへの書き込みをしたくない場合に使用されます。その構造は明確で分かりやすいです。INIファイルで記録された動作スケジュールの最初の例は以下のようになります。
…....... [d0] Allowed=0 BeginWork=0 EndWork=22 ......... [d2] Allowed=1 BeginWork=0 EndWork=22
そして、INIファイルを使用した動作ロジックの他の部分は、全く同様で、『サンドボックス』に配置する必要があり、それらの動作速度もかなり遅くなります(上記のCSV形式ファイルの動作時よりは少しは良いが)。初期化時や新しいローソク足が出現する時にこれらをCSVファイルのように扱うことをお勧めします。しかし、INIファイルはコメントに関する独自のルールを持っている為、INIファイルのテキスト内のコメントを貴方の好きなように配置することはできません。とは言っても、この事を考慮しても、ファイル形式の特性と関連したサポートドキュメントに何かしらの困難が生じることはありません。
この形式の欠点は、第三者のライブラリを接続する必要があるという点です。MQL言語群にはこのような形式ファイルを直接操作するツールがない為、kernell32.dllからインポートする必要があります。このサイトではこの操作を実行するいくつかのライブラリが提供されているので、さらに別のものを作成してここに挙げる意味はありません。全部で2つの関数をインポートするだけなので、このようなライブラリは簡単なものです。しかし、ライブラリはエラーを含んでいる可能性があり、全ての構成がLinux下で動作するものであるかどうかは不明であるということを覚えておく必要があります。第三者のライブラリの接続を恐れないユーザーは、記事で触れたCSV形式ファイルと同等のINIファイルの使用を歓迎するかもしれません。
INIファイルの他の方法もあります。それらを簡単に挙げてみましょう。
- レジストリの使用。私の見解では、これは非常に物議を醸す方法です。レジストリはオペレーションシステムの正常な動作の為に重要な部分です。そこにスクリプトやインディケータを書くのを許可することは正しいのだろうか?私は、これは戦略的に間違った結論だと思います。
- データベースの使用。これはすでに検証され、どんな量のどんなデータも保存できる信頼性の高い方法です。しかし、エキスパートアドバイザやインディケータに実際にこのような量のデータを保存する必要があるのか?ほとんどの場合、答えはノーです。データベースの機能は、私達のここでの目的を達成するには十分すぎる機能を持っていることは明らかです。データベースは、実際に数ギガバイトのデータを保存する必要ができた時には便利です。
- XMLの使用。これは同じテキストファイルですが、個別に習得する必要がある他の構文をもったものです。また、XMLファイルを操作するには、ライブラリを書く(または既製のものをダウンロードする)必要があります。しかし、ここには最も困難な課題があります。それは操作の終わりにサポートドキュメントを作成する必要があることです。開発者のその労力は価値のあるものなのでしょうか?このケースでは疑わしいと思います。
まとめ
結論として、テキストファイルはすでに使い慣れたものである(取引コピーのメカニズムなど)という点に注目したいと思います。MetaTraderのターミナル自体が、多種多様なテキストファイルを作成し使用しています。その中には設定ファイルや様々なログ、メール、テンプレートがあります。
記事では、エキスパートアドバイザやインディケータ、スクリプトなどのトレーディングツールの設定をするCSV形式の通常のテキストファイルとして、このような簡単なツールの実用を拡大する試みが行われました。いくつかの簡単な例では、このようなファイルを使用してどんな可能性を得ることができるかを見ていきました。可能な代替方法の検証と欠点を列挙しました。
いずれの場合にも、入力パラメータの保存方法の選択は、それぞれの場合のタスクに応じて行う必要があります。特定の問題を解決する方法の選択は、常に開発者の下に残り、他の要因の中のその選択の最適性は開発者の専門知識を表すものとなります。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2564





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