リスク設定に基づいてSL/TPを設定するクロスプラットフォームEAの開発

23 9月 2019, 08:28
Roman Klymenko
0
640

はじめに

おそらくご存知のように、資金管理ルールに従うことはすべての取引で強く推奨されています。つまり、預金のN%以上が失われる可能性のある取引に参加することは推奨されません。

ここでNはトレーダーによって選択され、ルールに従うためには、取引ロット値を正しく計算する必要があります。

関連するマスタークラスでは、発表者は通常、前もって準備した、各銘柄に対するロット計算式を含むExcelファイルを提示し、SL値を「単純に入力」することで必要なロット値を取得します。

これは本当に「単純」なのでしょうか。ロット計算操作には1分以上かかる場合があるため、ロットサイズが特定されるまでに、価格が意図したエントリポイントから非常に遠くに移動してしまう可能性があります。さらに、これには追加操作の実行が必要です。この方法のもう1つの欠点は、手動で計算するとエラーが発生する可能性が高くなることです。

このプロセスを本当に単純にしてみましょう。これを行うには、ビジュアルモードで始値とSL価格を設定するためのEAを作成します。これらのパラメータとリスク値に基づいて、EAは適切なロット値を設定し、関連する方向でポジションを開きます。

タスクの定義

最初のタスクの概要は説明しました。

EAによって実行されるあと1つのタスクは、希望されるSL:TP比に基づいてTP価格を設定することです。

Gerchikなどの勝ち組トレーダーは、SLの少なくとも3倍であるTPの使用を推進しています。つまり、40ポイントのSLを使用する場合、TPは少なくとも120ポイントである必要があります。価格がこのレベルに達する可能性があまりない場合は、取引を控えるべきです。

統計の計算をより便利にするために、同じSL:TP比を使用することが望ましくなります。たとえば、3:1、4:1などのTP:SL比で取引に参加します。取引目標に基づいて、特定の比率を選択する必要があります。

質問は、どのようにして時間を無駄にせずにTPを設定するかです。Excelが唯一の解決法なのでしょうか。それとも、チャート上のおおよその位置を推測する必要があるのでしょうか?

この可能性は、EAの設定で実装されます。TP:SL比を入力するための特別なパラメータが用意されます。たとえば、値「4」は、比率が4:1であることを意味し、EAは、SL値の4倍のTLを設定します。

EAの形式:EAの開発に進む前に、その動作モードを決定する必要があります。これはおそらく最も難しいタスクです。

1年以上にわたって同様のEAを使用してきましたが、EAは登場以来大きく変わりました。

最初のバージョンでは、EAの起動後にダイアログウィンドウが表示されて、新しく開くポジションのパラメータを変更することができました。これらの設定は自動的に保存され、その後のEAの起動時に使用されました。そして、これがこの方法の主な利点でした。非常に多くの場合、必要な設定を一度設定して、その後も使用し続けます。

ただし、設定を含むダイアログウィンドウには、チャートウィンドウのスペース全体をほぼ占有するという大きな欠点があります。このウィンドウのため、価格の変動を追跡できません。一度設定すれば使用されなくなるため、ダイアログウィンドウは役立つことなくチャートの表示を妨げます。

以下は、640ピクセル幅のチャートとダイアログウィンドウの例です。

ダイアログウィンドウ付きEAバージョン

図1 ダイアログウィンドウ付きEAバージョン

ご覧のとおり、ウィンドウは画面に完全には収まりません。

この問題を解決するために、さらに2つのEAバージョンを作成しました。

1番目のバージョンでは、設定ウィンドウはデフォルトで非表示になっており、[Settings](設定)ボタンをクリックして開くことができました。このバージョンは今でもMetaTrader 5マーケットで入手できます。

2番目のバージョンには設定ウィンドウがありません。すべてのEA設定は入力パラメータで指定されます。これにより、ダイアログウィンドウが使用されなくなります。ただし、これには次の問題のいずれかが伴います。

  • 各起動中にEAを新たに構成する必要性
  • 各起動時にSETファイルから設定をアップロードする必要性
  • デフォルト設定をEAコードで変更して再コンパイルする必要性

プログラマーの皆さんは、3番目のオプションを選択すると思います。本稿では、2番目のEAバージョンの簡略化されたクローンを作成します。EA操作は次のようになります。

ダイアログボックスなしEAバージョン

