証拠金維持率が一定水準を割り込むと、ブローカーによる強制ロスカットが近づきます。トレード画面を見ていない時間でもLINEで気づければ安心ですが、このEAのコードをそのまま動かすと、維持率が基準を下回っている間ずっと、毎ティック(1秒に何度も)LINE通知とアラートが送られ続けます。LINE Notify側の送信回数制限に引っかかったり、アラートのポップアップが画面を埋め尽くしてMT4の操作ができなくなったりする、実運用ではまず困る欠陥です。

この記事では、証拠金維持率を監視してLINEとアラートで知らせるEAの作り方を、通知を1回だけに抑える工夫と、コード中に残っていたもう2つの不具合の直し方とあわせて解説します。

指定の証拠金維持率以下になった時にLINEとアラートで通知するEA

今回は、指定の証拠金維持率以下になった場合にLINEとアラートで通知するEAを作っていきます。

メタエディタ(MetaEditor)を立ち上げる

メタエディタ(MetaEditor)を立ち上げましょう。今回は名前を「MagineCole-Alert-LINE」で作成します。

パラメーターを記述する

パラメーターを記述しましょう。

LINEに通知するためのアクセストークンを記述します。

input string Line_token = "";//LINEのアクセストークン

LINE Notifyの設定は以下の記事を参照してください。

【MQL】LINE Notifyを活用してLINEに通知を送るEA【上級編】

次に証拠金維持率以下になった場合にLINEに送りたいメッセージを記入しましょう。

input string Send_Massage = "証拠金維持率が基準を下回りました";//LINEに送りたいメッセージ

空文字のままだとLINE Notifyがメッセージ送信エラーを返すことがあるため、初期値として文言を入れています。

次にアラートとLINE通知のON/OFFを記述します。

input bool MarginAlert = true;//アラートのオン・オフ
input bool MarginLine = true;//LINE通知のオン・オフ

最後に証拠金維持率の基準を設定しておきましょう。

input int MarginAlertPer = 300;//証拠金維持率の基準(%)

これでパラメーターの記述は完了です。

input string Line_token = "";//LINEのアクセストークン
input string Send_Massage = "証拠金維持率が基準を下回りました";//LINEに送りたいメッセージ
input bool MarginAlert = true;//アラートのオン・オフ
input bool MarginLine = true;//LINE通知のオン・オフ
input int MarginAlertPer = 300;//証拠金維持率の基準(%)
Copy

OnTick()関数を記述する

OnTick()関数を記述していきましょう。まずは証拠金維持率を格納する変数を用意します。

double MarginPer = 0;//現在の証拠金維持率

次に証拠金維持率を計算します。

証拠金維持率の計算式は、「有効証拠金÷必要証拠金×100」で算出します。

AccountEquity()の関数は、現在アカウントの有効証拠金を返す関数です。

AccountMargin()の関数は、現在の保有ポジションを維持するために必要な金額(必要証拠金)を返す関数です。

MarginPer = AccountEquity()/AccountMargin()*100;//現在の証拠金維持率の計算

アラートがONで証拠金維持率が基準以下の場合に

if(MarginAlert && MarginAlertPer>=MarginPer)//アラートがONで証拠金維持率以下の場合

アラートを鳴らします。

Alert(DoubleToStr(MarginPer, 2) + "(%)");//アラートを鳴らす

同様にLINE通知がONで証拠金維持率が基準以下の場合に

if(MarginLine && MarginAlertPer>=MarginPer)//LINE通知がONで証拠金維持率以下の場合

LINEに通知を送ります。

LineNotify(Line_token,Send_Massage);//LineNotifyを呼び出し

ここまでで通知のロジック自体はできましたが、このまま動かすと2つの問題が残っています。次の2つのセクションで直していきます。

通知が連打され続けないようにする

上のコードのままOnTick()を動かすと、証拠金維持率が基準(MarginAlertPer)を下回っている間、つまり相場やポジションが変化して維持率が回復するまでのすべてのティックで、アラートとLINE通知が送られ続けます。

維持率が基準を下回った状態は数分〜数時間続くこともあり、その間ずっと通知が飛び続けると、LINE Notify側の送信回数制限に達して以降の通知が送れなくなったり、アラートのポップアップが積み上がってMT4の操作ができなくなったりします。

