MQL5 クックブック:MetaTrader 5 ストラレジーテスタでの position プロパティ分析
はじめに
本稿では先行記事 "MQL5 Cookbook: Position Properties on the Custom Info Panel"で作成した Expert Advisor を変更し、以下の問題に取り組みます。
- 現シンボルにおける新規バーイベント確認
- バーからのデータ取得
- ファイルに標準ライブラリのトレードクラスのインクルード
- トレードシグナル検索のための関数作成
- トレード処理実行用関数作成
- OnTrade()関数におけるトレードイベント決定
実際、上記問題はそれぞれ1件の記事に相当するものですが、試験ではそのようは方法は言語の調査をいたずらに複雑化するものだと思います。
ここではそういった機能を実装するシンプルな例を挙げていきます。言い換えれば、先にリストアップしたタスクそれぞれの実装は1件のシンプルで簡単な関数に文字通り収まるものです。シリーズの今後の記事への特定の考えを発展させると、必要に応じて目の前のタスクが要求する程度まで次第にこれら関数はより複雑なものにしてしまいます。
まず、先行記事から Expert Advisor をコピーします。そこの関数がすべて必要だからです。
Expert Advisorの作成
「標準ライブラリ」からわれわれのファイルに CTrade classをインクルードします。このクラスはトレード処理を実行するのに必要な関数をすべて備えています。まず簡単にそれらを使うことができます。中身を見るまでもなく使おうとしています。
このクラスをインクルードするためには以下を書く必要があります。
//--- Include a class of the Standard Library #include <Trade/Trade.mqh>
このコードはファイルの冒頭に入れ、のちに見つけやすいようにしておきます。たとえば #define 命令の後に入れるとよいでしょう。#include コマンドは Trade.mqh ファイルが<MetaTrader 5 terminal directory>\MQL5\Include\Trade\から取り込まれる必要があることを表しています。同じ方法で関数を持つその他ファイルをインクルードすることができます。プロジェクトコード量が大きくなり中を検索するのが難しくなるとき、これは特に便利です。
ここですべての関数にアクセスできるようクラスのインスタンスを作成します。これはクラス名の後にインスタンス名を書くことで行います。
//--- Load the class
CTrade trade;
Expert Advisorのこのバージョンでは CTrade クラスで利用可能な関数すべてのうち、トレード関数を1件だけ使用していきます。それはポジションオープンに使用される PositionOpen() 関数です。それはまた既存のオープンポジションをリバースするのにも使用可能です。クラスからのこの関数の呼びだし方はトレード処理を実行する関数作成をするとき本稿で後にお伝えします。
さらにグローバルスコープにおいて動的配列を2個追加します。この2つの配列はバーの値を付け足します。
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar)
次に CheckNewBar() 関数を作成します。これを用いてプログラムは新規バーイベントを確認します。これはトイレード処理は完了バーでしか実行されないためです。
以下は詳しいコメントを付けた CheckNewBar() 関数のコードです。
//+------------------------------------------------------------------+ //| CHECKING FOR THE NEW BAR | //+------------------------------------------------------------------+ bool CheckNewBar() { //--- Variable for storing the opening time of the current bar static datetime new_bar=NULL; //--- Array for getting the opening time of the current bar static datetime time_last_bar[1]={0}; //--- Get the opening time of the current bar // If an error occurred when getting the time, print the relevant message if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1) { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); } //--- If this is a first function call if(new_bar==NULL) { // Set the time new_bar=time_last_bar[0]; Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"][" +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]"); return(false); // Return false and exit } //--- If the time is different if(new_bar!=time_last_bar[0]) { new_bar=time_last_bar[0]; // Set the time and exit return(true); // Store the time and return true } //--- If we have reached this line, then the bar is not new, return false return(false); }
上記コードにあるように、バーが新規であれば CheckNewBar() 関数は true を、または新規バーがまだなければ false を返します。完了バーでトレード処理が実行される場合に限り、このようにトレード/検証時状態を管理することができます。
関数の冒頭では static 変数とdatetime タイプの静的配列を宣言します。静的なローカル変数はその値を関数実行後も保持します。続いて関数が呼ばれるたびにそのようなローカル変数は関数が前回呼ばれたときに取得した値を持ちます。
のちに CopyTime() 関数を書きます。それは time_last_bar 配列の最終バー時刻を取得するのに役立ちます。MQL5 参考資料で必ずこの関数の構文を確認してください。
またこの記事のシリーズでこれまで取り上げられたことのないユーザー定義関数 TimeframeToString() も記します。この関数はタイムフレーム値をユーザーに解りやすいの次列に変換します。
string TimeframeToString(ENUM_TIMEFRAMES timeframe) { string str=""; //--- If the passed value is incorrect, take the time frame of the current chart if(timeframe==WRONG_VALUE || timeframe == NULL) timeframe = Period(); switch(timeframe) { case PERIOD_M1 : str="M1"; break; case PERIOD_M2 : str="M2"; break; case PERIOD_M3 : str="M3"; break; case PERIOD_M4 : str="M4"; break; case PERIOD_M5 : str="M5"; break; case PERIOD_M6 : str="M6"; break; case PERIOD_M10 : str="M10"; break; case PERIOD_M12 : str="M12"; break; case PERIOD_M15 : str="M15"; break; case PERIOD_M20 : str="M20"; break; case PERIOD_M30 : str="M30"; break; case PERIOD_H1 : str="H1"; break; case PERIOD_H2 : str="H2"; break; case PERIOD_H3 : str="H3"; break; case PERIOD_H4 : str="H4"; break; case PERIOD_H6 : str="H6"; break; case PERIOD_H8 : str="H8"; break; case PERIOD_H12 : str="H12"; break; case PERIOD_D1 : str="D1"; break; case PERIOD_W1 : str="W1"; break; case PERIOD_MN1 : str="MN1"; break; } //--- return(str); }
CheckNewBar() 関数の使用方法は本稿でのちに必要な関数がすべて準備できたときにお伝えします。バーの依頼番号値を取る GetBarsData() 関数を見ていきます。
//+------------------------------------------------------------------+ //| GETTING BAR VALUES | //+------------------------------------------------------------------+ void GetBarsData() { //--- Number of bars for getting their data in an array int amount=2; //--- Reverse the time series ... 3 2 1 0 ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); //--- Get the closing price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the opening price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
上記コードを詳しく調べます。まず、数量 変数ではデータを取得する必要のあるバー番号を指定します。それから最終バー(現在)が配列のゼロ番目のインデックスになるようにオーダーにインデックスを付ける配列を設定します。これは ArraySetAsSeries() 関数を用いて行います。たとえば、計算の最終バーの値を使いたい場合、それは次のように書くことができます。始値を例にとると: open_price[0]。2番目から最後のバーまでの表記も同様に:open_price[1]となります。
終値と始値を取得するメカニズムは最終バーの時刻を取得した CheckNewBar() 関数のメカニズムに似ています。今回の場合、関数 CopyClose() および CopyOpen() を使用するだけです。同様に関数 CopyHigh() と CopyLow() はバー価格のそれぞれ最高値と最低値を取得するのに使われます。
先に進んでポジションオープン/リバースシグナルの判断の仕方を示すひじょうにシンプルな例を考察します。価格配列は2本のバー(現在バーと前回の完了バー)についてのデータを格納します。完了バーからのデータを使用します。
- 終値が始値を上回るとき(ブルバー)買いシグナル が発生します。
- 終値が始値を下回るとき(ベアバー)売りシグナル が発生します。
以下はこれらシンプルな条件を実装するコードです。
//+------------------------------------------------------------------+ //| DETERMINING TRADING SIGNALS | //+------------------------------------------------------------------+ int GetTradingSignal() { //--- A Buy signal (0) : if(close_price[1]>open_price[1]) return(0); //--- A Sell signal (1) : if(close_price[1]<open_price[1]) return(1); //--- No signal (3): return(3); }
ごらんのようにひじょうにシンプルです。同様の方法でもっと複雑な条件の処理方法も簡単に見つけ出すことができます。この関数は完了バーが上向きだればゼロ、下向きであれば1を返します。なんらかの理由でシグナル不在の場合は 3を返します。
ここではトレードの活動を実装するための TradingBlock() 関数を作成するだけです。以下は詳しいコメントをつけたこの関数のコードです。
//+------------------------------------------------------------------+ //| TRADING BLOCK | //+------------------------------------------------------------------+ void TradingBlock() { int signal=-1; // Variable for getting a signal string comment="hello :)"; // Position comment double start_lot=0.1; // Initial volume of a position double lot=0.0; // Volume for position calculation in case of reverse position double ask=0.0; // Ask price double bid=0.0; // Bid price //--- Get a signal signal=GetTradingSignal(); //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- If it is a Buy signal if(signal==0) { //--- Get the Ask price ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment)) { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a SELL position if(pos_type==POSITION_TYPE_SELL) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } //--- If there is a Sell signal if(signal==1) { //-- Get the Bid price bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a BUY position if(pos_type==POSITION_TYPE_BUY) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } }
ポジションのオープンについてはすべて明確になったと思います。上記コードにあるように、 (trade) ポインターにはドットが1個続き、今度そのドットには PositionOpen() メソッドが続きます。これはクラスから特定メソッドを呼ぶ方法です。ドットを付けたら、全クラスメソッドを持つリストを確認します。必要なことはリストから必要なメソッドを選択することだけです。
図1 クラスメソッドの呼び出し
TradingBlock() 関数には主要ブロックが2つあります。買いと売りです。シグナル方向を判断したらすぐあとに「買い」シグナルの場合には買い値と「売り」シグナルの場合売り価格を取得します。
トレードオーダーで使用されるすべての価格/レベルは NormalizeDouble() 関数を使って正常化する必要があります。そうしなければポジションのオープンや変更をしようとするとエラーを招いてしまいます。この関数はまたロット計算にも使用がおすすめです。また、パラメータストップロス と テイクプロフィット はゼロ値であることに注意してください。トレードレベル設定に関するより詳しい情報はシリーズの次の記事で提供します。
これでユーザー定義関数はすべて準備完了で、それらを正しい順序で整理することができます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get position properties and update the values on the panel GetPositionProperties(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If the bar is not new, exit if(!CheckNewBar()) return; //--- If there is a new bar else { GetBarsData(); // Get bar data TradingBlock(); // Check the conditions and trade } //--- Get the properties and update the values on the panel GetPositionProperties(); }
考察することがもう一つだけ残っています。それはOnTrade()関数を持ち手トレードイベントを決定することです。ここではそれについて一般的考えを提供するてめに簡単に触れるにとどめます。われわれの場合、次のシナリオを取り入れます。ポジションをマニュアルでオープン/クローズ/変更する場合、情報パネルの プロパティリスト上の値は新規ティックの受取時ではなくポジションが完了されるとすぐに更新される必要がある。このためには以下のコードを追加するだけです。
//+------------------------------------------------------------------+ //| TRADE EVENT | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(); }
基本的にすべて準備できたので、検証に進みます。ストラテジーテスタにより可視化モードで迅速に検証を実行し、エラーがあればそれを見つけることが可能です。ストラテジーテスタを使うと、マーケットが閉まっている週末でもプログラム作成を続けることができるというメリットもあります。
ストラテジーテスタを設定し、可視化モードを有効にし、開始するをクリックします。Expert Advisor はストラテジーテスタでトレードを開始し、みなさんは以下に表示しているのと似た描画を確認することとなります。
図2 MetaTrader 5 ストラテジーテスタの可視化モード
F12を押すことで可視化モードでの検証はいつでも中断し、順を追って検証を続けることができます。ストラテジーテスタを始値のみに設定しれいれば ステップはバー1本と同じです。また、 ティックごとモードに背亭しれいればティック1個と同じです。検証スピードを制御することもできます。
情報パネル上の値がマニュアルでのポジションオープン/クローズ、または ストップロスレベル、テイクプロフィットレベルの追加/変更の直後に更新されることを確認するには 現実の時間モードでExpert Advisor を検証する必要があります。あまり長く待ちすぎず、トレード処理が毎分行われるように1分足のタイムフレームで Expert Advisor を実行します。
その他、私は情報パネル上の position プロパティ名に対し別の配列を追加しました。
// Array of position property names string pos_prop_texts[INFOPANEL_SIZE]= { "Symbol :", "Magic Number :", "Comment :", "Swap :", "Commission :", "Open Price :", "Current Price :", "Profit :", "Volume :", "Stop Loss :", "Take Profit :", "Time :", "Identifier :", "Type :" };
先行記事ではこの配列は SetInfoPanel() 関数コードを減らすために必要になるだろうと申しました。みなさんがまだそれを実装していない、またはご自身で見つけていないからそれがどのように行われるかここで確認することができます。以下は position プロパティに関するオブジェクトを作成するリストの新たな実装です。
//--- List of the names of position properties and their values for(int i=0; i<INFOPANEL_SIZE; i++) { //--- Property name CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2); //--- Property value CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2); }
SetInfoPanel() 関数の初めで以下の行に注意します。
//--- Testing in the visualization mode if(MQL5InfoInteger(MQL5_VISUAL_MODE)) { y_bg=2; y_property=16; }
プログラムが現在可視化モードで検証中であれば、情報パネル上のオブジェクトのY軸の調整が必要なプログラムに伝達します。これはストラテジーテスタの可視化モードでの検証時には Expert Advisor 名が実践時のようにチャートの右上角に表示されないためです。そのため不要なインデントは削除可能です。
おわりに
いまのところ以上です。次稿ではトレードレベルの設定と変更に着目します。以下から Expert Advisor PositionPropertiesTesterEN.mq5のソースコードをダウンロードすることができます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/642
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索