図2 ダイアログウィンドウなしEAバージョン

入力パラメータ

作業範囲全体をよりよく理解するために、EA入力パラメータを見てみましょう。

EA入力パラメータ

図3 EA入力パラメータ

取引活動はSLによって左右されます。したがって、最初の2つのパラメータ「Stop Loss type」および「Stop Loss value in $ or %」に注意を払いましょう。

値はデフォルトでドル単位で設定され、「Stop Loss type」パラメータで示されます。SLは、残高の割合として設定することもできます。割合としてのSLを選択した場合、指定した値は預金の5%を超えることはできません。これは、EAパラメータの設定時のエラーを回避するために行われます。

パラメータ「Stop Loss value in $ or %」は、SLの場合に失ってもよい金額を示します。

Stop Loss value in cents (keys 7 and 8): もう1つのSL関連のパラメータは「Stop Loss value in cents (keys 7 and 8)」です。

次に、1回のクリックで希望するSLを設定できるショートカットキーのセットを実装しましょう。たとえば、取引で常に7セントのSLを使用する場合、キーボードの「7」キーを押すだけで、SLは現在の価格から指定された距離に設定されます。これについては後で詳しく説明します。

このパラメータは、SLの場合に失う金額ではなく、始値とSL発動価格の間の距離を特定することに注意してください。

No deal entry if with min. lot the risk is greater than specified: 取引エントリロット値は「Stop Loss value in $ or %」パラメータに基づいて自動的に計算されるため、最小許容ロットが指定値よりも高い取引あたりのリスクにつながる状況が発生する可能性があります。

この場合、最小ロットで取引してリスクを無視するか、取引開始をキャンセルできます。適切な動作はこのパラメータで定義されます。

デフォルトでは、EAはリスクが増加した場合には取引に参加しません。

Cancel limit order after, hours: 成行注文の発注に加えて、EAでは指定された価格に基づいた指値注文の発注をサポートしています。このパラメータにより、注文の有効期間を制限できます。

有効期間は時間単位で設定されます。たとえば、有効期間が2時間に設定されており、指値注文が2時間以内に発動されない場合、そのような注文は削除されます。

Tale Profit multiplier:特定のTP対SL比で取引している場合、このパラメータを使用して、ルールに従って自動TP設定を構成できます。

デフォルト値は4です。それは、TP場合の利益が可能な損失の4倍になるような価格でTPが設定されることを意味します。

その他のパラメータ:他に可能な操作は次の通りです。

  • EA注文のマジックナンバーの設定
  • 注文コメントの設定
  • インターフェイス言語の変更(英語またはロシア語(デフォルト))

ポジションを開く関数

作成中のEAはクロスプラットフォームであるため、MetaTrader 4とMetaTrader 5の両方で動作しますが、ポジションを開く関数はEAのバージョンごとに異なります。2つのプラットフォームでEA操作を有効にするには、条件付きコンパイルを使用します。

条件付きコンパイルについては、すでにクロスプラットフォームグラインドEAの開発などの他の記事で言及しました。

要するに、条件付きコンパイルコードとは以下のとおりです。

#ifdef __MQL5__ 
   //MQL5 code
#else 
   //MQL4 code
#endif 

本稿では、条件付きコンパイルの可能性を3回使用しますが、そのうち2回はポジションを開く関数に関係します。残りのコードは、MetaTrader 4とMetaTrader 5の両方で機能します。

ポジションを開く関数は、クロスプラットフォームグラインドEAの開発稿で既に開発されているので、すぐに使用できるソリューションを使用しましょう。指値注文の有効期間を設定する機能を追加する必要があります。

// Possible order types for the position opening function
enum TypeOfPos
  {
   MY_BUY,
   MY_SELL,
   MY_BUYSTOP,
   MY_BUYLIMIT,
   MY_SELLSTOP,
   MY_SELLLIMIT,
   MY_BUYSLTP,
   MY_SELLSLTP,
  }; 

// Selecting deal filling type for MT5
#ifdef __MQL5__ 
   enum TypeOfFilling //Deal filling type
     {
      FOK,//ORDER_FILLING_FOK
      RETURN,// ORDER_FILLING_RETURN
      IOC,//ORDER_FILLING_IOC
     }; 
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; //Order filling mode
#endif 


