【MT4対応】LINE通知でトレード情報を自動送信するインジケーターを作る方法|EA監視をスマホでリアルタイム把握!

LINE通知① (640 x 360 px)
MT4ノウハウ・インジケータ
記事内に商品プロモーションを含む場合があります
スポンサーリンク

はじめに

MT4で自動売買や裁量トレードを行う際、「どのタイミングでエントリー・決済したのか」をリアルタイムで把握できない…という悩みは多いです。
EA運用中は常にチャートを開けないため、トレードチャンスを逃すこともしばしば。

そこで今回は、MQL4で作るLINE通知インジケーターを紹介します。
設定すれば、EAや裁量トレードの売買タイミングを LINEアプリに即通知!
スマホひとつでポジション状況を確認できるようになります。

💡チャート画像も自動送信する上位版(画像付き通知)は以下noteで公開中👇

なぜLINE通知インジケーターを作ったのか

MT4標準の「アラート機能」や「メール通知」は便利ですが、

  • 通知が遅れる
  • 設定が複雑
  • メールアプリを開く手間

といった課題がありました。

筆者自身もEA運用中にチャンスを逃した経験があり、
トレード発生をスマホに即知らせたい」という目的で、Messaging APIを利用したLINE通知機能を開発しました。


⚙️ インジケーターの概要と機能

このインジケーターは、MT4で新しいポジションが発生・決済されたタイミングで、自動的に通知を送ります。ポジションの有無で判定しているので、市販EAにも適用可能です。
通知内容は以下の通りです👇

  • 通貨ペア名
  • ロット数
  • 売買区分(買い/売り)
  • 価格
  • マジックナンバー
  • コメント
  • 損益(決済時)

通知方法はパラメータで選択可能です:

パラメータ内容
通知方法トレード通知方法(LINE/メール/通知なし)
(LINE)チャネルアクセストークンMessaging APIのチャネルアクセストークン  
(LINE)ユーザーIDMessaging API設定時に発行されるID
マジックナンバー監視するEAのマジックナンバー(空白で全て対象)

💬 通知イメージ(実際のLINEメッセージ)

MT4からLINEにメッセージを送信する

エントリー通知例:

【OANDA JAPAN】 2025.10.05 18:34
Lot:0.10 ▲買い注文 USDJPY 149.230
MN:12345 TestEA

決済通知例:

【OANDA JAPAN】 2025.10.05 22:15
Lot:0.10 ▽売り決済 USDJPY 149.320
損益:+900

EAが自動売買を行うたびに、このような通知がLINEに届きます。
スマホ通知がONなら、外出中でも即時に把握可能です。

📩 ダウンロードと設定方法

今回は合計2ファイルが必要となります。

📂 フォルダ構成(LINE_CALLzip)
├─ README.txt
└─ Files/
  └─ S_LINE_API_TXT.bat
└─ Indicators/
  └─ LINE_Send_TradeAlert_Harf.mq4 // ソースコード(学習・改造用)
  └─ LINE_Send_TradeAlert_Harf.ex4 // コンパイル済み実行ファイル

