English Deutsch
preview
MQL5で日次ドローダウンリミッターEAを作成する

MQL5で日次ドローダウンリミッターEAを作成する

MetaTrader 5トレーディング | 2 9月 2024, 10:38
41 0
Kikkih25
Kikkih25

はじめに 

この記事では、MetaTrader 5用のMetaQoutes Language (MQL5)で日次ドローダウンリミッターFX取引のエキスパートアドバイザー(EA)を作成します。このEAの目的は、取引口座の1日の引き出し上限を設定することです。EAは、合計バー数、開始残高、時間帯、毎日の残高などの要素を分析し、特定の条件に基づいて取引が発生するかどうかを確認します。また、MetaTraderプラットフォームから残高、資本金、資金などの情報を収集します。このEAは、MetaTrader取引プラットフォームで動作するように特別に設計されており、適切に機能するためには取引口座が必要です。

この旅では以下のトピックを取り上げます。

  1. ドローダウンリミッターの説明
  2. MQL5でEAを作成する
  3. 結論


ドローダウンリミッターの説明

ドローダウンリミッターとは、取引や投資において、ドローダウン期間中の潜在的な損失を制限することでリスクを管理するためのツールです。ドローダウン期間は、市場の変動や経済状況によって資産やポートフォリオの価値が下落したときに発生します。この期間中、ドローダウンリミッターは、価値が設定したレベルを下回ると自動的に投資の全部または一部を売却し、投資家を多額の損失から守るのに役立ちます。このツールは、起こりうる損失を減らし、投資家の資本を保護することを目的としています。マネージドフューチャーズ口座やその他の投資ツールは、一般的にドローダウンリミッターを利用してリスクをコントロールし、市場の大幅な下落に備えます。 

ファンド口座を取引する際、トレーダーは、ドローダウンのコントロールに苦しみます。日次ドローダウンリミッターは、そうした人たちのためのものです。プロップファームは通常、「トレーダー日次ドローダウン」と呼ばれるルールを設定し、それが守られない場合、トレーダーは失格となります。ドローダウンリミッターは次においてトレーダーをサポートします。

  1. 口座のドローダウンを追跡する
  2. トレーダーがリスクの高い取引をしている場合に警告を出す
  3. トレーダーのデイリードローダウンを追跡する
  4. ポジションを制限することで、トレーダーのオーバー取引を防止する 


MQL5でEAを作成する 

EAの本質的な機能は、それが別のEAによる手動または自動取引であるかどうかにかかわらず、口座上のすべての活動を監視することです。トレーダーがチャートを追加するだけで、EAがコントロールし始めます。トレーダーはスクリーンに「交通信号」が表示されていることに気づくでしょう。「交通信号」機能は、上記の4つのキーについて、トレーダーにシンプルなグラフィカルな方法で通知します。EAのコメントは非表示にでき、好みの色やフォントにカスタマイズできます。ワンクリックで詳細ページを非表示にし、チャート上のスペースを確保することができます。交通信号の位置やスタイルは、チャートのスタイルに合わせて自由にカスタマイズできます。

取引ポジションを建てる必要があります。ポジションを建てる最も簡単な方法は、取引インスタンスを含めることであり、通常はポジション専用の別のファイルを含めることで実現します。includeディレクティブを使用して、取引操作のための関数を含む取引ライブラリをインクルードします。まず、山括弧を使用して、インクルードするファイルがincludeフォルダに含まれていることを示し、tradeフォルダを指定します。その後に通常のスラッシュまたはバックスラッシュ、次にターゲットファイル名(この場合はTrade.mqh)を指定します。cTradeは取引操作を処理するクラスであり、obj-tradeはこのクラスのインスタンスで、通常はクラスのメンバー変数にアクセスできるようにcTradeクラスから作成されたポインタオブジェクトです。

#include <Trade/Trade.mqh>
CTrade obj-Trade;

