FXトレードMT4運用のモバイル通知(Discord)の設定方法

見出し画像

こんにちは。

FXトレード戦略の本番環境でのフォワードテストを実施しています。
モバイルでのモニタリングのためこちら ↓ の記事で MT4アプリの活用をご紹介しました。

この記事では、モニタリングをさらに改善した私のやり方について共有いたします。
Discordへメッセージ、および、チャート画像の送信をするというものです。

背景

『高い勝率で利小損大』を乗り切る方針のFXトレード戦略を作成・運用しています。
『損小利大』とは全く逆の戦略です。

このトレードの運用を始めたばかりなので、しっかりモニタリングをする必要があります。
先日の記事でMT4アプリを利用したモバイルでのモニタリングが非常にいい感じにできることをご紹介しました。
実際にモニタリングを行ったところ、いくつか改善点がでてきました。
今回は、この改善のために私が実施した内容、スクリプトを共有いたします。
アプリ、ツールなどは利用せず直接EAスクリプトを修正する方法です。
この改善により、以下のようなイベント発生時にイベント時のマーケット情報、また、チャート情報を即時に通知されるようにできます。

  • 買いエントリーイベント

  • スプレッドが広く、エントリーをスキップした際の通知

  • 利確ポイント発生による売却イベント

  • ポジション保持時間による手仕舞いの売却イベント

今回、LINEではなく Discordにした理由は以下の二つあります。

ということで、Discordでの通知をすることにしました。
"MT4  Discord  通知"でネット検索すると、いろいろと情報がでてきます。
私がみた限りでは有償のツールを前提とするものが多く、かつ、あまり柔軟に変更できるようなものはありませんでした。
また、多くのツールではインジケーター利用が前提となっていました。
私はEAスクリプトみで自動取引をしており、インジケーターを利用せず直接EAスクリプトに仕込む形での実装しました。
実装にあたり、あまり参考になりブログ、ネット情報がなかったため、AIに相談しながらやりましたが、チャート画像の連携部分で非常に苦労しました。

MT4 EAからのDiscord通知の事前準備

事前準備として、以下の設定を準備していただく必要があります。
この記事では、事前準備の詳細は省きますが、ネット検索にて必要な設定などは確認できるかと思います。

  • DiscordのWebhook URL発行

  • MT4の通知許可を設定

二点目のMT4通知許可については、➀ツール > オプション > エキスパートアドバイザ画面での設定と②チャートに仕込んだEAの設定の二か所あるのでご注意ください。

Discord通知するEAスクリプト例 

実際のスクリプトは、OrderSend()などイベント発生の中でDiscord通知を呼び出すトリガー部分とその呼び出しにより、実際にDiscordに連携する部分に分かれます。

私が利用しているスクリプトをベースにご自身の要望に合わせて手直しをしていただければいいかと思います。
AIを活用して修正することもできると思いますが、EAスクリプト言語Pythonなどに比べるとAIから提案されるコードはそれほど品質が高くないのでご注意ください。

トリガー側の例

SendInfoAndChartToDiscord()が呼び出し部分です。

   string msg; 

   if (spreadInPips <= maxSpreadInPips ) {
      ticket = OrderSend(Symbol(), OP_BUY, lotSize, askPrice, 2, stopLossPrice, takeProfitPrice, "SimpleEA Buy", MAGIC_NUMBER, 0, clrGreen);
      if (ticket < 0) {
          Print("## Error opening buy order: ", GetLastError());
      } else {
          Print("## Buy order opened: ", ticket, 
          " NY時間: ", TimeToString(NYTime, TIME_DATE|TIME_MINUTES|TIME_SECONDS), " spread= ", spreadInPips);

          msg = "[買いエントリー発生] price=" + askPrice + " tp=" + takeProfitPrice + " spread=" + spreadInPips + "\n";
          SendInfoAndChartToDiscord(msg);
      }
   }
copy


Discord呼び出し側の例

このままコピペで利用いただくこともできるかと思います。
webhook_url の XXXXXXXXX部分のみご自身で準備した webhook URLに変更してください。
また、SendChartImage()、AlertDiscord()を個別に呼び出し、テキストメッセージのみ、または、チャート画像のみを個別に送信・通知することもできますので、必要に応じ手直しください。

//#########################################################################################
// Discord Webhook URL
input string webhook_url = "https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