/*
Position opening or limit order placing function
*/
bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym="", datetime expiration=0){
      if( !StringLen(sym) ){
         sym=_Symbol;
      }
      int curDigits=(int) SymbolInfoInteger(sym, SYMBOL_DIGITS);
      if(sl>0){
         sl=NormalizeDouble(sl,curDigits);
      }
      if(tp>0){
         tp=NormalizeDouble(tp,curDigits);
      }
      if(price>0){
         price=NormalizeDouble(price,curDigits);
      }else{
         #ifdef __MQL5__ 
         #else
            MqlTick latest_price;
            SymbolInfoTick(sym,latest_price);
            if( mytype == MY_SELL ){
               price=latest_price.ask;
            }else if( mytype == MY_BUY ){
               price=latest_price.bid;
            }
         #endif 
      }
   #ifdef __MQL5__ 
      ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL;
      ENUM_ORDER_TYPE type=ORDER_TYPE_BUY;
      switch(mytype){
         case MY_BUY:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSLTP:
            action=TRADE_ACTION_SLTP;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_STOP;
            break;
         case MY_BUYLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_LIMIT;
            break;
         case MY_SELL:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSLTP:
            action=TRADE_ACTION_SLTP;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_STOP;
            break;
         case MY_SELLLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_LIMIT;
            break;
      }
      
      MqlTradeRequest mrequest;
      MqlTradeResult mresult;
      ZeroMemory(mrequest);
      
      mrequest.action = action;
      mrequest.sl = sl;
      mrequest.tp = tp;
      mrequest.symbol = sym;
      if(expiration>0){
         mrequest.type_time = ORDER_TIME_SPECIFIED_DAY;
         mrequest.expiration = expiration;
      }
      if(position>0){
         mrequest.position = position;
      }
      if(StringLen(comment)){
         mrequest.comment=comment;
      }
      if(action!=TRADE_ACTION_SLTP){
         if(price>0){
            mrequest.price = price;
         }
         if(volume>0){
            mrequest.volume = volume;
         }
         mrequest.type = type;
         mrequest.magic = EA_Magic;
         switch(useORDER_FILLING_RETURN){
            case FOK:
               mrequest.type_filling = ORDER_FILLING_FOK;
               break;
            case RETURN:
               mrequest.type_filling = ORDER_FILLING_RETURN;
               break;
            case IOC:
               mrequest.type_filling = ORDER_FILLING_IOC;
               break;
         }
         mrequest.deviation=100;
      }
      if(OrderSend(mrequest,mresult)){
         if(mresult.retcode==10009 || mresult.retcode==10008){
            if(action!=TRADE_ACTION_SLTP){
               switch(type){
                  case ORDER_TYPE_BUY:
//                     Alert("Order Buy #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
                  case ORDER_TYPE_SELL:
//                     Alert("Order Sell #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
               }
            }else{
//               Alert("Order Modify SL #:",mresult.order," sl",sl," tp",tp," !!");
            }
            return true;
         }else{
            msgErr(GetLastError(), mresult.retcode);
         }
      }
   #else 
      int type=OP_BUY;
      switch(mytype){
         case MY_BUY:
            type=OP_BUY;
            break;
         case MY_BUYSTOP:
            type=OP_BUYSTOP;
            break;
         case MY_BUYLIMIT:
            type=OP_BUYLIMIT;
            break;
         case MY_SELL:
            type=OP_SELL;
            break;
         case MY_SELLSTOP:
            type=OP_SELLSTOP;
            break;
         case MY_SELLLIMIT:
            type=OP_SELLLIMIT;
            break;
      }
      
      if(OrderSend(sym, type, volume, price, 100, sl, tp, comment, EA_Magic, expiration)<0){
            msgErr(GetLastError());
      }else{
         switch(type){
            case OP_BUY:
               Alert("Order Buy sl",sl," tp",tp," p",price," !!");
               break;
            case OP_SELL:
               Alert("Order Sell sl",sl," tp",tp," p",price," !!");
               break;
            }
            return true;
      }
   
   #endif 
   return false;
}

MetaTrader 5では、ポジションを開く際にポジションタイプを選択する必要があります。したがって、MetaTrader 5用に入力をもう1つ(Order fill mode)追加します。

利用できる注文タイプはブローカーによって異なりますが、ブローカーの間で最も人気のあるものはORDER_FILLING_FOKで、デフォルトで選択されています。お使いのブローカーがこのモードをサポートしていない場合、他のモードをご選択ください。

EAのローカライズ