その後、ポジションを建てるシグナルを生成するための制御ロジックが必要です。この場合、関数OnTick()は変数isTradeAllowedがtrueかどうかを確認しています。その場合、checkDailyProfit()関数が呼び出され、OnTick()関数の目的が日々の利益を確認してそれに基づいて取引を許可または禁止する可能性があることを示唆しています。バーはチャート上のバーの合計数を記録し、取引ロジックが新しいバーごとに1回だけ実行されるようにし、1つのバー内で複数回実行されるのを防ぎます。これらの変数を組み合わせることで、EAは適切な執行タイミングを維持しながら、値に基づいて売買シグナルを生成することができます。この関数はパラメータを取らないので、この関数が実行するアクションに移りましょう。これは次の通りです。

  • 変数total_day_Profitを定義し、0に初期化します。  
  • さらに、現在時刻を受け取り、日付変数に格納されているTimeToString関数を使用して文字列に変換します。
  • 同様に、1日の始まりに1を足してその日の最初の時間を計算し、変数に保存します。
  • 現在時刻(daytime)が指定した時刻より小さいかどうかを確認します。もしそうなら、dayTime値を設定し、Acc_B関数を使用して現在の残高を計算し、dayBalance変数に格納します。
  • HistorySelect関数でその日の履歴データを選択し、開始時刻と終了時刻をその日の開始時刻と終了時刻に設定します。
  • HistoryDealsTotal関数を使用してその日の取引の合計数を計算し、TotalDeals変数に格納します。
  • それだけでなく、履歴の各トランザクションを調べ、トランザクションエントリのタイプがDEAL_ENTRY_OUTであるかどうかを確認します。これは決済トランザクションです。そうであれば、DEAL_PROFIT、DEAL_COMMISSION、DEAL_SWAPの値を加算して取引利益を計算し、total_day_profit変数に加算します。
  • AccountInfoDouble関数をACCOUNT-BALANCEパラメータで使用して、現在の口座残高からtotal_day_Profitを差し引くことにより、その日の開始残高を計算します。

この関数は、計算された開始残高をdouble値として返します。

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed = true;

次に関数の定義に移ります。この関数は口座情報に関連しているようで、関数名に基づいて異なる値を返します。関数Acc_B()、Acc_E()、Acc'S()は、それぞれ口座残高、資本、通貨に関する情報を取得するために使用されます。これらの関数は、口座の財務状況をモニターするために使用されます。

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S(){return AccountInfoString(ACCOUNT_CURRENCY);}

ポジションを建てるための全コードは以下の通りです。

#include <Trade/Trade.mqh>
CTrade obj-Trade;

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed=true;

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc'S(){return AccountInfoString(ACCOUNT_CURRENCY);}

Onlnitイベントハンドラは、EAが初期化されるたびに呼び出されます。これは、指標を初期化し、さらに分析するために口座の初期残高データを表示するテキストを作成するために必要なインスタンスです。指標を初期化するために、組み込み関数を使用し、正しいパラメータを与えてcreateTextを返します。テキストオブジェクトは特定の座標に配置され、フォントサイズとともに色が使用されます。この関数で達成されることの内訳は以下の通りです。

  1. Acc_B()関数で口座の初期残高を探し、変数initialBalanceに格納します。
  2. これにより、画面上の(30,30)の位置に「* PROP FIRM PROGRESS DASHBOARD *」というテキストボックスが作成され、フォントサイズは13、色は水色(clrAqual)となります。
  3. これにより、ユーザーにさまざまなメッセージや情報を表示するための複数のテキストボックスが作成されます。これらのテキストボックスは、画面上の異なる位置に配置され、異なるフォントサイズと色を持っています。