これを防ぐため、「直近ですでに通知済みかどうか」を覚えておくフラグを用意し、基準を下回った最初の1回だけ通知するようにします。維持率が基準を上回ったらフラグを戻し、次に下回ったときにまた通知できるようにします。

bool AlertSent = false;//直近で通知済みかどうか
  double MarginPer = 0;//現在の証拠金維持率
  MarginPer = AccountEquity()/AccountMargin()*100;//現在の証拠金維持率の計算

  if(MarginAlertPer>=MarginPer)
  {
    if(!AlertSent)//まだ今回の下回りで通知していない場合だけ通知する
    {
      if(MarginAlert)
        Alert(DoubleToStr(MarginPer, 2) + "(%)");//アラートを鳴らす

      if(MarginLine)
        LineNotify(Line_token,Send_Massage);//LineNotifyを呼び出し

      AlertSent = true;
    }
  }
  else
  {
    AlertSent = false;//維持率が回復したら次回また通知できるようにする
  }
Copy

ポジションが無い時のゼロ除算を防ぐ

証拠金維持率の計算式「AccountEquity()/AccountMargin()*100」は、ポジションを1つも持っていないときに問題が起こります。AccountMargin()(必要証拠金)はポジションが無ければ0を返すため、0で割ることになってしまいます。

ポジションが無い状態は普通にあり得るので、AccountMargin()が0のときは計算・通知処理そのものをスキップするようにします。

if(AccountMargin()==0)
   return;//ポジションが無い場合はゼロ除算になるためスキップ

LineNotify関数を記述する

LineNotify関数を記述しましょう。変数をまず設定します。

  string headers;
  char data[], result[];

headersの変数にコンテンツタイプを使用した単純なフォームを入れます。

headers="Authorization: Bearer "+Token+"\r\nContent-Type: application/x-www-form-urlencoded\r\n";

元のコードはこの「Content-Type:」の部分が抜けていました。LINE NotifyのAPIはフォーム形式でのPOSTを期待しているため、ヘッダーに明記しておきます。

ArrayResize()は配列のサイズを新しく設定する関数、StringToCharArray()は文字列をchar型配列にコピーする関数です。それらの情報を以下のように記載していきます。

ArrayResize(data,StringToCharArray("message="+Massage,data,0,WHOLE_ARRAY,CP_UTF8)-1);

WebRequest()関数は指定したサーバーにHTTPリクエストを送ります。書式は次のとおりです。

int WebRequest(
   string method,          // HTTPメソッド
   string url,             // 送信先URL
   string headers,         // 送信するヘッダー
   int    timeout,         // タイムアウト(ミリ秒)
   char   &data[],         // 送信データ
   char   &result[],       // 受信結果の格納先
   string &result_headers  // 受信ヘッダーの格納先
);

元のコードは、6番目の引数(受信結果を格納するresult配列)に、誤って5番目の送信データと同じdata配列を渡していました。せっかく用意したresult配列が使われておらず、送信データを上書きしてしまう不具合です。あわせて、4番目のタイムアウトが0(実質即タイムアウト)になっていたため、これも5000(5秒)に直します。

なお、7番目の引数(受信したレスポンスヘッダーの格納先)には、リクエストヘッダーの構築に使ったheaders変数をそのまま使い回しています。WebRequest()の実行後にheadersの中身はレスポンスヘッダーで上書きされますが、この関数ではその後headersを参照しないため実害はありません。厳密に分けたい場合は、別途`string response_headers;`を用意して7番目の引数に渡してください。

int res = WebRequest("POST", "https://notify-api.line.me/api/notify", headers, 5000, data, result, headers);

WebRequest()を実行する前に、MT4の「ツール」→「オプション」→「Expert Advisors」タブで、`https://notify-api.line.me` をWebRequestの許可URLリストに追加しておいてください。登録していないと、この時点でres=-1が返り、通知が送信されません(後述のコンパイル後の確認でもう一度触れます)。

resの値が-1であれば、

if(res==-1)

エラーコードを出します。

 Print("Error in WebRequest. Error code =",GetLastError()); 
 MessageBox("Add the address 'https://notify-api.line.me' in the list of allowed URLs on tab 'Expert Advisors'","Error",MB_ICONINFORMATION);

