DEV Community

Omais Ali
Omais Ali

Posted on

Adding a pause button to an MQL5 Expert Advisor with OnChartEvent

Sooner or later every trader running an Expert Advisor wants to stop it from opening new trades for a while. A news release is coming, the spread looks strange, or they simply want to watch for an hour. The usual options are not great. Removing the EA from the chart also stops it managing the positions it already has. Turning off Algo Trading in the terminal stops every EA on every chart.

A pause button on the chart solves this. It blocks new entries for this EA only, while the EA keeps managing its open positions. This tutorial builds one with OBJ_BUTTON and OnChartEvent(), and makes the paused state survive a restart.

Creating the button

input long InpMagic = 7310;

const string BTN = "ctb_pause_btn";
bool gPaused = false;

string PauseKey()
{
   // global variable names are limited to 63 characters
   return StringFormat("ctb_paused_%I64d_%s_%I64d",
                       AccountInfoInteger(ACCOUNT_LOGIN), _Symbol, InpMagic);
}

void UpdateButton()
{
   ObjectSetInteger(0, BTN, OBJPROP_STATE, gPaused);
   ObjectSetString(0, BTN, OBJPROP_TEXT, gPaused ? "Resume entries" : "Pause entries");
   ObjectSetInteger(0, BTN, OBJPROP_BGCOLOR, gPaused ? clrOrange : clrSilver);
   ChartRedraw(0);
}

void CreateButton()
{
   if(ObjectFind(0, BTN) < 0)
      ObjectCreate(0, BTN, OBJ_BUTTON, 0, 0, 0);

   ObjectSetInteger(0, BTN, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
   ObjectSetInteger(0, BTN, OBJPROP_XDISTANCE, 150);  // at least the width
   ObjectSetInteger(0, BTN, OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0, BTN, OBJPROP_XSIZE, 140);
   ObjectSetInteger(0, BTN, OBJPROP_YSIZE, 28);
   ObjectSetInteger(0, BTN, OBJPROP_SELECTABLE, false);
   UpdateButton();
}
Enter fullscreen mode Exit fullscreen mode

Two details matter here.

With CORNER_RIGHT_UPPER the distances are measured from the right edge of the chart to the left edge of the button, so OBJPROP_XDISTANCE has to be at least as large as OBJPROP_XSIZE or the button ends up partly off screen.

The key for the saved state includes the account number, the symbol and the magic number. If the same EA runs on two charts, each copy gets its own pause switch.

Reacting to clicks

void SetPaused(const bool paused)
{
   gPaused = paused;
   GlobalVariableSet(PauseKey(), paused ? 1.0 : 0.0);
   GlobalVariablesFlush();
   PrintFormat("New entries %s", paused ? "paused" : "resumed");
   UpdateButton();
}

void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == BTN)
   {
      // the terminal has already flipped OBJPROP_STATE for us
      SetPaused((bool)ObjectGetInteger(0, BTN, OBJPROP_STATE));
      return;
   }

   if(id == CHARTEVENT_OBJECT_DELETE && sparam == BTN)
      CreateButton();   // someone deleted it by hand, put it back
}
Enter fullscreen mode Exit fullscreen mode

A button object toggles its own OBJPROP_STATE when clicked, so the handler reads the state instead of keeping a separate toggle. That way the variable and what the trader sees cannot drift apart.

The delete event is not sent by default. You turn it on with ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true), which is done in OnInit() below.

Wiring it into the EA

int OnInit()
{
   ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);

   if(GlobalVariableCheck(PauseKey()))
      gPaused = GlobalVariableGet(PauseKey()) > 0.5;

   CreateButton();
   if(gPaused)
      Print("Starting with new entries paused");
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   ObjectDelete(0, BTN);
}

void OnTick()
{
   ManageOpenPositions();   // stops, trailing, exits: always runs

   if(gPaused)
      return;               // no new entries while paused

   CheckForEntry();
}
Enter fullscreen mode Exit fullscreen mode

ManageOpenPositions() and CheckForEntry() stand for whatever your EA already does. The order inside OnTick() is the important part. Position management runs first and always. The pause only skips the entry logic. A pause button that also stopped trailing stops and exits would be more dangerous than no button at all.

The state is stored in a terminal global variable, so it survives a timeframe change, a change of inputs and a terminal restart. GlobalVariablesFlush() writes it to disk straight away instead of waiting for the terminal to close. Global variables that are not accessed are deleted after four weeks, which is fine for a pause flag.

Deleting the button in OnDeinit() keeps the chart clean when the EA is removed. On a timeframe change OnInit() runs again and recreates it with the saved state.

Testing it

Chart events are not delivered in the Strategy Tester, so try the button on a demo chart.

  1. Attach the EA, click Pause and check the Experts log for the message.
  2. Change the timeframe and confirm the button comes back still paused.
  3. Restart the terminal and check again.
  4. Open a position, pause, and confirm the trailing stop or exit still works while no new trades open.
  5. Delete the button from the object list (Ctrl+B) and confirm it reappears.

A pause switch is a small feature, but it gives the trader a safe middle ground between leaving the EA alone and pulling it off the chart.

Top comments (0)