以下のファイルをダウンロードして各フォルダに追加してください。
(ファイル名:LINE_CALL.zip

📥 LINE_CALL.zip をダウンロード

設置手順

  1. MT4を開く
  2. 「ファイル」→「データフォルダを開く」
  3. MQL4 → Indicators フォルダに「LINE_Send_TradeAlert_Harf.ex4 」をコピー
  4. MQL4 → Files フォルダに「S_LINE_API_TXT.bat」ファイルをコピー
  5. MT4を再起動し、ナビゲーターからチャートに適用
  6. パラメータ設定を行う。
    通知方法・・・任意
    (LINE)チャネルアクセストークン・・・登録したアクセストークンを設定してください
    (LINE)ユーザーID・・・ユーザーIDを設定してください
    マジックナンバー・・・任意

🧩 ソースコード解説

LINE_Send_TradeAlert_Harf.mq4

//+------------------------------------------------------------------+
//|                                    LINE_Send_TradeAlert_Harf.mq4 |
//|                                  Copyright © 2020-2025 ぷろぐらむFX |
//|                                             https://fx-prog.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2020-2025 ぷろぐらむFX"
#property link      "https://fx-prog.com/"
#property version   "1.00"
#property strict
#property indicator_chart_window


#import "shell32.dll"
   int ShellExecuteW(int hWnd,string lpVerb,string lpFile,string lpParameters,string lpDirectory,int nCmdShow);
#import

enum EnumAreart
{
   mail,       // メール
   line,       // LINE
   sinai,       // 通知しない   
};

input EnumAreart AreartFlg =  line;                                     //通知方法
input string LineToken = "c+・・・";                                       //(LINE)チャネルアクセストークン
input string ID = "U3・・・0";                                             //(LINE)ユーザーID
input string MagicNo = "";                                              //マジックナンバー(空白で全て対象)

int Save_ticket[10];    //ポジションのチケット番号保有用(2だと3ポジまで) 5ポジ持たせたい場合は4にすること

string objectName = "LINE_C";
string str_Ticket ="";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   //ラベルオブジェクトの作成
   if(!ObjectCreate(0, objectName, OBJ_LABEL, 0, 0, 0))
   {
      //return(INIT_FAILED);
   }
   // 座標位置
   ObjectSetInteger(0,objectName, OBJPROP_CORNER, CORNER_LEFT_LOWER);
   ObjectSetInteger(0,objectName, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
   ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, 5);
   ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, 5);
   
   // 色
   ObjectSetInteger(0, objectName, OBJPROP_COLOR, clrBlue);
   // 読み取り専用
   ObjectSetInteger(0, objectName, OBJPROP_READONLY, true);
   // 選択不可
   ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);
   // フォント名
   ObjectSetString(0, objectName, OBJPROP_FONT, "MS ゴシック");
   // フォントサイズ
   ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, 12);
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){
//---
   ObjectDelete(0,objectName); 
  
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
string Posision_ticket[10]; //ポジションのチケット番号保有用(10だと11ポジまで)
bool mochiFlg =false;
string dataBuff[10];  //マジックナンバー保管用

   int spc = StringSplit(MagicNo, ',', dataBuff);
   
   for(int cnt = 0; cnt < OrdersTotal(); cnt++){
      if(OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES) == true){
      
         //既に通知済みかチェック
         int searchCnt = StringSplit(str_Ticket,',',Posision_ticket);
         
         for(int c = 0;c < searchCnt;c++){
            if(OrderTicket() == StringToInteger(Posision_ticket[c])){
               mochiFlg =true;
            }
         }
         
         if(mochiFlg==false){

            string OP_BUF = "";
            if(OrderType()==OP_BUY){
               OP_BUF =" ▲買い";
            }else{
               OP_BUF =" ▲売り";
            }
               
            string mes = "【" + AccountCompany() +  "】" + TimeToStr(ChangeJPtime(TimeHour(OrderOpenTime())), TIME_SECONDS) + " Lot:" + DoubleToStr(OrderLots(),2) + OP_BUF + "注文 " + OrderSymbol() + " " + DoubleToString(OrderOpenPrice(), 5) + " MN:" + IntegerToString(OrderMagicNumber()) + " " + OrderComment();


            if(spc==0){
               if(AreartFlg==line){
                  SendTextToLine(mes);
               }else if(AreartFlg==mail){
                  SendMail("MT4-トレード通知",mes);
               }
            }else{
               for(int mg = 0; mg < spc; mg++){
               
                  if(IntegerToString(OrderMagicNumber()) == dataBuff[mg]){
                     if(AreartFlg==line){
                        SendTextToLine(mes);
                     }else if(AreartFlg==mail){
                        SendMail("MT4-トレード通知",mes);
                     }
                  }
               }
            }
            
            Print(mes);
            
            str_Ticket= str_Ticket + IntegerToString(OrderTicket()) + ",";
         }
         
         mochiFlg =false;
      }
   }
        
   

   //既に通知済みかチェック
   int searchCnt = StringSplit(str_Ticket,',',Posision_ticket);

   for(int c = 0;c < searchCnt;c++){

      for(int cnt = OrdersHistoryTotal(); cnt > 0; cnt--){
         if(OrderSelect(cnt-1,SELECT_BY_POS,MODE_HISTORY) == true){
            if(IntegerToString(OrderTicket()) == Posision_ticket[c]){
               
               string OP_BUF = "";
               if(OrderType()==OP_BUY){
                  OP_BUF =" ▽買い";
               }else{
                  OP_BUF =" ▽売り";
               }


               
               string mes =  "【" + AccountCompany() +  "】" + TimeToStr(ChangeJPtime(TimeHour(OrderCloseTime())), TIME_SECONDS) + " Lot:" + DoubleToStr(OrderLots(),2) + OP_BUF + "決済 " + OrderSymbol() + " " + DoubleToString(OrderClosePrice(), 5) + " MN:" + IntegerToString(OrderMagicNumber()) + " " + OrderComment() + " 損益:" + DoubleToString(OrderProfit(), 2);
               
               if(spc==0){
                  if(AreartFlg==line){
                     SendTextToLine(mes); 
                  }else if(AreartFlg==mail){
                     SendMail("MT4-トレード通知",mes);
                  }
               }else{
               for(int mge = 0; mge < spc; mge++){
               
                  if(IntegerToString(OrderMagicNumber()) == dataBuff[mge]){
                     if(AreartFlg==line){
                        SendTextToLine(mes); 
                     }else if(AreartFlg==mail){
                        SendMail("MT4-トレード通知",mes);
                     }
                  }
               }
               }
   
               Print(mes);
               
               StringReplace(str_Ticket,Posision_ticket[c]+",","");
               
               break;
            }
         }
      }
   }   
   
   if(AreartFlg==line){
      ObjectSetString(0, objectName, OBJPROP_TEXT, "LINE-CALL(LINE)");
   }else if(AreartFlg==mail){
      ObjectSetString(0, objectName, OBJPROP_TEXT, "LINE-CALL(MAIL)");   
   }else{
      ObjectSetString(0, objectName, OBJPROP_TEXT, "LINE-CALL(OFF)");   
   }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