これでLineNotify関数の記述は完了です。

void LineNotify(string Token,string Massage)
{
   string headers;//ヘッダー
   char data[], result[];//送信データ、受信結果

   headers="Authorization: Bearer "+Token+"\r\nContent-Type: application/x-www-form-urlencoded\r\n";
   ArrayResize(data,StringToCharArray("message="+Massage,data,0,WHOLE_ARRAY,CP_UTF8)-1);
   int res = WebRequest("POST", "https://notify-api.line.me/api/notify", headers, 5000, data, result, headers);
   if(res==-1) //エラーの場合
     {
      Print("Error in WebRequest. Error code =",GetLastError());
      MessageBox("Add the address 'https://notify-api.line.me' in the list of allowed URLs on tab 'Expert Advisors'","Error",MB_ICONINFORMATION);
     }
}

コンパイルする

コンパイルして実際の動きを確認して動いていたら完了です。

ちなみにWebRequest()を使う通知処理は、リアルトレードの場合は使えますが、バックテスト時には使えません。バックテスト時にはコメントアウトなどしてリアルトレード時のみ使うようにしてください。

また、WebRequest()を実際に動かす前に、MT4の「ツール」→「オプション」→「Expert Advisors」タブで、`https://notify-api.line.me` をWebRequestの許可URLリストに追加しておく必要があります。登録していないとResまたはエラーコードが返り、通知が送信されません。

ソースコード全体

ここまでの修正をすべて反映した、実際にMetaEditorでコンパイルできる状態のコードです。

//+------------------------------------------------------------------+
//|                                        MagineCole-Alert-LINE.mq4 |
//|                     Copyright 2020, FX-EA System Project Creator |
//|                        https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, FX-EA System Project Creator"
#property link      "https://creator.fx-ea-system-project.com/"
#property version   "1.01"
#property strict

/*パラメーター*/
input string Line_token     = "";//LINEのアクセストークン
input string Send_Massage   = "証拠金維持率が基準を下回りました";//LINEに送りたいメッセージ
input bool   MarginAlert    = true;//アラートのオン・オフ
input bool   MarginLine     = true;//LINE通知のオン・オフ
input int    MarginAlertPer = 300;//証拠金維持率の基準(%)

bool AlertSent = false;//直近で通知済みかどうか

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

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---
  //ポジションが無い場合はAccountMargin()が0になりゼロ除算になるためスキップ
  if(AccountMargin()==0)
     return;

  double MarginPer = 0;//現在の証拠金維持率
  MarginPer = AccountEquity()/AccountMargin()*100;//現在の証拠金維持率の計算

  if(MarginAlertPer>=MarginPer)//証拠金維持率が基準以下の場合
  {
    if(!AlertSent)//まだ今回の下回りで通知していない場合だけ通知する
    {
      if(MarginAlert)
        Alert(DoubleToStr(MarginPer, 2) + "(%)");//アラートを鳴らす

      if(MarginLine)
        LineNotify(Line_token,Send_Massage);//LineNotifyを呼び出し

      AlertSent = true;
    }
  }
  else
  {
    AlertSent = false;//維持率が回復したら次回また通知できるようにする
  }
}
//+------------------------------------------------------------------+

void LineNotify(string Token,string Massage)
{
   string headers;//ヘッダー
   char data[], result[];//送信データ、受信結果

   headers="Authorization: Bearer "+Token+"\r\nContent-Type: application/x-www-form-urlencoded\r\n";
   ArrayResize(data,StringToCharArray("message="+Massage,data,0,WHOLE_ARRAY,CP_UTF8)-1);
   int res = WebRequest("POST", "https://notify-api.line.me/api/notify", headers, 5000, data, result, headers);
   if(res==-1) //エラーの場合
     {
      Print("Error in WebRequest. Error code =",GetLastError());
      MessageBox("Add the address 'https://notify-api.line.me' in the list of allowed URLs on tab 'Expert Advisors'","Error",MB_ICONINFORMATION);
     }
}
Copy

まとめ

今回はアラートとLINEに通知を送るという2つを行いましたが、これにメールアドレスへの送信なども追加で改良することが可能です。

関連記事