ここでの主な目的は、口座管理と取引に関連する様々な情報を表示するユーザーインターフェイスを作成することです。テキストボックスは、口座情報、メッセージ、その他の関連データをユーザーに表示するために使用されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {

   initialBalance = Acc_B();
   
   createText("0","***DAILY DRAWDOWN LIMITER ***",30,30,clrAqua,13);
   createText("00","______________________________________",30,30,clrAqua,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrWhite,11);
   createText("2","Counters will be Reset on Next Day Start.",70,65,clrWhite,10);
   createText("3","From: ",70,80,clrWhite,10);
   createText("4",'Time Here',120,80,clrGray,10);
   createText("5","To: ",70,95,clrWhite,10);
   createText("6",'Time Here',120,95,clrGray,10);
   createText("7",'Current: ',70,110,clrWhite,10);
   createText("8",'Time Here',120,110,clrGray,10);

   createText("9",'ACCOUNT DRAWDOWN ============',70,130,clrPeru,11);
   createText("10",'Account Initial Balance: ',70,145,clrWhite,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrWhite,10);
   createText("12",'Torelated DrawDown: ',70,160,clrWhite,10);
   createText("13","12.00 %",250,160,clrAqua,10);
   createText("14",'Current Account Equity: ',70,175,clrWhite,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
   createText("16",'Current Balance Variation: ',70,190,clrWhite,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18",'DAILY DRAWDOWN ================',70,210,clrPeru,11);
   createText("19",'Starting Balance: ',70,225,clrWhite,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("21",'DrawDowm Maximum Threshold: ',70,240,clrWhite,10);
   createText("22",'5.00 %"+" "+Acc_S(),270,240,clrAqua,10);
   createText("23",'DrawDown Maximum Amount: ',70,255,clrWhite,10);
   createText("24",'-"+DoubleToString(Acc_B()*5/100,2)+' "+Acc_S(),270,255,clrYellow,10);
   createText("25",'Current Closed Daily Profit: ',70,270,clrWhite,10);
   createText("26",'0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27",'Current DrawDown Percent: ',70,285,clrWhite,10);
   createText("28",'0.00"+" %",270,285,clrGray,10);

   createText("29",'>>> Initializing The Program, Get Ready To Trade.",70,300,clrYellow,10);
   
   return(INIT_SUCCEEDED);
}

ここでOnTick関数は、EAがアタッチされている銘柄の新しいティックがあるたびに呼び出されます。checkDailyProfit関数については、それが正しく実装されていることを確認する必要があります。isTradeAllowedは、取引が許可されているかどうかを制御するブール変数です。isTradeAllowedがfalseの場合、即座に戻り、OnTick関数内でそれ以上のコードは実行されません。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
    

ブレークダウンスイングのインスタンスを定義するだけです。これはすべてのティックにおこなう必要があるので、制限なくおこないます。まず、それぞれの条件が満たされた時点でポジションを建てるために使用するAsk価格とBid価格を宣言します。これは、最新の価格相場を入手するために、ティックごとにおこなう必要があることに注意してください。ここでは、最近の価格を格納するためにdoubleデータ型の変数を宣言し、精度を保つために浮動小数点数を四捨五入して銘柄通貨の桁数に正規化します。

double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); 
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); 

ブレイクダウンスイングのインスタンスを定義したら、次にiBarsという名前の関数を定義します。この場合は、「_Symbol」と「period」という2つのパラメータを取ります。iBarsはbarsという整数値を返します。次に、変数totalBarsがiBars関数が返す値と等しいかどうかを確認します。等しい場合、関数は何もせずに戻ります。両者が等しくない場合、totalBarsの値はiBars関数が返すbarsの値に設定されます。

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;

ここでは、positionsTotal()関数を呼び出した結果が1より大きいかどうかを確認します。その場合、関数は何もせずに戻ります。そうでない場合、コードは次の行に進みます。「int number = MathRand()%,」という行は、閉じ括弧やセミコロンがないため、不完全に見えます。ランダムな整数を生成することが目的であると仮定すると、行は「int number = MathRand()% totalBars;」のように完成します。この行は、0からtotalBars(を含む)の値の間のランダムを生成し、変数numberに代入します。

 if (PositionsTotal() > 1) return;
   int number = MathRand()%

関数定義の全コードは以下の通りです。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
   
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   if (PositionsTotal() > 1) return;
   int number = MathRand()%

ここでは、取引の発注、チャート上のテキストラベルの作成、日々の利益の確認など、記事の主要機能の内訳を紹介します。 

1.取引執行 

このコンポーネントでは、numberという変数の値を調べ、その値に基づいてさまざまなアクションを起こします。

もしnumberが0なら、コードはobj-Tradeとして識別されるオブジェクトのBuyという名前のメソッドをトリガーします。このメソッドは5つのパラメータを必要とします。最初のパラメータは0.1、2番目は変数「_Symbol」、3番目は変数「ask」、4番目は「ask」の値から変数「_Point」の70倍を引いた値、5番目は「ask」の値に同じ変数「_Point」の70倍を足した値です。これは、bidとaskのスプレッドを考慮して、コードが現在のask価格よりわずかに低い価格で資産を購入しようとしていることを示します。

もし'number'が1なら、コードは同じオブジェクト'obj-Trade'に対して'Sell'という名前のメソッドを実行します。最初のパラメータは0.1、2番目は変数「_Symbol」、3番目は変数「bid」、4番目は「bid」の値に変数「_Point」の70倍を足した値、5番目は「bid」の値から同じ変数「_Point」の70倍を引いた値です。これは、コードが現在の買値より少し高い価格で資産を売ろうとしていることを意味し、買値と売値のスプレッドも考慮しています。これはnumberとして知られる変数の値を評価し、その値に基づいてさまざまなアクションを実行します。

if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }

2.テキスト作成

以前のコードでは、チャート上にラベルオブジェクトを作成するcreateTextという関数がありました。この関数を実行するには、オブジェクト名(objName)、テキスト内容(text)、ラベル配置のためのx座標とy座標(xおよびy)、テキスト色(clrTxt)、フォントサイズ(fontsize)などの特定のパラメータが必要です。これらの入力を利用して、この関数はチャートにラベルオブジェクトを作成し、その機能をカスタマイズし、チャートを更新します。さらに、ラベルの作成が成功したか失敗したかを示すブール値が関数から返されます。


bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP_COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

3. 毎日の利益確認

このMQL関数はcheckDailyProfitと呼ばれ、指定された日の合計利益を計算します。この関数はパラメータを取らず、以下のステップを実行します。

  • 変数total_day_profitを定義し、0に初期化します。
  • 現在時刻を取得し、TimeToString関数を使用して文字列に変換します。これはdate変数に格納されます。
  • 1日の始まりに1を足して1日の最初の時間を計算し、変数に保存します。
  • 毎日の時刻が現在の時刻より小さいかどうかを確認します。yesの場合、dayTimeを設定し、dayBalance変数に格納されているAcc_B関数を使用して現在の残高を計算します。
  • HistorySelect関数を使用してその日の履歴データを選択し、開始時刻と終了時刻をその日の開始時刻と終了時刻に設定します。
  • HistoryDealsTotal関数を使用してその日の取引の合計数を計算し、TotalDeals変数に格納します。
  • 履歴のトランザクションを調べ、トランザクションのエントリタイプがDEAL_ENTRY_OUTであるかどうかを確認します。Yesの場合、DEAL_PROFIT、DEAL_COMMISSION、DEAL_SWAPの値を加算して取引利益を計算し、total_day_profit変数に加算します。
  • ACCOUNT_BALANCEパラメータを持つAccountInfoDouble関数を使用して、現在の口座残高からtotal_day_profitを引くことにより、その日の開始残高を計算します。

この関数は、計算された開始残高をdouble値として返します。

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrL…

記事の関数の主要な構成要素の完全なコードは以下の通りです。

2;
   if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
}
//+------------------------------------------------------------------+
bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ-LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP-XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP-YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP-CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP-TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP-COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP-FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT-BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrRed);

以下はその結果です。

次は、ファースレッショルドロジックの例です。

ファースレスホールド

次は、ニアスレッシュホールドロジックの例です。

ニアスローホールド

次は、スレッシュホールドヒットロジックの例です。

スレスホールドヒット

ドローダウンリミッターを作成するための完全なコードは以下の通りです。

//+------------------------------------------------------------------+
//|                                       Daily Drawdown Limiter.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

int totalBars = 0;
double initialBalance = 0;
double dayBalance = 0;
datetime dayTime = 0;
bool isTradeAllowed = true;

// Functions to get account balance, equity, and currency
double Acc_B() {return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E() {return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S() {return AccountInfoString(ACCOUNT_CURRENCY);}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Initialize initial balance
   initialBalance = Acc_B();
   
   // Create dashboard texts
   createText("0","*** Daily Drawdown Limiter ***",30,30,clrBlack,13);
   createText("00","______________________________________",30,30,clrBlack,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrBlack,11);
   createText("2","Counters will be reset on Next Day Start.",70,65,clrBlack,10);
   createText("3","From: ",70,80,clrBlack,10);
   createText("4","Time Here",120,80,clrGray,10);
   createText("5","To: ",70,95,clrBlack,10);
   createText("6","Time Here",120,95,clrGray,10);
   createText("7","Current: ",70,110,clrBlack,10);
   createText("8","Time Here",120,110,clrGray,10);

   createText("9","ACCOUNT DRAWDOWN ============",70,130,clrPeru,11);
   createText("10","Account Initial Balance: ",70,145,clrBlack,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   createText("12","Tolerated DrawDown: ",70,160,clrBlack,10);
   createText("13","12.00 %",250,160,clrBlack,10);
   createText("14","Current Account Equity: ",70,175,clrBlack,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
   createText("16","Current Balance Variation: ",70,190,clrBlack,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18","DAILY DRAWDOWN ================",70,210,clrPeru,11);
   createText("19","Starting Balance: ",70,225,clrBlack,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("21","DrawDown Maximum Threshold: ",70,240,clrBlack,10);
   createText("22","5.00 %",270,240,clrBlack,10);
   createText("23","DrawDown Maximum Amount: ",70,255,clrBlack,10);
   createText("24","-"+DoubleToString((Acc_B()*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   createText("25","Current Closed Daily Profit: ",70,270,clrBlack,10);
   createText("26","0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27","Current DrawDown Percent: ",70,285,clrBlack,10);
   createText("28","0.00 %",270,285,clrGray,10);
   createText("29",">>> Initializing The Program, Get Ready To Trade.",70,300,clrBlue,10);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Deinitialization code here (if needed)
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Check daily profit and drawdown
   checkDailyProfit();
   
   // If trading is not allowed, exit function
   if (!isTradeAllowed) return;
   
   // Get current ask and bid prices
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   // Check for new bar
   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   // If more than one position, exit function
   if (PositionsTotal() > 1) return;
   
   // Random trade decision
   int number = MathRand()%2;
   Print(number);
   
   if (number == 0){
      obj_Trade.Buy(1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
  }
//+------------------------------------------------------------------+
//| Check daily profit and drawdown                                  |
//+------------------------------------------------------------------+
void checkDailyProfit() {
   
   double total_day_Profit = 0.0;
   datetime end = TimeCurrent();
   string sdate = TimeToString (TimeCurrent(), TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   // Reset daily balance and time at start of new day
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }

   // Calculate total daily profit
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();

   for(int i = 0; i < TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(Ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                                    + HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                                    + HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   
   double startingBalance = 0.0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   string day_profit_in_TextFormat = "";
   double daily_Profit_or_Drawdown = NormalizeDouble(((total_day_Profit) * 100/startingBalance),2);
   day_profit_in_TextFormat = DoubleToString(daily_Profit_or_Drawdown,2) + " %";
      
   // Update dashboard texts with new data
   createText("4",TimeToString(start),120,80,clrBlue,10);
   createText("6",TimeToString(to),120,95,clrBlue,10);
   createText("8",TimeToString(end),120,110,clrBlack,10);

   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrMediumBlue,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrMediumBlue,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrRed,10);
   }
   else if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrBlack,10);
   }

   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("24","-"+DoubleToString((dayBalance*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrMediumBlue,10);
      createText("28",day_profit_in_TextFormat,270,285,clrMediumBlue,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",day_profit_in_TextFormat,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrBlack,10);
      createText("28",day_profit_in_TextFormat,270,285,clrBlack,10);
   }
   
   // Check if drawdown limits are hit and update trading permission
   if (daily_Profit_or_Drawdown <= -5.00 ||((Acc_E()-initialBalance)/initialBalance)*100 < -12.00){
      createText("29",">>> Max ThreshHold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Max ThresHold Not Hit, Can Trade.",70,300,clrMediumBlue,10);
      isTradeAllowed = true;
   }
}

//+------------------------------------------------------------------+
//| Create text label on the chart                                   |
//+------------------------------------------------------------------+
bool createText(string objName, string text, int x, int y, color clrTxt,int fontSize) {
 ResetLastError();
     if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
        Print(__FUNCTION__,": failed to create the Label! Error code = ", GetLastError());
        return(false);
     }

   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT, text);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fontSize);
   //ObjectSetString(0,objName,OBJPROP_FONT, "Calibri");
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt);
   
   ChartRedraw(0);
   
   return(true); 
}

お疲れ様です。これで、取引口座の毎日の引き出し上限を確立することに基づいて外国為替取引のEAのための1日のドローダウンの制限を作成しました。


結論

この記事では、取引活動を監視し、取引状況、利益、特定の閾値に基づく取引許可を示す関連テキストラベルでチャートを更新します。チャート上の操作やテキストラベルの更新に関数を使用すると、記事が整理され、メンテナンスが簡単になります。MQL5で有名な日次ドローダウンリミッターFX取引戦略を自動化するために実装する必要がある基本的なステップを見てきました。ストラテジーの基本的な定義と説明をおこない、MQL5でどのように作成できるかを示しました。トレーダーは、示された知識を利用して、より複雑な日次ドローダウンリミッターシステムを開発することができます。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15199

添付されたファイル |
知っておくべきMQL5ウィザードのテクニック(第26回):移動平均とハースト指数 知っておくべきMQL5ウィザードのテクニック(第26回):移動平均とハースト指数
ハースト指数は、時系列データが長期間にわたってどれだけ自己相関しているかを示す指標です。ハースト指数は、時系列データの長期的な特性を捉えることがわかっているため、経済や金融に限らず、幅広い時系列分析において重要な役割を果たします。本稿では、ハースト指数を移動平均線と組み合わせることで、トレーダーにとって有用なシグナルをどのように得られるかを検討し、その潜在的なメリットに焦点を当てます。
多通貨エキスパートアドバイザーの開発(第7回):フォワード期間に基づくグループの選択 多通貨エキスパートアドバイザーの開発(第7回):フォワード期間に基づくグループの選択
以前は、個々のインスタンスの最適化が実施されたのと同じ期間においてのみ、共同運用の結果を改善する目的で、取引戦略インスタンスグループの選択を評価しました。フォワード期間中に何が起こるか見てみましょう。
Pythonを使ったEAとバックテストのための感情分析とディープラーニング Pythonを使ったEAとバックテストのための感情分析とディープラーニング
この記事では、EAで使用するPythonによる感情分析とONNXモデルを紹介します。あるスクリプトはTensorFlowで学習させたONNXモデルをディープラーニング予測用に実行し、別のスクリプトはニュースのヘッドラインを取得し、AIを使用して感情を数値化します。
MQLプロジェクトでJSON Data APIを使用する MQLプロジェクトでJSON Data APIを使用する
MetaTraderにはないデータを使用できることを想像してみてください。価格分析とテクニカル分析による指標からデータを得るだけです。取引力を一段と高めるデータにアクセスできることを想像してみてください。APIデータを通して他のソフトウェア、マクロ分析手法、超高度ツールの出力をMetaTraderを通じてミックスすれば、MetaTraderソフトウェアのパワーを倍増させることができます。この記事では、APIの使い方を教え、便利で価値のあるAPIデータサービスを紹介します。