//メッセージバッチ立ち上げ
void SendTextToLine(string message) {
   string batPath = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL4\\Files\\S_LINE_API_TXT.bat";

   // バッチにパラメータを全て渡す(順番注意)
   string params = 
      "\"" + LineToken + "\" " +
      "\"" + ID + "\" " +
      "\"" + message + "\"";

   int res = ShellExecuteW(0, "open", batPath, params, "", 1);
   Print("SendTextToLine result:", res);
}

//+------------------------------------------------------------------+
//|【関数】日本時間(JST)をGMTに変換                                   |
//|                                                                  |
//|【引数】START_HOUR: JSTの0-23                                     |
//|【戻値】cTime: GMT 0-23                                           |
//+------------------------------------------------------------------+
int ChangeJPtime(int START_HOUR)
{
   int diff = IsSummerTime() ? -9 : -9;  // 日本はサマータイムないので実質いつも-9
   // ただし夏時間のGMT基準の考えがある場合はここで変える
   int cTime = (START_HOUR + diff + 24) % 24;  // マイナスを防ぐため+24
   return cTime;
}

//+------------------------------------------------------------------+
//| 【関数】サマータイム判定(米国ルール)                             |
//| 3月第2日曜~11月第1日曜までをサマータイムとする                   |
//+------------------------------------------------------------------+
bool IsSummerTime()
{
   int year = TimeYear(TimeCurrent());
   
   // 3月第2日曜
   datetime marchFirst = StringToTime(StringFormat("%04d.%02d.01 00:00", year, 3));
   int marchWeekday = TimeDayOfWeek(marchFirst); // 0=日曜
   int marchDaysToFirstSunday = (7 - marchWeekday) % 7;
   datetime summerStart = marchFirst + (marchDaysToFirstSunday + (2 - 1) * 7) * 86400;
   
   // 11月第1日曜
   datetime novFirst = StringToTime(StringFormat("%04d.%02d.01 00:00", year, 11));
   int novWeekday = TimeDayOfWeek(novFirst);
   int novDaysToFirstSunday = (7 - novWeekday) % 7;
   datetime summerEnd = novFirst + (novDaysToFirstSunday + (1 - 1) * 7) * 86400;
   
   return (TimeCurrent() >= summerStart && TimeCurrent() < summerEnd);
}

