
MQL5 Cookbook:トレードレベルを設定/変更する際エラーを避ける方法
はじめに
シリーズの前稿 "MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester" からの Expert Advisor への取り組みの続編として、既存の関数を改良、最適化しつつ有用な関数を数多く用いて Expert Advisor を強化していきます。
MQL プログラミングフォーラムでは、トレードレベル(ストップロス、テイクプロフィット、指値注文)を設定/変更する際生じるエラーに関する問い合わせが 初心者から頻繁に寄せられます。みなさんの多くはすでに [Invalid stops]で終了するジャーナルメッセージをよくご存じだと思います。本稿では、ポジションをオープン/変更する前の正確性を保つためトレードレベル値を正常化し確認する関数を作成します。
今回 Expert Advisor は MetaTrader 5 「ストラテジーテスタ」で最適化可能な外部パラメータを取得し、いくつかの点でシンプルなトレーディングシステムのようになります。ほんもののトレーディングシステムを開発するにはまだまだ長い道のりであることは確かです。しかし、ローマは一日にしてならず、です。というわけですることは満載です。
本稿では初めに既存関数におけるコードの最適化を考察します。情報パネルはこの時点では取り上げません。標準識別子を用いて取得できないポジションプロパティについてまだ見ていく必要があるためです(ディールの履歴使用が求められます)。この話題はシリーズの今後の記事で取り上げることとなるでしょう。
Expert Advisor の開発
では始めましょう。従来通り、ファイルの初めに追加の列挙、変数、配列、予備関数を挿入することで始めます。シンボルプロパティを簡単に取得できる関数が必要となります。ポジションプロパティを取得するにも同じシンプルな方法が必要となります。
前稿にグローバル変数が GetPositionProperties 関数内にすべてのポジションプロパティを一度に割り当てるとありました。今回は各プロパティを個別に取得する機能を持たせたいと思います。以下が前述の実装への列挙 2件 です。関数それ自体はもう少し後で再考します。
//--- Enumeration of position properties enum ENUM_POSITION_PROPERTIES { P_SYMBOL = 0, P_MAGIC = 1, P_COMMENT = 2, P_SWAP = 3, P_COMMISSION = 4, P_PRICE_OPEN = 5, P_PRICE_CURRENT = 6, P_PROFIT = 7, P_VOLUME = 8, P_SL = 9, P_TP = 10, P_TIME = 11, P_ID = 12, P_TYPE = 13, P_ALL = 14 }; //--- Enumeration of symbol properties enum ENUM_SYMBOL_PROPERTIES { S_DIGITS = 0, S_SPREAD = 1, S_STOPSLEVEL = 2, S_POINT = 3, S_ASK = 4, S_BID = 5, S_VOLUME_MIN = 6, S_VOLUME_MAX = 7, S_VOLUME_LIMIT = 8, S_VOLUME_STEP = 9, S_FILTER = 10, S_UP_LEVEL = 11, S_DOWN_LEVEL = 12, S_ALL = 13 };
列挙 ENUM_SYMBOL_PROPERTIES にはシンボルプロパティがすべて含まれているわけではありませんが、いつでも必要に応じて追加が可能です。列挙にはシンボルプロパティを基に計算されるユーザー定義プロパティ(10、11、12)も含みます。ポジションプロパティの列挙で行われるのと同じように、列挙から一度にプロパティをすべて取得するのに使うことのできる識別子があります。
そしてそれに Expert Advisor の外部パラメータが続きます。
//--- External parameters of the Expert Advisor input int NumberOfBars=2; // Number of Bullish/Bearish bars for a Buy/Sell input double Lot =0.1; // Lot input double StopLoss =50; // Stop Loss input double TakeProfit =100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse =true; // Position reverse
外部パラメータを詳しくみていきます。
- NumberOfBars -ポジションをオープンするバーの一方向の番号を設定します。
- Lot -ポジションボリューム
- TakeProfit -ポイントでのテイクプロフィットゼロ値はテイクプロフィットの設定が必要であることを意味します。
- StopLoss-ポイントでのストップロスゼロ値はストップロスの設定が不要であることを意味します。
- TrailingStop -ポイントでのトレーリングストップ「買い」ポジションに対しては、計算はバーの最小値を基にします(最長値- StopLoss パラメータからのポイントの番号)。「買い」ポジションに対しては、計算はバーの最大値を基にします(最大値+ StopLoss パラメータからのポイントの番号)。ゼロ値はトレーリングストップがオフであることを示します。
- Reverse は逆ポジションを有効化/無効化します。
より明確化が必要なのは NumberOfBars パラメータだけです。このパラメータ値を、たとえば、5以上に設定しても意味はありません。これはひじょうに稀でそのような設定の後でポジションを開いたのではすでに遅いからです。そこで必要なのは、このパラメータ値を調整するのに役立つ変数です。
//--- To check the value of the NumberOfBars external parameter int AllowedNumberOfBars=0;
このパラメータはまた価格配列に格納されるバーデータの分量を判断します。これはカスタム関数を変更するようになったらお話します。
ポジションプロパティにはよくあることですが、どの関数からでもアクセス可能なようにシンボルプロパティについてのグローバルスコープ内で変数を宣言します。
//--- Symbol properties int sym_digits=0; // Number of decimal places int sym_spread=0; // Spread in points int sym_stops_level=0; // Stops level double sym_point=0.0; // Point value double sym_ask=0.0; // Ask price double sym_bid=0.0; // Bid price double sym_volume_min=0.0; // Minimum volume for a deal double sym_volume_max=0.0; // Maximum volume for a deal double sym_volume_limit=0.0; // Maximum permissible volume for a position and orders in one direction double sym_volume_step=0.0; // Minimum volume change step for a deal double sym_offset=0.0; // Offset from the maximum possible price for a transaction double sym_up_level=0.0; // Upper Stop level price double sym_down_level=0.0; // Lower Stop level price
トレーリングストップはバーの最大値と最小値を基に計算されるため、そのようなバーデータに関する配列が必要となります。
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar) double high_price[]; // High (bar's highs) double low_price[]; // Low (bar's lows)
次に関数の変更および作成に進みます。すでにバー価格のオープンまたクローズを価格配列にコピーする関数 GetBarsData は取得済みです。ここではまた高値および安値が必要です。その上、 NumberOfBars パラメータから取得される値には調整の必要があります。以下が変更後の関数記述です。
//+------------------------------------------------------------------+ //| Getting bar values | //+------------------------------------------------------------------+ void GetBarsData() { //--- Adjust the number of bars for the position opening condition if(NumberOfBars<=1) AllowedNumberOfBars=2; // At least two bars are required if(NumberOfBars>=5) AllowedNumberOfBars=5; // but no more than 5 else AllowedNumberOfBars=NumberOfBars+1; // and always more by one //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); ArraySetAsSeries(high_price,true); ArraySetAsSeries(low_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,AllowedNumberOfBars,close_price)<AllowedNumberOfBars) { 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,AllowedNumberOfBars,open_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the bar's high // If the number of the obtained values is less than requested, print the relevant message if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the High price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the bar's low // If the number of the obtained values is less than requested, print the relevant message if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Low price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
最低バーを2本とつねに複数バーを必要とする条件があります。それは指数 [1]で始まる完全なバーだけを判断の基準とするためです。事実、バーデータが関数 CopyOpen、CopyClose、CopyHigh、CopyLow の第三パラメータで指定される指数から始まってコピーされるため、この場合調整は不要とされる可能性があります。バー5本の限度も自由な判断で変更(上昇/下降)可能です。
いまやGetTradingSignal 関数はやや複雑なものとなりました。これは条件が NumberOfBars パラメータで指定されるバーの番号によって異なって作成されるためです。またここで戻り値のより正確なタイプ、order typeを使用します。
//+------------------------------------------------------------------+ //| Determining trading signals | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- A Buy signal (ORDER_TYPE_BUY) : if(AllowedNumberOfBars==2 && close_price[1]>open_price[1]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==3 && close_price[1]>open_price[1] && close_price[2]>open_price[2]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==4 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==5 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars>=6 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4] && close_price[5]>open_price[5]) return(ORDER_TYPE_BUY); //--- A Sell signal (ORDER_TYPE_SELL) : if(AllowedNumberOfBars==2 && close_price[1]<open_price[1]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==3 && close_price[1]<open_price[1] && close_price[2]<open_price[2]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==4 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==5 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars>=6 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4] && close_price[5]<open_price[5]) return(ORDER_TYPE_SELL); //--- No signal (WRONG_VALUE): return(WRONG_VALUE); }
それでは GetPositionProperties 関数を変更しましょう。前稿では、この関数で一度にすべてのプロパティを取得することができました。ですがプロパティを1つだけ必要だということもあることでしょう。そのためにもちろん言語で提供される標準関数を使うことができます。がこれは思うほど便利でないものです。以下は修正した GetPositionProperties 関数のコードです。列挙 ENUM_POSITION_PROPERTIES から特定の識別子を渡すとき、ポジションプロパティを1いつだけ取得するか、または一度にすべてのプロパティを取得するかのどちらかが可能です。
//+------------------------------------------------------------------+ //| Getting position properties | //+------------------------------------------------------------------+ void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property) { //--- Check if there is an open position pos_open=PositionSelect(_Symbol); //--- If an open position exists, get its properties if(pos_open) { switch(position_property) { case P_SYMBOL : pos_symbol=PositionGetString(POSITION_SYMBOL); break; case P_MAGIC : pos_magic=PositionGetInteger(POSITION_MAGIC); break; case P_COMMENT : pos_comment=PositionGetString(POSITION_COMMENT); break; case P_SWAP : pos_swap=PositionGetDouble(POSITION_SWAP); break; case P_COMMISSION : pos_commission=PositionGetDouble(POSITION_COMMISSION); break; case P_PRICE_OPEN : pos_price=PositionGetDouble(POSITION_PRICE_OPEN); break; case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); break; case P_PROFIT : pos_profit=PositionGetDouble(POSITION_PROFIT); break; case P_VOLUME : pos_volume=PositionGetDouble(POSITION_VOLUME); break; case P_SL : pos_sl=PositionGetDouble(POSITION_SL); break; case P_TP : pos_tp=PositionGetDouble(POSITION_TP); break; case P_TIME : pos_time=(datetime)PositionGetInteger(POSITION_TIME); break; case P_ID : pos_id=PositionGetInteger(POSITION_IDENTIFIER); break; case P_TYPE : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; case P_ALL : pos_symbol=PositionGetString(POSITION_SYMBOL); pos_magic=PositionGetInteger(POSITION_MAGIC); pos_comment=PositionGetString(POSITION_COMMENT); pos_swap=PositionGetDouble(POSITION_SWAP); pos_commission=PositionGetDouble(POSITION_COMMISSION); pos_price=PositionGetDouble(POSITION_PRICE_OPEN); pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); pos_profit=PositionGetDouble(POSITION_PROFIT); pos_volume=PositionGetDouble(POSITION_VOLUME); pos_sl=PositionGetDouble(POSITION_SL); pos_tp=PositionGetDouble(POSITION_TP); pos_time=(datetime)PositionGetInteger(POSITION_TIME); pos_id=PositionGetInteger(POSITION_IDENTIFIER); pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; default: Print("The passed position property is not listed in the enumeration!"); return; } } //--- If there is no open position, zero out variables for position properties else ZeroPositionProperties(); }
同様に、シンボルプロパティを取得するのに GetSymbolProperties 関数を実装します。
//+------------------------------------------------------------------+ //| Getting symbol properties | //+------------------------------------------------------------------+ void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property) { int lot_offset=1; // Number of points for the offset from the Stops level //--- switch(symbol_property) { case S_DIGITS : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); break; case S_SPREAD : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); break; case S_STOPSLEVEL : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); break; case S_POINT : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); break; //--- case S_ASK : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); break; case S_BID : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); break; //--- case S_VOLUME_MIN : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); break; case S_VOLUME_MAX : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); break; case S_VOLUME_LIMIT : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); break; case S_VOLUME_STEP : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); break; //--- case S_FILTER : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); break; //--- case S_UP_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); break; //--- case S_DOWN_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- case S_ALL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- default: Print("The passed symbol property is not listed in the enumeration!"); return; } }
シンボルプロパティの中にはまず他のプロパティを取得することを要求するものもあることに注意が必要です。
新しい関数 CorrectValueBySymbolDigitsを取得しました。これは価格内の小数点以下の桁数に応じて適切な値を返します。この関数には整数または実数を渡すことができます。渡されたデータタイプが使用する関数のバージョンを判断します。この機能は 関数オーバーロードと呼ばれます。
//+------------------------------------------------------------------+ //| Adjusting the value based on the number of digits in the price (int)| //+------------------------------------------------------------------+ int CorrectValueBySymbolDigits(int value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; } //+------------------------------------------------------------------+ //| Adjusting the value based on the number of digits in the price (double)| //+------------------------------------------------------------------+ double CorrectValueBySymbolDigits(double value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; }
われわれの Expert Advisor はオープンするポジションのボリューム(Lot)を指定するための外部パラメータを持ちます。シンボル仕様、CalculateLotに従ってロットを調整する関数を作成します。
//+------------------------------------------------------------------+ //| Calculating position lot | //+------------------------------------------------------------------+ double CalculateLot(double lot) { //--- To adjust as per the step double corrected_lot=0.0; //--- GetSymbolProperties(S_VOLUME_MIN); // Get the minimum possible lot GetSymbolProperties(S_VOLUME_MAX); // Get the maximum possible lot GetSymbolProperties(S_VOLUME_STEP); // Get the lot increase/decrease step //--- Adjust as per the lot step corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step; //--- If less than the minimum, return the minimum if(corrected_lot<sym_volume_min) return(NormalizeDouble(sym_volume_min,2)); //--- If greater than the maximum, return the maximum if(corrected_lot>sym_volume_max) return(NormalizeDouble(sym_volume_max,2)); //--- return(NormalizeDouble(corrected_lot,2)); }
本稿のタイトルに直接かかわる関数に進みます。それらはひじょうにシンプルで簡単です。コード内のコメントから問題なくその目的を理解することができます。
CalculateTakeProfit 関数はテイクプロフィット値を計算するのに使用されます。
//+------------------------------------------------------------------+ //| Calculating the Take Profit value | //+------------------------------------------------------------------+ double CalculateTakeProfit(ENUM_ORDER_TYPE order_type) { //--- If Take Profit is required if(TakeProfit>0) { //--- For the calculated Take Profit value double tp=0.0; //--- If you need to calculate the value for a SELL position if(order_type==ORDER_TYPE_SELL) { //--- Calculate the level tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Return the calculated value if it is lower than the lower limit of the Stops level // If the value is higher or equal, return the adjusted value return(tp<sym_down_level ? tp : sym_down_level-sym_offset); } //--- If you need to calculate the value for a BUY position if(order_type==ORDER_TYPE_BUY) { //--- Calculate the level tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Return the calculated value if it is higher that the upper limit of the Stops level // If the value is lower or equal, return the adjusted value return(tp>sym_up_level ? tp : sym_up_level+sym_offset); } } //--- return(0.0); }
CalculateStopLoss 関数はストップロス値を計算するのに使用されます。
//+------------------------------------------------------------------+ //| Calculating the Stop Loss value | //+------------------------------------------------------------------+ double CalculateStopLoss(ENUM_ORDER_TYPE order_type) { //--- If Stop Loss is required if(StopLoss>0) { //--- For the calculated Stop Loss value double sl=0.0; //--- If you need to calculate the value for a BUY position if(order_type==ORDER_TYPE_BUY) { // Calculate the level sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Return the calculated value if it is lower that the lower limit of the Stops level // If the value is higher or equal, return the adjusted value return(sl<sym_down_level ? sl : sym_down_level-sym_offset); } //--- If you need to calculate the value for a SELL position if(order_type==ORDER_TYPE_SELL) { //--- Calculate the level sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Return the calculated value if it is higher than the upper limit of the Stops level // If the value is lower or equal, return the adjusted value return(sl>sym_up_level ? sl : sym_up_level+sym_offset); } } //--- return(0.0); }
CalculateTrailingStop 関数はトレーリングストップ値を計算するのに使用されます。
//+------------------------------------------------------------------+ //| Calculating the Trailing Stop value | //+------------------------------------------------------------------+ double CalculateTrailingStop(ENUM_POSITION_TYPE position_type) { //--- Variables for calculations double level =0.0; double buy_point =low_price[1]; // The Low value for a Buy double sell_point =high_price[1]; // The High value for a Sell //--- Calculate the level for a BUY position if(position_type==POSITION_TYPE_BUY) { //--- Bar's low minus the specified number of points level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is lower than the lower limit of the Stops level, // the calculation is complete, return the current value of the level if(level<sym_down_level) return(level); //--- If it is not lower, try to calculate based on the bid price else { level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is lower than the limit, return the current value of the level // otherwise set the nearest possible value return(level<sym_down_level ? level : sym_down_level-sym_offset); } } //--- Calculate the level for a SELL position if(position_type==POSITION_TYPE_SELL) { // Bar's high plus the specified number of points level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is higher than the upper limit of the Stops level, // the calculation is complete, return the current value of the level if(level>sym_up_level) return(level); //--- If it is not higher, try to calculate based on the ask price else { level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is higher than the limit, return the current value of the level // Otherwise set the nearest possible value return(level>sym_up_level ? level : sym_up_level+sym_offset); } } //--- return(0.0); }
これでトレーディング処理に正しい値を返す必要な関数をすべて入手しました。指定の条件が満たされればトレーリングストップを変更する条件を確認し、変更する関数、 ModifyTrailingStopを作成します。以下が詳細コメントを伴うこの関数のコードです。
上記で作成/変更された関数すべての使用には注意を払ってください。switch スイッチは現在ポジションのタイプに応じて適切な条件を判断し、それから条件結果は condition 変数に格納されます。ポジションを変更するには、「標準ライブラリ」の CTrade クラスから PositionModify メソッドを使用します。
//+------------------------------------------------------------------+ //| Modifying the Trailing Stop level | //+------------------------------------------------------------------+ void ModifyTrailingStop() { //--- If the Trailing Stop and Stop Loss are set if(TrailingStop>0 && StopLoss>0) { double new_sl=0.0; // For calculating the new Stop Loss level bool condition=false; // For checking the modification condition //--- Get the flag of presence/absence of the position pos_open=PositionSelect(_Symbol); //--- If the position exists if(pos_open) { //--- Get the symbol properties GetSymbolProperties(S_ALL); //--- Get the position properties GetPositionProperties(P_ALL); //--- Get the Stop Loss level new_sl=CalculateTrailingStop(pos_type); //--- Depending on the position type, check the relevant condition for the Trailing Stop modification switch(pos_type) { case POSITION_TYPE_BUY : //--- If the new Stop Loss value is higher // than the current value plus the set step condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point); break; case POSITION_TYPE_SELL : //--- If the new Stop Loss value is lower // than the current value minus the set step condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point); break; } //--- If there is a Stop Loss, compare the values before modification if(pos_sl>0) { //--- If the condition for the order modification is met, i.e. the new value is lower/higher // than the current one, modify the Trailing Stop of the position if(condition) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is no Stop Loss, simply set it if(pos_sl==0) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } }
ここでは上記変更に従い TradingBlock 関数を調整します。ModifyTrailingStop 関数内とそっくり同じようにトレーディングオーダーに対する変数の値はすべて switch スイッチを用いて決定されます。それはコードの分量を大幅に削減し、のちの変更を簡素化します。というのも2つのポジションタイプごとにブランチ1つの代わりに1つだけが残るためです。
//+------------------------------------------------------------------+ //| Trading block | //+------------------------------------------------------------------+ void TradingBlock() { ENUM_ORDER_TYPE signal=WRONG_VALUE; // Variable for getting a signal string comment="hello :)"; // Position comment double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volume for position calculation in case of reverse position double position_open_price=0.0; // Position opening price ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Order type for opening a position ENUM_POSITION_TYPE opposite_position_type=WRONG_VALUE; // Opposite position type //--- Get a signal signal=GetTradingSignal(); //--- If there is no signal, exit if(signal==WRONG_VALUE) return; //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- Get all symbol properties GetSymbolProperties(S_ALL); //--- Determine values for trade variables switch(signal) { //--- Assign values to variables for a BUY case ORDER_TYPE_BUY : position_open_price=sym_ask; order_type=ORDER_TYPE_BUY; opposite_position_type=POSITION_TYPE_SELL; break; //--- Assign values to variables for a SELL case ORDER_TYPE_SELL : position_open_price=sym_bid; order_type=ORDER_TYPE_SELL; opposite_position_type=POSITION_TYPE_BUY; break; } //--- Calculate the Take Profit and Stop Loss levels sl=CalculateStopLoss(order_type); tp=CalculateTakeProfit(order_type); //--- If there is no position if(!pos_open) { //--- Adjust the volume lot=CalculateLot(Lot); //--- Open a position // If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type GetPositionProperties(P_TYPE); //--- If the position is opposite to the signal and the position reverse is enabled if(pos_type==opposite_position_type && Reverse) { //--- Get the position volume GetPositionProperties(P_VOLUME); //--- Adjust the volume lot=pos_volume+CalculateLot(Lot); //--- Open the position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; }
また SetInfoPanel 関数にもうひとつ重要な修正を加える必要がありますが、まずプログラムが現在どこでどのように使用されているか示す予備関数を作成します。
//+------------------------------------------------------------------+ //| Returning the testing flag | //+------------------------------------------------------------------+ bool IsTester() { return(MQL5InfoInteger(MQL5_TESTER)); } //+------------------------------------------------------------------+ //| Returning the optimization flag | //+------------------------------------------------------------------+ bool IsOptimization() { return(MQL5InfoInteger(MQL5_OPTIMIZATION)); } //+------------------------------------------------------------------+ //| Returning the visual testing mode flag | //+------------------------------------------------------------------+ bool IsVisualMode() { return(MQL5InfoInteger(MQL5_VISUAL_MODE)); } //+------------------------------------------------------------------+ //| Returning the flag for real time mode outside the Strategy Tester| //| if all conditions are met | //+------------------------------------------------------------------+ bool IsRealtime() { if(!IsTester() && !IsOptimization() && !IsVisualMode()) return(true); else return(false); }
SetInfoPanel 関数に必要な追加は、プログラムに対して、情報パネルが視覚的にリアルタイムモードで表示されているだけであることを示す条件のみです。これが無視されると、検証時間は 4~5 倍長くなります。パラメータを最適化するときにはこれは特に重要です。
//+------------------------------------------------------------------+ //| Setting the info panel | //|------------------------------------------------------------------+ void SetInfoPanel() { //--- Visualization or real time modes if(IsVisualMode() || IsRealtime()) { // The remaining code of the SetInfoPanel() function // ... } }
これで必要なのはメインプログラムの関数がパラメータ最適化および Expert Advisor の検証を進めることができるように少し変更を加えることだけです。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get the properties and set the panel GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); }
//+------------------------------------------------------------------+ //| 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 ModifyTrailingStop(); // Modify the Trailing Stop level } //--- Get the properties and update the values on the panel GetPositionProperties(P_ALL); //--- Update the info panel SetInfoPanel(); }
//+------------------------------------------------------------------+ //| Trade event | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(P_ALL); //--- Update the info panel SetInfoPanel(); }
パラメータの最適化と Expert Advisor の検証
パラメータを最適化します。以下に図示されるように「ストラテジーテスタ」を設定します。
図1 パラメータ最適化のためのストラテジーテスタ設定
Expert Advisor のパラメータは広範囲におよびます。
図2 パラメータ最適化のためのExpert Advisor 設定
デュアルコアプロセッサ(Intel Core2 Duo P7350 @ 2.00GHz)では最適化に要する時間は約7分です。以下は最大リカバリーファクタ検証結果です。
図3 最大リカバリーファクタ検証結果
おわりに
いまのところ以上です。調査、検証、最適化、実験、そしてなんと!本稿で取り上げている Expert Advisor のソースコードは今後の調査のため以下のリンクによってダウンロードすることができます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/643





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