🚀 AI One-Click Generation of mql4 Strategy Code

How to Make Mql4 EAs Communicate Using EventChartCustom()

mql4 | Published on: 2025/12/3
The following content is generated by EasyQuant .

Problem Description

How can I make one trading program send a signal to another program running on the same chart?

Solution

To send a signal from one program (like an Indicator or a Panel) to another program (like an Expert Advisor) running on the same chart in MQL4, the most efficient and modern method is using Custom Chart Events.

This method uses the EventChartCustom() function to broadcast a message and the OnChartEvent() handler to receive it. It is event-driven, meaning the receiver reacts immediately without needing to constantly check (poll) for signals in OnTick.

Method: Custom Chart Events

In this scenario, we will create two programs:

  1. The Sender (Indicator): Creates a button on the chart. When clicked, it sends a custom event signal.
  2. The Receiver (Expert Advisor): Listens for the event and executes an action (e.g., prints to the log or opens a trade) when the signal is received.

1. The Sender (Indicator)

Save this code as SignalSender.mq4 in your MQL4/Indicators folder.

//+------------------------------------------------------------------+
//|                                                 SignalSender.mq4 |
//|                                     Copyright 2023, MetaQuotes   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property strict

// Define a unique ID for our custom event (0-65535)
#define MY_CUSTOM_EVENT_ID 12345

string btnName = "btnSendSignal";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // Create a button to trigger the signal manually
   if(ObjectFind(0, btnName) < 0)
   {
      ObjectCreate(0, btnName, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0, btnName, OBJPROP_XDISTANCE, 20);
      ObjectSetInteger(0, btnName, OBJPROP_YDISTANCE, 20);
      ObjectSetInteger(0, btnName, OBJPROP_XSIZE, 100);
      ObjectSetInteger(0, btnName, OBJPROP_YSIZE, 30);
      ObjectSetString(0, btnName, OBJPROP_TEXT, "Send Signal");
      ObjectSetInteger(0, btnName, OBJPROP_COLOR, clrWhite);
      ObjectSetInteger(0, btnName, OBJPROP_BGCOLOR, clrRed);
      ObjectSetInteger(0, btnName, OBJPROP_BORDER_COLOR, clrNone);
      ObjectSetInteger(0, btnName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, btnName, OBJPROP_HIDDEN, true); // Hide from object list
      ObjectSetInteger(0, btnName, OBJPROP_STATE, false);
   }
   
   ChartRedraw();
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(0, btnName);
}

//+------------------------------------------------------------------+
//| 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[])
{
   // Indicators need OnCalculate, even if empty
   return(rates_total);
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // Check if the object clicked is our button
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == btnName)
   {
      Print("Sender: Button clicked. Sending signal...");
      
      // Reset button state to unpressed
      ObjectSetInteger(0, btnName, OBJPROP_STATE, false);
      ChartRedraw();

      // --- SEND THE SIGNAL ---
      // chart_id: 0 (current chart)
      // event_id: MY_CUSTOM_EVENT_ID (offset from CHARTEVENT_CUSTOM)
      // lparam:   Can be any integer (e.g., 1 for Buy, 2 for Sell)
      // dparam:   Can be any double (e.g., price or lot size)
      // sparam:   Can be any string (e.g., a description)
      
      long signalType = 1; // Let's say 1 = Buy Signal
      double price = Ask;
      string desc = "Manual_Entry";
      
      EventChartCustom(0, MY_CUSTOM_EVENT_ID, signalType, price, desc);
   }
}
//+------------------------------------------------------------------+

2. The Receiver (Expert Advisor)

Save this code as SignalReceiver.mq4 in your MQL4/Experts folder.

//+------------------------------------------------------------------+
//|                                               SignalReceiver.mq4 |
//|                                     Copyright 2023, MetaQuotes   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

// Must match the ID defined in the Sender
#define MY_CUSTOM_EVENT_ID 12345

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("Receiver: Waiting for signals...");
   return(INIT_SUCCEEDED);
}

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Standard EA logic here
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // Check if the event ID matches our custom ID
   // Note: Custom events come in as CHARTEVENT_CUSTOM + your_id
   if(id == CHARTEVENT_CUSTOM + MY_CUSTOM_EVENT_ID)
   {
      Print("Receiver: Signal Received!");
      
      // Extract data sent from the indicator
      long signalType = lparam;
      double price = dparam;
      string description = sparam;
      
      // Logic based on the signal
      if(signalType == 1)
      {
         Print("Action: BUY Signal detected.");
         Print("Price: ", price);
         Print("Comment: ", description);
         
         // Example: Execute trade logic here
         // if(IsTradeAllowed()) OrderSend(...)
      }
      else if(signalType == 2)
      {
         Print("Action: SELL Signal detected.");
      }
   }
}
//+------------------------------------------------------------------+

How It Works

  1. EventChartCustom(chart_id, event_id, lparam, dparam, sparam):

    • chart_id: We pass 0 to indicate the signal is for the current chart.
    • event_id: An integer (0-65535). This is the specific ID the receiver looks for.
    • lparam, dparam, sparam: These are data payloads (Long Integer, Double, String) you can use to pass specific details like trade direction, price, or comments.
  2. OnChartEvent(id, ...):

    • The EA listens for events.
    • The system automatically adds CHARTEVENT_CUSTOM to the ID you sent.
    • Therefore, the check is if(id == CHARTEVENT_CUSTOM + MY_CUSTOM_EVENT_ID).

Alternative Method: Global Variables (GV)

If you need the signal to persist even if the EA is restarted, or if you want to communicate between different charts, you can use Global Variables of the Client Terminal. This is slower (requires polling) but very robust.

Sender Logic:

// Set a variable named "TradeSignal" to 1.0
GlobalVariableSet("TradeSignal", 1.0); 

Receiver Logic (inside OnTick):

void OnTick()
{
   // Check if variable exists
   if(GlobalVariableCheck("TradeSignal"))
   {
      double signal = GlobalVariableGet("TradeSignal");
      
      if(signal == 1.0)
      {
         Print("Buy Signal Received via GV");
         // Delete the variable so we don't process it twice
         GlobalVariableDel("TradeSignal"); 
      }
   }
}

Q&A: MQL4 Inter-Program Communication

Q: Can I send a signal to a specific chart ID instead of the current one?
A: Yes. In EventChartCustom, replace the first parameter 0 with the specific ChartID() of the target chart. This allows cross-chart communication.

Q: What happens if multiple EAs are running on the same chart?
A: Technically, MT4 allows only one Expert Advisor per chart. However, you can have multiple Indicators and one EA. If you send a custom event, all programs running on that chart (the EA and all Indicators) that have an OnChartEvent handler will receive the event.

Q: Is there a limit to how much data I can send in sparam?
A: The sparam is a string limited to 63 characters when using EventChartCustom. If you need to send more data, you should write to a file (FileOpen, FileWrite) and send the filename in the sparam, or use Global Variables.

Q: Why use Custom Events over Global Variables?
A: Custom Events are asynchronous and immediate. The EA reacts the millisecond the event is sent. Global Variables usually require the EA to wait for a new Tick (OnTick) or a Timer (OnTimer) to check the variable, which introduces latency.