MQL4のOnCalculate内で、

  • 現在のポジションを走査
  • 新規チケット番号を検出
  • LINE API用バッチファイルへ送信

という流れで通知を実現しています。

SendTextToLine()関数に、LINE通知するメッセージを渡してそこからバッチファイルを呼び出しています。

S_LINE_API_TXT.bat

@echo off
setlocal

set "TOKEN=%1"
set "GROUP_ID=%2"
set "MESSAGE=%~3"

set "JSON_DATA={\"to\":\"%GROUP_ID%\",\"messages\":[{\"type\":\"text\",\"text\":\"%MESSAGE%\"}]}"
curl -X POST -H "Authorization: Bearer %TOKEN%" -H "Content-Type: application/json" -d "%JSON_DATA%" https://api.line.me/v2/bot/message/push
endlocal

MT4側から呼び出されるので、アクセストークン・ユーザーID・メッセージを受け取りMessaging API側で利用する形です。


【最新版】LINE公式アカウント経由でMessaging APIチャネルを作成する手順・実装

※2024年9月以降、LINE Developersから直接チャネルを作成する仕様は廃止されました。
現在は「LINE公式アカウント」を作成し、その管理画面からMessaging APIを有効化する手順に変更されています。


STEP①:LINE公式アカウントを作成

  1. LINEビジネスIDに登録
  2. アカウント情報を入力し、公式アカウントを作成
  3. 作成後は「LINE Official Account Manager」で管理可能になります。

STEP②:Official Account ManagerでMessaging APIを有効化

  1. 公式アカウントの設定画面を開く
  2. 左メニューから「Messaging API」を選択
  3. Messaging APIを利用する」ボタンをクリックし、規約に同意して有効化

💡この操作により、裏側でMessaging APIチャネルが自動的に作成されます。


STEP③:アクセストークンなどの取得

有効化後に以下の情報を取得します👇

  • チャネルアクセストークン(長期版)
  • ユーザーID(自分側)

これらをMT4側の設定画面に入力することで、通知機能が連携されます。

例)以下の情報が必要になります。

チャネルアクセストークン(長期版)画像
ユーザーID(通知先)画像

💬 ここまででMessaging APIの設定が完了しました。


スポンサーリンク

💡補足:LINEのMessaging APIの無料プランについて

  • 1チャネルあたり 月200通まで無料送信可能
    上限を超えると送信エラーになります
  • **ライトプラン(月5,000円)**では、最大5,000通まで送信可能です

⚠️ LINEにメッセージを送る時にハマった事

LINEにメッセージを送る際、唯一ハマったことがあります。

それは、CRYPT_E_NO_REVOCATION_CHECK (0x80092012)「失効の関数は証明書の失効を確認できませんでした。」というエラーが出てLINEにメッセージを送信できなかったことです。

検索しても、証明書関連の解決策が出てきて『自分は証明書とか特に発行してないしなー』という感じでしたが、原因がわかりました。

失効の関数は証明書の失効を確認できませんでした解説

原因は、ウィルス対策ソフトでした。例外のWEBサイトに『https://api.line.me/*』を追加する事で解決できました。

✅ まとめ

  • EAや裁量トレードをリアルタイム監視できる
  • MagicNo指定で特定EAのみ監視可能
  • 改造次第で、シグナル通知にも使える
  • さらにバッチ経由で画像送信も拡張可能

LINE通知によって、EAの挙動を見逃すことがなくなります。
「EAを動かしてるけど、どのタイミングで注文されたか分からない」
──そんな悩みを、このインジケータで一気に解消しましょう!

💡 チャート画像も自動送信する上位版は、こちらのnoteで配布中です👇

コメント

タイトルとURLをコピーしました