クロスプラットフォームグラインドEAの開発で以前に説明したもう1つのメカニズムは、EAによって生成されたテキストメッセージをローカライズする可能性です。したがって、ここでは再検討しないことにします。EA操作形式の詳細については、言及された記事をお読みください。

EAインターフェイスの開発

本稿では、EA開発をゼロから検討することはせず、また、読者が少なくとも基本的なMQLの知識を持っていることを前提としています。

EAの主要な部分がどのように実装されているかを見ていきます。これは、必要に応じて追加機能を実装するのに役立ちます。

EAのインターフェイスから始めましょう。

EAの起動時には次のインターフェイス要素が作成されます。

  • コメントには、ドル、ポイント、価格のパーセントでの現在のスプレッドと、銘柄セッションの終了時間が含まれる
  • 現在の価格からのスプレッド距離に、SLの配置に使用される水平線が表示される
  • [Show (0) open price line]ボタンが追加され、注文の始値で緑色の水平線が表示される
  • パラメーターを指定して注文を開くには、別のボタンが必要である

したがって、必要なパラメータ(EA入力を使用して設定)で注文を作成するには、SLを設定する価格に赤線を移動する必要があります。

赤線が現在の価格の上に移動すると、ショートポジションが開きます。赤線が現在の価格より下に移動すると、ロングポジションが開きます。

ポジションボリュームは自動的に計算されるため、SLの場合、EAパラメータで指定された量に近い量を失うことになります。ポジションを開くボタンをクリックすることだけが必要です。その後、市場価格でポジションが開かれます。

指値注文を出したい場合は、さらに[Show (0) open price line]をクリックし、表示された緑色の線を指値注文を開く価格に移動する必要があります。指値注文のタイプと方向は、SL線とTP線の位置に基づいて自動的に決定されます(SLは始値の上または下です)。

基本的に、[Show (0) open price line]ボタンをクリックする必要はありません。赤いSL線を望まれるレベルに移動すると、ボタンが自動的に押され、緑色の始値線が表示されます。この線を移動すると、指値注文が設定されます。線をそのままにすると、成行ポジションが開かれます。

動作原理を分析したので、開発に移ります。

チャートコメントの使用:標準的なComment関数は、チャートのコメントを操作するために使用されます。したがって、チャートに表示される文字列はComment関数を介して準備する必要があります。この目的のために、カスタム関数getmespreadを作成します。

/*
   Showing data on spread and session closing time in a chart comment
*/
void getmespread(){
   string msg="";
   
   // Get spread in the symbol currency
   curSpread=lastme.ask-lastme.bid;
   
   // If the market is not closed, show spread info
   if( !isClosed ){
      if(curSpread>0){
         StringAdd(msg, langs.Label1_spread+": "+(string) DoubleToString(curSpread, (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))+" "+currencyS+" ("+DoubleToString(curSpread/curPoint, 0)+langs.lbl_point+")");
         StringAdd(msg, "; "+DoubleToString(((curSpread)/lastme.bid)*100, 3)+"%");
      }else{
         StringAdd(msg, langs.Label1_spread+": "+langs.lblNo);
      }
      StringAdd(msg, "; ");
   }
   
   // Show market closing time if we could determine it
   if(StringLen(time_info)){
      StringAdd(msg, "   "+time_info);
   }
      
   Comment(msg);
}

getmespread関数はEA初期化中(OnInit)およびすべてのティック(OnTick)で呼ばれます。

getmespreadでは、lastmeisClosedtime_infocurrencyScurPointの5つのグローバルEA変数が使用されます。

lastme変数にはAsk、Bid、Lastのデータが格納され、変数値は、以下を使用してOnInitおよびOnTick関数で更新されます。

SymbolInfoTick(_Symbol,lastme);

