MQL5 クックブック:ポジションプロパティを取得するためのディール履歴と関数ライブラリ
はじめに
ポジションプロパティについて先行記事で提供されている情報を簡単にまとめます。本稿では、ディールヒストリーにアクセスした後にのみ取得可能なプロパティを得る関数を数個追加して作成します。また便利な方法でポジションやシンボルプロパティにアクセスできるようにするデータストラクチャについても知識を得ます。
存在する限りポジションボリュームが変わらないトレーディングシステムは実は本稿で提供される関数の使用は必要としません。ただもっと後の段階で資金管理システムの実装とトレーディング戦略でポジションロットサイズの管理を計画しているなら、これら関数は外せません。
始める前に本稿へのリンクをたどってきた読者の方、本ウェブサイトを初めて訪問するかMQL5 言語の学習を始めたばかりという場合には『MQL5クックブックシリーズ』の先行記事から始めることを提案します。
Expert Advisor の作成
先行記事"MQL5 Cookbook: How to Avoid Errors When Setting/Modifying Trade Levels"で変更した Expert Advisor の新しい関数の処理を確認するには、ポジションがすでに存在しながら、オープンのシグナルが再び発生する場合にポジションボリュームを増やす機能を追加します。
ポジション履歴には複数のディールがあるかもしれません。トレード中にポジションボリュームに変更が生じるようなら、現ポジション価格にも変更が行われるはずです。最初のエントリーポイント価格を見つけるためにはその指定ポジションに関するディール履歴にアクセスする必要があります。下図はポジションだけが1件のディール(エントリーポイント)を持つ場合を示しています。
図1 ポジションにおける最初のディール
次の図は二番目のディールに続くポジション価格の変化を示しています。
図2 ポジションにおける2番目のディール
先行記事で示しているように、標準識別子によって現ポジション価格(POSITION_PRICE_OPEN)およびポジションがオープンしているあるシンボルの現在価格(POSITION_PRICE_CURRENT)のみ取得可能です。
ただしトレーディングシステムの中には最初のエントリーポイントからの価格による距離、最後のディールの価格を知る必要があります。これら情報はすべてディール/オーダーのアカウント履歴で入手可能です。以下は前出の図に関するディールリストです。
図3 アカウントのディールヒストリー
これで状況は明確で目標は設定されたと思います。先行記事で取り上げられた Expert Advisor の変更を続けます。まずポジションプロパティの列挙に 0、6、9、12、16と番号付けされた新しい識別子を追加します。
//--- Enumeration of position properties enum ENUM_POSITION_PROPERTIES { P_TOTAL_DEALS = 0, P_SYMBOL = 1, P_MAGIC = 2, P_COMMENT = 3, P_SWAP = 4, P_COMMISSION = 5, P_PRICE_FIRST_DEAL= 6, P_PRICE_OPEN = 7, P_PRICE_CURRENT = 8, P_PRICE_LAST_DEAL = 9, P_PROFIT = 10, P_VOLUME = 11, P_INITIAL_VOLUME = 12, P_SL = 13, P_TP = 14, P_TIME = 15, P_DURATION = 16, P_ID = 17, P_TYPE = 18, P_ALL = 19 };
各プロパティに対するコメントはもう少し下で検討するストラクチャに付けられます。
外部パラメータの数を増やします。これで以下を指定することができます。
- MagicNumber -Expert Advisor のユニーク ID (マジックナンバー)
- Deviation -スリッページ
- VolumeIncrease -ポジションボリュームが増やされるポジション値
- InfoPanel -情報パネルの表示を有効化/無効化することのできるパラメータ
以下はその実装方法です。
//--- External parameters of the Expert Advisor sinput long MagicNumber=777; // Magic number sinput int Deviation=10; // Slippage input int NumberOfBars=2; // Number of Bullish/Bearish bars for a Buy/Sell input double Lot=0.1; // Lot input double VolumeIncrease=0.1; // Position volume increase input double StopLoss=50; // Stop Loss input double TakeProfit=100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse=true; // Position reversal sinput bool ShowInfoPanel=true; // Display of the info panel
sinput 修飾子が設定されたパラメータに注意してください。この修飾子はストラレジーテスタの最適化の無効を可能にします。実際、自己使用のためのプログラムを開発する際、どのパラメータが最終結果に影響を与えるか完璧に理解しています。そのためみなさんはただ最適化からチェックを外すのです。ただひじょうに大きな数のパラメータとなると、この方法ではそれらがグレー表示されるため視覚的にそれらをその他から分離することができます。
図4 最適化から無効化されるパラメータはグレー表示されます。
ポジションとシンボルプロパティ値を格納するグローバル変数をデータストラクチャ(struct)と置き換えます。
//--- Position properties struct position_properties { uint total_deals; // Number of deals bool exists; // Flag of presence/absence of an open position string symbol; // Symbol long magic; // Magic number string comment; // Comment double swap; // Swap double commission; // Commission double first_deal_price; // Price of the first deal in the position double price; // Current position price double current_price; // Current price of the position symbol double last_deal_price; // Price of the last deal in the position double profit; // Profit/Loss of the position double volume; // Current position volume double initial_volume; // Initial position volume double sl; // Stop Loss of the position double tp; // Take Profit of the position datetime time; // Position opening time ulong duration; // Position duration in seconds long id; // Position identifier ENUM_POSITION_TYPE type; // Position type };
//--- Symbol properties struct symbol_properties { int digits; // Number of decimal places in the price int spread; // Spread in points int stops_level; // Stops level double point; // Point value double ask; // Ask price double bid; // Bid price double volume_min; // Minimum volume for a deal double volume_max; // Maximum volume for a deal double volume_limit; // Maximum permissible volume for a position and orders in one direction double volume_step; // Minimum volume change step for a deal double offset; // Offset from the maximum possible price for a transaction double up_level; // Upper Stop level price double down_level; // Lower Stop level price }
ここで、ストラクチャの特定エレメントにアクセスするため、このストラクチャタイプの変数を作成する必要があります。手順は記事 "MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester"で考察されているトレードクラス用オブジェクトの作成に似ています。
//--- variables for position and symbol properties
position_properties pos;
symbol_properties symb;
クラスメソッドを処理するときと同じ方法でエレメントにアクセスすることができます。すなわち、その特定のストラクチャ内のエレメントリストを表示するためにストラクチャ変数名の後にドットと1個入れるだけです。これはひじょうに便利です。1行コメントがストラクチャフィールドに与えられている場合(われわれの例のように)、それらは右側のツールチップに表示されます。
図5 ストラクチャフィールドリスト
もう一つ重要なポイントです。Expert Advisorの変更において、実質的に多くの関数で使用されるグローバル変数をすべて変更しました。よってここではそれらをシンボルおよびポジションプロパティの対応するストラクチャフィールドと置き換える必要があります。たとえば、オープンポジションの在/不在のフラグを格納するのに使用したグローバル変数pos_open は position_properties ストラクチャタイプのexistsフィールドと置き換えられました。よってpos_open 変数が使用されるときはいつでもpos.existsと置き換えられる必要があります。
マニュアルでしなけらばならないと長く疲れるプロセスです。よって MetaEditor 機能を利用してこのタスクに対するソリューションを自動化するのが良いでしょう。編集 メニューまたはキーの組合せCtrl+H から検索と置換 -> 地検と進みます。
図6 テキストの検索と置換
ファイルをコンパイルしたら、のちに検証を行うためシンボルおよびポジションプロパティに対するグローバル変数をすべて検索し置き換える必要があります。エラーが検出されなけらば、すべてが正しく行われたということです。本稿が不必要に長くならないようにここでコードは表示しません。また既製のソースコードが本稿末尾でダウンロード可能です。
変数について整理できたところで、既存の関数を修正し新しい関数を作成していきます。
外部パラメータではこれでマジックナンバーとポイント表示のスリッページを設定することができます。また Expert Advisorのコードに適切な変更を加える必要があります。ユーザー定義の補助関数 OpenPosition()を作成します。ここにポジションオープンのオーダーを送信する前に CTrade クラスの関数を用いてプロパティをセットします。
//+------------------------------------------------------------------+ //| Opening a position | //+------------------------------------------------------------------+ void OpenPosition(double lot, ENUM_ORDER_TYPE order_type, double price, double sl, double tp, string comment) { trade.SetExpertMagicNumber(MagicNumber); // Set the magic number in the trading structure trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); // Set the slippage in points //--- If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } }
Expert Advisor のメインのトレード関数-TradingBlock()のコードに少し変更を加えるだけです。以下は変更が施された関数コードの一部です。
//--- If there is no position if(!pos.exists) { //--- Adjust the volume lot=CalculateLot(Lot); //--- Open a position OpenPosition(lot,order_type,position_open_price,sl,tp,comment); } //--- If there is a position else { //--- Get the position type GetPositionProperties(P_TYPE); //--- If the position is opposite to the signal and the position reversal is enabled if(pos.type==opposite_position_type && Reverse) { //--- Get the position volume GetPositionProperties(P_VOLUME); //--- Adjust the volume lot=pos.volume+CalculateLot(Lot); //--- Reverse the position OpenPosition(lot,order_type,position_open_price,sl,tp,comment); return; } //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume if(!(pos.type==opposite_position_type) && VolumeIncrease>0) { //--- Get the Stop Loss of the current position GetPositionProperties(P_SL); //--- Get the Take Profit of the current position GetPositionProperties(P_TP); //--- Adjust the volume lot=CalculateLot(Increase); //--- Increase the position volume OpenPosition(lot,order_type,position_open_price,pos.sl,pos.tp,comment); return; }
上記コードは現行ポジションの方向がシグナル方向に対してチェックされるブロックによって強化されました。その方向が一致しており、ポジションボリューム増加が外部パラメータ(VolumeIncreaseパラメータ値がゼロより大きい)可能であれば、既定のロットを確認/調整し、適切なオーダーを送信します。ここでオーダーのオープンまたはクローズ送信、あるいはポジションボリュームを増やすために必要なことはすべてコードの1行に書き込みます。
ディールの履歴からポジションプロパティを取得するための関数を作成します。現行ポジションでディール番号を返す関数 CurrentPositionTotalDeals() から始めます。
//+------------------------------------------------------------------+ //| Returning the number of deals in the current position | //+------------------------------------------------------------------+ uint CurrentPositionTotalDeals() { int total =0; // Total deals in the selected history list int count =0; // Counter of deals by the position symbol string deal_symbol =""; // symbol of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, increase the counter if(deal_symbol==_Symbol) count++; } } //--- return(count); }
上記コードにはかなり詳細なコメントが付けられています。ただ履歴の選択方法について少し解説が必要です。われわれの場合、HistorySelect() 関数を使用してオープン時刻によって決められる現在ポジションのオープン時点から現在時点までのリストを取得しました。履歴が選択されたら、 HistoryDealsTotal() 関数を用いてリスト上のディール数を見つけ出すことができます。その他はコメントから明らかでしょう。
特定ポジションの履歴は HistorySelectByPosition() を用いるとその識別子でも選択することができます。ここでポジション識別子はポジションが逆転しても変わらないことに配慮が必要です。それは Expert Advisorで時々起ることです。ただしポジションオープン時刻は逆転で変わり、そのためこのバリアントは実装がより簡単です。ですが現在オープンしているポジションに対してのみディールヒストリーが適用しなければ、識別子を使う必要があります。今後の記事でディールヒストリーの話題に戻りたいと思います。
ポジションの最初のディール価格、すなわちポジションがオープンしているときのディール価格を返す関数CurrentPositionFirstDealPrice() を作成を続けます。
//+------------------------------------------------------------------+ //| Returning the price of the first deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionFirstDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // symbol of the deal double deal_price =0.0; // Price of the deal datetime deal_time =NULL; // Time of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- Get the time of the deal deal_time=(datetime)HistoryDealGetInteger(HistoryDealGetTicket(i),DEAL_TIME); //--- If the time of the deal equals the position opening time, // and if the symbol of the deal and the current symbol are the same, exit the loop if(deal_time==pos.time && deal_symbol==_Symbol) break; } } //--- return(deal_price); }
ここでの原則は前出の関数のときと同じです。ポジションがオープンしている点からヒストリーを取得し、それからディール時刻と各反復でポジションオープンの時刻を確認します。ディール価格とともにシンボル名とディール時刻も取得します。一番最初のディールはディール時刻がポジションオープン時刻に一致するとき特定されます。その価格がすでに適切な変数に割り当てられているため必要なことは値を返すだけです。
先に進みます。現ポジションの最終ディール価格を取得することが必要なこともあります。このためには CurrentPositionLastDealPrice() 関数を作成します。
//+------------------------------------------------------------------+ //| Returning the price of the last deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionLastDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // Symbol of the deal double deal_price =0.0; // Price //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, exit the loop if(deal_symbol==_Symbol) break; } } //--- return(deal_price); }
今回、ループはリストにある最終ディールで始まり、よくあるのは必要なディールがループの最初の反復で特定されるというものです。ですが複数シンボルでトレードをするなら、ループはディールシンボルが現シンボルに一致するまでループを続けます。
現ポジションボリュームは標準識別子 POSITION_VOLUME を用いて取得することができます。最初のポジションボリューム(1番目のディールのボリューム)を見つけるには CurrentPositionInitialVolume() 関数を作成します。
//+------------------------------------------------------------------+ //| Returning the initial volume of the current position | //+------------------------------------------------------------------+ double CurrentPositionInitialVolume() { int total =0; // Total deals in the selected history list ulong ticket =0; // Ticket of the deal ENUM_DEAL_ENTRY deal_entry =WRONG_VALUE; // Position modification method bool inout =false; // Flag of position reversal double sum_volume =0.0; // Counter of the aggregate volume of all deals, except for the first one double deal_volume =0.0; // Volume of the deal string deal_symbol =""; // Symbol of the deal datetime deal_time =NULL; // Deal execution time //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- If the order ticket by its position is obtained, then... if((ticket=HistoryDealGetTicket(i))>0) { //--- Get the volume of the deal deal_volume=HistoryDealGetDouble(ticket,DEAL_VOLUME); //--- Get the position modification method deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); //--- Get the deal execution time deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); //--- When the deal execution time is less than or equal to the position opening time, exit the loop if(deal_time<=pos.time) break; //--- otherwise calculate the aggregate volume of deals by the position symbol, except for the first one if(deal_symbol==_Symbol) sum_volume+=deal_volume; } } } //--- If the position modification method is a reversal if(deal_entry==DEAL_ENTRY_INOUT) { //--- If the position volume has been increased/decreased // I.e. the number of deals is more than one if(fabs(sum_volume)>0) { //--- Current volume minus the volume of all deals except for the first one double result=pos.volume-sum_volume; //--- If the resulting value is greater than zero, return the result, otherwise return the current position volume deal_volume=result>0 ? result : pos.volume; } //--- If there are no more deals, other than the entry, if(sum_volume==0) deal_volume=pos.volume; // return the current position volume } //--- Return the initial position volume return(NormalizeDouble(deal_volume,2)); }
この関数はその前の関数よりも複雑に表現されています。私は誤った値になる状況として可能なものすべてを考慮するようにしてきました。注意深い検証により問題は何も出ませんでした。コードにつけられている詳細コメントは重要事項を理解するのに役立つはずです。
ポジション継続時間を返す関数も便利です。ユーザーが秒、分、時間、日など戻り値の適切なフォーマットを選択できるように作成します。このためもう一つ別の列挙を作成します。
//--- Position duration enum ENUM_POSITION_DURATION { DAYS = 0, // Days HOURS = 1, // Hours MINUTES = 2, // Minutes SECONDS = 3 // Seconds };
以下は適切な計算をすべて行う関数 CurrentPositionDuration() のコードです。
//+------------------------------------------------------------------+ //| Returning the duration of the current position | //+------------------------------------------------------------------+ ulong CurrentPositionDuration(ENUM_POSITION_DURATION mode) { ulong result=0; // End result ulong seconds=0; // Number of seconds //--- Calculate the position duration in seconds seconds=TimeCurrent()-pos.time; //--- switch(mode) { case DAYS : result=seconds/(60*60*24); break; // Calculate the number of days case HOURS : result=seconds/(60*60); break; // Calculate the number of hours case MINUTES : result=seconds/60; break; // Calculate the number of minutes case SECONDS : result=seconds; break; // No calculations (number of seconds) //--- default : Print(__FUNCTION__,"(): Unknown duration mode passed!"); return(0); } //--- Return result return(result); }
ではポジションプロパティが表示される情報パネルについての関数 CurrentPositionDurationToString() を作成します。この関数は秒で表されているポジション継続時間をユーザーが理解しやすいフォーマットに変換します。秒数は関数に渡され、関数は日数、時間数、分、秒でポジション継続時間を持つ文字列を返します。
//+------------------------------------------------------------------+ //| Converting the position duration to a string | //+------------------------------------------------------------------+ string CurrentPositionDurationToString(ulong time) { //--- A dash if there is no position string result="-"; //--- If the position exists if(pos.exists) { //--- Variables for calculation results ulong days=0; ulong hours=0; ulong minutes=0; ulong seconds=0; //--- seconds=time%60; time/=60; //--- minutes=time%60; time/=60; //--- hours=time%24; time/=24; //--- days=time; //--- Generate a string in the specified format DD:HH:MM:SS result=StringFormat("%02u d: %02u h : %02u m : %02u s",days,hours,minutes,seconds); } //--- Return result return(result); }
これですべて準備が整いました。上記の変更すべてに従って修正が必要な関数 GetPositionProperties() および GetPropertyValue() は提供しません。シリーズの先行記事をお読みになればご自身でこれを行うのは難しくないはずです。いずれにせよソースコードファイルは本稿に添付されています。
結果、情報パネルは以下のように表示されます。
図7 情報パネル上の全ポジションプロパティ表示
これでポジションプロパティを取得するための関数ライブラリを取得しました。続きは必要に応じて今後の記事にゆだねることとなるでしょう。
パラメータの最適化と Expert Advisorの検証
実験的に Expert Advisorのパラメータを最適化してみましょう。今入手したものはまだ完全に機能するトレーディングシステムとは言えませんが、達成する結果は何かについてわれわれの目を開けてくれ、トレーディングシステム開発者としての経験を高めてくれることでしょう。
ストラテジーテスタの設定は以下のように行います。
図8 パラメータを最適化のためのストラテジーテスタ設定
Expert Advisor の外部パラメータ設定は以下のようなものとなります。
図9 最適化のためのExpert Advisor のパラメータ設定
最適化に続き最大リカバリーファクターによりアーカイブされた結果をソートします。
図10 最大リカバリーファクターによりソートされた結果
リカバリーファクター値が4.07であるパラメータの一番上のセットを検証します。最適化は EURUSDに対して行われましたが、多くのシンボルにたいして結果はポジティブであることが確認できます。
EURUSDに対する結果
図11 EURUSDに対する結果
AUDUSDに対する結果
図12 AUDUSDに対する結果
NZDUSDに対する結果
図13 NZDUSDに対する結果
おわりに
実際どのような考えも作成し強化することが可能です。トレーディングシステムは不良品として却下する前にどれもひじょうに注意深く検証する必要があります。後の記事でほとんどあらゆるトレーディングシステムをカスタマイズしそこに取り入れるひじょうに役立つさまざまなメカニズムとスキームを見ていくつもりです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/644
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索