//+------------------------------------------------------------------+
//| 市場情報を取得                                                    |
//+------------------------------------------------------------------+
string GetMarketInfo()
{
    string info = "\n";
    
    // 基本情報
    info += "Symbol: " + Symbol() + "\n";
    info += "Time: " + TimeToString(TimeLocal(), TIME_DATE|TIME_SECONDS) + "\n";
    
    // 価格情報
    double bid = MarketInfo(Symbol(), MODE_BID);
    double ask = MarketInfo(Symbol(), MODE_ASK);
    double spread = (ask - bid) / Point;
    
    info += "Bid: " + DoubleToString(bid, Digits) + "\n";
    info += "Ask: " + DoubleToString(ask, Digits) + "\n";
    info += "Spread: " + DoubleToString(spread, 1) + " pips\n\n";
    
    // 日足情報
    info += "Daily Stats:\n";
    info += "High: " + DoubleToString(iHigh(Symbol(), PERIOD_D1, 0), Digits) + "\n";
    info += "Low: " + DoubleToString(iLow(Symbol(), PERIOD_D1, 0), Digits) + "\n";
    double daily_range = (iHigh(Symbol(), PERIOD_D1, 0) - iLow(Symbol(), PERIOD_D1, 0)) / Point;
    info += "Range: " + DoubleToString(daily_range, 1) + " pips\n";
    
    return info;
}

//+------------------------------------------------------------------+
//| チャート画像を保存して送信                                        |
//+------------------------------------------------------------------+
bool SendChartImage()
{
    string filename = "chart_" + Symbol() + "_" + TimeToStr(TimeLocal(), TIME_DATE|TIME_SECONDS) + ".png";
    StringReplace(filename, ":", "-");
    StringReplace(filename, " ", "_");

    if(!ChartScreenShot(0, filename, 460, 320, ALIGN_RIGHT))
    {
        Print("## スクリーンショットの取得エラー");
        return false;
    }
    
    //string file_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL4\\Files\\" + filename;
    //int file_handle = FileOpen(file_path, FILE_READ|FILE_BIN);
    int file_handle = FileOpen(filename, FILE_READ|FILE_BIN);
    if(file_handle == INVALID_HANDLE)
    {
        Print("## ファイルオープンエラー。 Error code: ", GetLastError());
        return false;
    }
    
    int file_size = FileSize(file_handle);
    uchar file_data[];
    ArrayResize(file_data, file_size);
    FileReadArray(file_handle, file_data, 0, file_size);
    FileClose(file_handle);
    
    string boundary = "---------------------------" + IntegerToString(TimeLocal());
    
    string headers = "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";
    
    string data = "";
    data += "--" + boundary + "\r\n";
    data += "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n";
    data += "Content-Type: image/png\r\n\r\n";
    
    uchar post_data[];
    StringToCharArray(data, post_data, 0, StringLen(data), CP_UTF8);
    ArrayCopy(post_data, file_data, ArraySize(post_data));
    
    string end_boundary = "\r\n--" + boundary + "--\r\n";
    uchar end_data[];
    StringToCharArray(end_boundary, end_data, 0, StringLen(end_boundary), CP_UTF8);
    ArrayCopy(post_data, end_data, ArraySize(post_data));
    
    char result[];
    string result_headers;
    
    ResetLastError();
    int res = WebRequest(
        "POST",
        webhook_url,
        headers,
        5000,
        post_data,
        result,
        result_headers
    );
    
    FileDelete(filename);
    
    if(res == -1)
    {
        Print("## WebRequestエラー: ", GetLastError());
        return false;
    }
    
    return true;
}

//+------------------------------------------------------------------+
//| Discord通知関数                                                   |
//+------------------------------------------------------------------+
bool AlertDiscord(const string url, const string text)
{
    int status_code;
    string headers;
    char data[];
    char result[];
    
    StringToCharArray("content=" + text, data, 0, WHOLE_ARRAY, CP_UTF8);
    
    status_code = WebRequest("POST", url, NULL, NULL, 5000, data, 0, result, headers);
   
    if(status_code == -1)
    {
        Print(GetLastError());
        return(false);
    }
    
    return(true);
}

bool SendInfoAndChartToDiscord(string title)
{
   if(IsTesting()
    || (AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_REAL)
   ) return(false);
    
   string msg;
   
   // Discordに通知
   msg = "```\n"; // Discordのコードブロック形式
   msg += "実行EA:" + WindowExpertName() + "\n";
   msg += title + "\n";
   msg += GetMarketInfo();
   msg += "```";
 
   AlertDiscord(webhook_url, msg);
 
   // チャート画像を送信
   SendChartImage();
   
   return true;
}
copy

コードの詳細説明は省きますが、質問などはコメントいただければ、できるだけ回答いたします。

Discord通知の例

Discord側に送られてくる通知イメージの例は以下のとおりです。

画像

なかなかいい感じに通知が届くようなりました。
これで、モバイルでのモニタリングは非常に快適になり、FXトレードの成果にもつながることを期待したいと思います。

最後に

この記事はお気に入りいただけましたでしょうか?
内容お役にたちましたらうれしく思います。
また、サポートなど応援いただけましたら幸いです。