他の変数はOnInit関数で初期化されます。isClosedtime_infoは次のように初期化されます。

  isClosed=false;
  // Get the current date
  TimeToStruct(TimeCurrent(), curDay);
  // Get symbol trading time for today
  if(SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){
      time_info="";
      TimeToStruct(dto, curEndTime);
      TimeToStruct(dfrom, curStartTime);
         
         isEndTime=true;
         string tmpmsg="";
         tmp_val=curEndTime.hour;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val+":");
         tmp_val=curEndTime.min;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val);
         if(curEndTime.hour==curDay.hour){
            if(tmp_val>curDay.min){
            }else{
               isClosed=true;
            }
         }else{
            if(curEndTime.hour==0){
            }else{
               if( curEndTime.hour>1 && (curDay.hour>curEndTime.hour || curDay.hour==0)){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isClosed=true;
               }else if(curDay.hour<curStartTime.hour ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }else if(curDay.hour==curStartTime.hour && curDay.min<curStartTime.min ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }
            }
         }

         if(isEndTime){
            StringAdd(time_info, langs.lblshow_TIME+": "+tmpmsg+time_info);
         }else{
            StringAdd(time_info, langs.lblshow_TIME2+": "+tmpmsg+time_info);
         }
  }

currencyS変数には、現在の金融商品の利益計算に使用される通貨が格納されます。通貨は、次のコマンドを使用して取得できます。

currencyS=SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);

銘柄のポイントサイズはcurPoint変数に格納されます。

curPoint=SymbolInfoDouble(_Symbol, SYMBOL_POINT);

SL線:EAの起動時にチャートに表示されるのは、SLを設定するための赤線のみです。

ボタンと同様に、この線はOnInit関数でプロットされます。線を描く前に、チャート上にそのような線が既にあるかどうかを確認する必要があります。ある場合は、新しい線や他のUI要素は作成しません。代わりに、現在の線レベルの価格と取引の始値線の価格(チャートにある場合)のデータをグローバル変数に追加します。

  // if there are Stop Loss and open price lines on the chart
  // add the appropriate prices to variables
  if(ObjectFind(0, exprefix+"_stop")>=0){
      draw_stop=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
      if(ObjectFind(0, exprefix+"_open")>=0){
         draw_open=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
      }
  // otherwise create the entire Expert Advisor UI
  }else{
      draw_open=lastme.bid;
      draw_stop=draw_open-(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)*curPoint);
      ObjectCreate(0, exprefix+"_stop", OBJ_HLINE, 0, 0, draw_stop);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_ANCHOR,ANCHOR_TOP);

      // other UI elements
  }

一般に、チャートにすでにUI要素が含まれている可能性はあるのでしょうか。

EAを閉じるときに作成されたすべてのユーザーインターフェイス要素を削除するコードを実装しない場合、チャートには確実に要素が残されます。このコードが実装されている場合でも、EA操作でエラーが発生し、作成された要素をチャートに残したままEAが閉じられる可能性があります。したがって、作成する前に、そのような要素がチャートに存在するかどうかを常に確認します。

さて、赤線を作成し、デフォルトで選択済みとして設定してあるので、ダブルクリックして選択する必要はありません。必要な価格に移動するだけです。ただし、今赤線を移動しても、何も起こりません。適切なアクションを実行するコードはまだ実装されていません。

UI要素との対話は、標準のOnChartEvent関数で実行されます。インターフェース要素を移動すると、CHARTEVENT_OBJECT_DRAGIDを持ったイベントが生成されます。したがって、チャートで移動を実装するには、OnChartEvent関数がこのイベントをインターセプトしてどの要素がそれを呼び出したかを確認し、要素がEAに属している場合には必要なコードを実行する必要があります。

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 
   switch(id){
      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }
         break;
   }
}

赤線を移動すると、setstopbyline関数が開始され、今後の注文のSLレベルが記憶されます。

/*
"Remembers" the Stop Loss levels for the future order
*/
void setstopbyline(){
   // Receive the price in which the Stop Loss line is located
   double curprice=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
   // If the price is different from the one in which the Stop Loss line was positioned at the EA launch,
   if(  curprice>0 && curprice != draw_stop ){
      double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      if( tmp_double>0 && tmp_double!=1 ){
         if(tmp_double<1){
            resval=DoubleToString(curprice/tmp_double, 8);
            if( StringFind(resval, ".00000000")>0 ){}else{
               curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
            }
         }else{
            if( MathMod(curprice,tmp_double) ){
               curprice= MathFloor(curprice/tmp_double)*tmp_double;
            }
         }
      }
      draw_stop=STOPLOSS_PRICE=curprice;
                  
      updatebuttontext();
      ChartRedraw(0);
   }
}

setstopbyline関数に加えて、赤線を移動すると、チャートに始値ラインが表示され(showOpenLine関数)、[Show (0) open price line]ボタンのステータスが変更されます。

始値ラインとボタン:EAの初期化中には、[Show (0) open price line]ボタンも作成されます。

      if(ObjectFind(0, exprefix+"_openbtn")<0){
         ObjectCreate(0, exprefix+"_openbtn", OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XDISTANCE,0); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YDISTANCE,33); 
         ObjectSetString(0,exprefix+"_openbtn",OBJPROP_TEXT, langs.btnShowOpenLine); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XSIZE,333); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_FONTSIZE, 8);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YSIZE,25); 
      }

前述のように、インターフェイス要素との対話は、標準のOnChartEvent関数内で処理されます。これには、ボタンの押下が含まれ、IDCHARTEVENT_OBJECT_CLICKを持つイベントが対応します。このイベントをインターセプトし、イベントソースを確認し、適切なアクションを実行する必要があります。このために、 OnChartEvent関数のswitch演算子に追加のcaseを追加しましょう。

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

ボタン[Show (0) open price line]がクリックされたときに呼び出されるupdateOpenLine関数はメインのshowOpenLine関数呼び出しの小さいラッパーです。この関数は、単にチャートに始値を表示します。

void showOpenLine(){
   if(ObjectFind(0, exprefix+"_open")<0){
      draw_open=lastme.bid;
      ObjectCreate(0, exprefix+"_open", OBJ_HLINE, 0, 0, draw_open);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_ANCHOR,ANCHOR_TOP); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_COLOR,clrGreen);
   }
}

次に、SL線と始値線の移動に反応できるように、CHARTEVENT_OBJECT_DRAGイベントハンドラを書き直す必要があります。

      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }else if(sparam==exprefix+"_open"){
               curprice=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
               if( curprice>0 && curprice != draw_open ){
                  double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                  if( tmp_double>0 && tmp_double!=1 ){
                     if(tmp_double<1){
                        resval=DoubleToString(curprice/tmp_double, 8);
                        if( StringFind(resval, ".00000000")>0 ){}else{
                           curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
                        }
                     }else{
                        if( MathMod(curprice,tmp_double) ){
                           curprice= MathFloor(curprice/tmp_double)*tmp_double;
                        }
                     }
                  }
                  draw_open=open=OPEN_PRICE=curprice;
                  
                  updatebuttontext();
                  ObjectSetString(0,exprefix+"Edit3",OBJPROP_TEXT,0, (string) NormalizeDouble(draw_open, _Digits));
                  ChartRedraw(0);
               }
         }
         break;

TP線:赤線と緑の線に加えて、点線を実装する必要があります。これは、赤いSL線を望まれる価格レベルに移動すると表示されます。点線は、取引のTPレベルの価格を示します。

SL線、始値線、TP線

図4 SL線、始値線、TP線

ポジションを開くボタン:ポジションを開くボタンはボタン[Show (0) open price line]と同様に描画されます。

このボタンがクリックされるとCHARTEVENT_OBJECT_CLICKイベントが生成されます。このイベントの処理は先に考察しました。このボタンを押すと、startPosition関数が実行されます。

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_send"){
            startPosition();
         }else if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

EA操作の完了時のUI要素の削除:EA操作の完了後、インターフェイス要素を忘れずに削除してください。これを行わないと、すべての要素がチャートに残ります。

EA操作の完了時にコマンドを実行するには、標準のOnDeinit関数内にコマンドを追加します。

void OnDeinit(const int reason)
  {

     if(reason!=REASON_CHARTCHANGE){
        ObjectsDeleteAll(0, exprefix);
        Comment("");
     }
      
  }

reason変数には、EA操作を完了する理由に関する情報が含まれています。現在、唯一の重要な理由は、時間枠の変更(REASON_CHARTCHANGE)です。デフォルトでは、時間枠の変更はEA操作の完了と再起動につながりますが、この動作はあまり望ましいものではありません。時間枠が変更されると、設定されているすべてのSLと始値レベルがリセットされることになります。

したがって、OnDeinitでは、EAが終了される理由が時間枠の変更であるかどうかを確認し、異なる場合にのみ、すべての要素を削除し、チャートへのコメントをクリアする必要があります。

EAショートカットの実装

マウスを使用した発注は検討しましたが、キーを使用した高速操作が非常に役立つ場合があります。

キーボードのキーの押下は、EA UI要素との相互作用に関係します。つまり、EAが実行されているチャート全体に関係します。キーストロークは、OnChartEvent関数でインターセプトする必要があります。

キーが押下されるとCHARTEVENT_KEYDOWNイベントが生成されて、押下されたキーのコードがsparamパラメータに追加されます。このデータは、キーストローク処理を開始するのに十分です。

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 
   string text="";
   double curprice=0;
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // Pressing of buttons on the chart
         break;
      case CHARTEVENT_OBJECT_DRAG:
         // Moving lines
         break;
      case CHARTEVENT_KEYDOWN:
         switch((int) sparam){
            // Terminate EA operation without placing an order
            case 45: //x
               closeNotSave();
               break;
            // Place an order and complete EA operation
            case 31: //s
               startPosition();
               break;
            // Set minimum possible Stop Loss to open a Buy position
            case 22: //u
               setMinStopBuy();
               break;
            // Set minimum possible Stop Loss to open a Sell position
            case 38: //l
               setMinStopSell();
               break;
            // Cancel the set open price
            case 44: //z
               setZero();
               ChartRedraw();
               break;
            // Set Stop Loss at 0.2% from the current price to open a Long position
            case 3: //2
               set02StopBuy();
               break;
            // Set Stop Loss at 0.2% from the current price to open a Short position
            case 4: //3
               set02StopSell();
               break;
            // Set Stop Loss to 7 cents from the current price (the CENT_STOP parameter)
            // To open a Long position
            case 8: //7
               set7StopBuy();
               break;
            // Set Stop Loss to 7 cents from the current price (the CENT_STOP parameter)
            // To open a Short position
            case 9: //8
               set7StopSell();
               break;
         }
         break;
   }
}

したがって、固定SLを最小サイズに設定すれば(0.2%またはセント)、マウスを使用する必要さえありません。EAを開始し、キー「2」を押してSLをロング方向の価格から0.2%に設定し、キー「S」を押すと適切なポジションが開きます 。

また、MetaTrader 5を使用している場合は、割り当てられたホットキーを使用してキーボードからEAを起動することもできます。[Navigator](ナビゲーター)ウィンドウで、EAコンテキストメニューを呼び出し、[Set hotkey](ホットキーを設定)をクリックすれば、高速でアクセスできます。

EAへのホットキーの割り当て

図5 EAへのホットキーの割り当て

適切な取引量の計算

ここで、ポジションを開く関数startPositionについて考えてみましょう。ここには、興味深いものはほとんどありません。必要なすべてのデータ(SL価格、ポジション始値、EA設定)の利用可能性をチェックするだけです。その後、EAはリスク設定に従ってエントリロット値を計算します。次に、前述の関数pdxSendOrderが呼び出されます。

最も興味深いのは、取引量の計算メカニズムです。

まず、可能な限り小さいボリュームで、SLの場合の潜在的な損失を計算する必要があります。MQL5でのこの関数の実装は、MQL4の実装とは異なります。

MQL5には特別な OrderCalcProfit関数があり、銘柄価格が指定されたレベルに達した場合に獲得できる潜在的な利益のサイズを計算できるので、この関数を使用すると、潜在的な利益と潜在的な損失を計算できます。

MQL4では、より複雑な損失計算式が使用されます。

得られるのは次の関数です。

double getMyProfit(double fPrice, double fSL, double fLot, bool forLong=true){
   double fProfit=0;
   
   fPrice=NormalizeDouble(fPrice,_Digits);
   fSL=NormalizeDouble(fSL,_Digits);
   #ifdef __MQL5__ 
      if( forLong ){
         if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }else{
         if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }
   #else
      if( forLong ){
         fProfit=(fPrice-fSL)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }else{
         fProfit=(fSL-fPrice)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }
   #endif 
   if( fProfit!=0 ){
      fProfit=MathAbs(fProfit);
   }
   
   return fProfit;
}

したがって、最小ボリュームで損失額を計算しました。ここで、損失が指定されたリスク設定を増加させない取引量を特定する必要があります。

      profit=getMyProfit(open, STOPLOSS_PRICE, lot);
      if( profit!=0 ){
         // If loss with the minimum lot is less than your risks,
         // calculate appropriate deal volume
         if( profit<stopin_value ){
            // get the desired deal volume
            lot*=(stopin_value/profit);
            // adjust the volume if it does not correspond to the minimum allowed step
            // for this trading instrument
            if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.01 ){
               lot=(floor(lot*100))/100;
            }else if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.1 ){
               lot=(floor(lot*10))/10;
            }else{
               lot=floor(lot);
            }
         // If loss with the minimum lot is greater than your risks,
         // cancel position opening if this option is set in EA parameters
         }else if( profit>stopin_value && EXIT_IF_MORE ){
            Alert(langs.wrnEXIT_IF_MORE1+": "+(string) lot+" "+langs.wrnEXIT_IF_MORE2+": "+(string) profit+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(string) stopin_value+" "+AccountInfoString(ACCOUNT_CURRENCY)+")!");
            return;
         }
      }

エントリの制限

EAは、特定の条件を確認して、取引が許容範囲でない場合には取引できないようにします。

たとえば、初期化中には現在の商品の最小許容ロット量が確認され、この値が0の場合、EAは起動しません。これは、そのような銘柄のポジションを開くことができないためです。

   if(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)==0){
      Alert(langs.wrnMinVolume);
      ExpertRemove();
   }

銘柄取引が許可されているかどうかも確認します。銘柄取引が禁止されている場合、または以前に開かれた取引の決済のみが許可されている場合、EAは起動しません。

   if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
      Alert(langs.wrnOnlyClose);
      ExpertRemove();
   }

ポジションを開くとき、EAは始値とSLレベルの正確さを確認します。たとえば、最低価格ステップが0.25で、SLが23.29に設定されている場合、ブローカーは注文を受け入れません。通常、EAは価格を適切な値に自動的に調整できるため(SL価格は23.25または23.5に設定されます)、無効な価格を示すことはありません。ただし、追加の確認も実行されます。

   if( SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)>0 && SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)!=1 ){
      resval=DoubleToString(STOPLOSS_PRICE/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), 8);
      if( StringFind(resval, ".00000000")>0 ){}else{
         Alert(langs.wrnSYMBOL_TRADE_TICK_SIZE+" "+(string) SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)+"! "+langs.wrnSYMBOL_TRADE_TICK_SIZE_end);
         return;
      }
   }

終わりに

本稿で実装されたのは基本的な発注機能のみですが、これだけでもGerchikレベルまたは他のレベルを使用するトレーダーのみなさんにとっては大きな助けとなるでしょう。

これにより、Excelテーブルを使用する手間が省けることを願っています。取引速度と精度が向上し、最終的に利益を増やすことができます。

EAの改善は大歓迎です。

ご自分ではプログラミングできないが特定の機能が必要だという場合は、お気軽にお問い合わせください。ただし、実装は有料になる場合があります。

このEAの拡張バージョンの機能をマーケットでご確認ください。

おそらく必要な機能はすでにここに含まれています。

MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/6986

添付されたファイル |
openWithRisk.ex5 (126.3 KB)
openWithRisk.ex4 (56.42 KB)
openWithRisk.mq5 (119.01 KB)
openWithRisk.mq4 (119.01 KB)
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第12部): 「口座」オブジェクトクラスと口座オブジェクトのコレクション MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第12部): 「口座」オブジェクトクラスと口座オブジェクトのコレクション

前の記事では、ライブラリでMQL4ポジション決済イベントを定義し、未使用の注文プロパティを取り除きました。本稿では、口座オブジェクトの作成を検討して口座オブジェクトのコレクションを開発し、口座イベントを追跡する機能を準備します。

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部)MQL4との互換性 - ポジション決済イベント MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部)MQL4との互換性 - ポジション決済イベント

MetaTrader 5およびMetaTrader 4プラットフォーム用のプログラムの開発を簡素化する大規模なクロスプラットフォームライブラリの開発を継続します。第10部では、MQL4とのライブラリの互換性に関する作業を再開し、ポジションを開くイベントと未決注文の発動イベントを定義しました。本稿では、ポジション決済イベントを定義し、未使用の注文プロパティを取り除きます。

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第13部): 口座オブジェクトイベント MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第13部): 口座オブジェクトイベント

本稿では、自動取引に影響する口座プロパティの重要な変更を追跡するために、口座イベントの使用について検討しています。口座イベントを追跡するための機能のいくつかは、前の記事で口座オブジェクトコレクションを開発するときに既に実装しています。

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第14部): 銘柄オブジェクト MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第14部): 銘柄オブジェクト

本稿では、銘柄コレクションを作成するための基本オブジェクトとなる銘柄オブジェクトクラスを作成します。このクラスによって、さらなる分析と比較に必要な銘柄のデータを取得できるようになります。