To elevate this to an enterprise-grade execution tool, it must be converted from a static Script into an Event-Driven Indicator.
A Script executes once and dies, leaving "orphaned" objects on the chart. A professional tool manages its own lifecycle. This architecture introduces state management (a collapsible UI to save chart real estate), event listeners (mouse clicks), and bulletproof garbage collection (OnDeinit) so the chart remains pristine when the tool is removed.
It also upgrades the EURUSD isolation rule: instead of just halting execution, it returns INIT_FAILED, which forces the MetaTrader terminal to automatically detach the indicator from invalid charts.
Code: Select all
//+------------------------------------------------------------------+
//| EURUSD_NewsNotes.mq5|
//| Enterprise Event-Driven On-Chart Dashboard |
//+------------------------------------------------------------------+
#property copyright "Institutional Trading Infrastructure"
#property version "3.00"
#property strict
#property indicator_chart_window
#property indicator_plots 0
//--- Input parameters for UI layout
input int InputStartX = 30; // X Offset
input int InputStartY = 30; // Y Offset
input color InputBgColor = clrBlack; // Panel Background
input color InputBorderColor = clrDimGray; // Panel Border
input color InputTextColor = clrLightGray; // Base Text
input string InputFontName = "Trebuchet MS"; // Font
input int InputFontSize = 10; // Font Size
//+------------------------------------------------------------------+
//| ENUMS & STRUCTS |
//+------------------------------------------------------------------+
enum ENUM_RISK_PROFILE
{
PROFILE_FORCE_FLAT,
PROFILE_REDUCED_SIZE,
PROFILE_WATCH_ONLY,
PROFILE_EXECUTION
};
struct SNewsRule
{
ENUM_RISK_PROFILE riskProfile;
string trigger;
string action;
};
//+------------------------------------------------------------------+
//| CLASS: CNewsDashboard |
//| PURPOSE: Manages state, event routing, and UI rendering |
//+------------------------------------------------------------------+
class CNewsDashboard
{
private:
string m_prefix;
int m_x;
int m_y;
bool m_isExpanded;
SNewsRule m_rules[];
//--- Internal rendering helpers
color GetProfileColor(ENUM_RISK_PROFILE profile);
string GetProfileTag(ENUM_RISK_PROFILE profile);
void DrawLabel(string name, string text, int x, int y, color clr, int size, bool isButton = false);
void DrawPanel(int width, int height);
void ClearAll();
public:
CNewsDashboard(string objectPrefix, int x, int y);
~CNewsDashboard();
void AddRule(ENUM_RISK_PROFILE profile, string trigger, string action);
void ToggleState();
void Render();
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
};
//--- Constructor
CNewsDashboard::CNewsDashboard(string objectPrefix, int x, int y)
{
m_prefix = objectPrefix;
m_x = x;
m_y = y;
m_isExpanded = true; // Default to open
}
//--- Destructor (Garbage Collection)
CNewsDashboard::~CNewsDashboard()
{
ClearAll();
}
//--- Wipes specific indicator objects without touching user analysis
void CNewsDashboard::ClearAll()
{
int total = ObjectsTotal(0, 0, -1);
for(int i = total - 1; i >= 0; i--)
{
string objName = ObjectName(0, i, 0, -1);
if(StringFind(objName, m_prefix) == 0)
{
ObjectDelete(0, objName);
}
}
ChartRedraw();
}
void CNewsDashboard::AddRule(ENUM_RISK_PROFILE profile, string trigger, string action)
{
int size = ArraySize(m_rules);
ArrayResize(m_rules, size + 1);
m_rules[size].riskProfile = profile;
m_rules[size].trigger = trigger;
m_rules[size].action = action;
}
void CNewsDashboard::ToggleState()
{
m_isExpanded = !m_isExpanded;
Render();
}
color CNewsDashboard::GetProfileColor(ENUM_RISK_PROFILE profile)
{
switch(profile)
{
case PROFILE_FORCE_FLAT: return clrTomato;
case PROFILE_REDUCED_SIZE: return clrGold;
case PROFILE_WATCH_ONLY: return clrGray;
case PROFILE_EXECUTION: return clrDodgerBlue;
default: return clrWhite;
}
}
string CNewsDashboard::GetProfileTag(ENUM_RISK_PROFILE profile)
{
switch(profile)
{
case PROFILE_FORCE_FLAT: return "[FLAT]";
case PROFILE_REDUCED_SIZE: return "[REDUCED]";
case PROFILE_WATCH_ONLY: return "[WATCH]";
case PROFILE_EXECUTION: return "[EXEC]";
default: return "";
}
}
void CNewsDashboard::DrawPanel(int width, int height)
{
string panelName = m_prefix + "Background";
ObjectCreate(0, panelName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, m_x);
ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, m_y);
ObjectSetInteger(0, panelName, OBJPROP_XSIZE, width);
ObjectSetInteger(0, panelName, OBJPROP_YSIZE, height);
ObjectSetInteger(0, panelName, OBJPROP_BGCOLOR, InputBgColor);
ObjectSetInteger(0, panelName, OBJPROP_COLOR, InputBorderColor);
ObjectSetInteger(0, panelName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, panelName, OBJPROP_BACK, false);
ObjectSetInteger(0, panelName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, panelName, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, panelName, OBJPROP_ZORDER, 0); // Keep behind text
}
void CNewsDashboard::DrawLabel(string name, string text, int x, int y, color clr, int size, bool isButton = false)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetString(0, name, OBJPROP_FONT, InputFontName);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, name, OBJPROP_ZORDER, 1);
if(isButton)
{
// Make the label clickable by ensuring it can trigger events without being selectable
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}
}
//--- State-aware rendering engine
void CNewsDashboard::Render()
{
ClearAll();
int internalPadding = 15;
string toggleText = m_isExpanded ? "[-] COLLAPSE" : "[+] EXPAND";
if(!m_isExpanded)
{
// Render Minimized State
DrawPanel(250, 45);
DrawLabel(m_prefix + "Header", "EURUSD NEWS RULES", m_x + internalPadding, m_y + 12, clrWhite, InputFontSize + 2);
DrawLabel(m_prefix + "Toggle", toggleText, m_x + 170, m_y + 14, clrDodgerBlue, InputFontSize, true);
ChartRedraw();
return;
}
// Render Expanded State
int lineSpacing = 20;
int ruleCount = ArraySize(m_rules);
int panelHeight = (ruleCount * lineSpacing * 2) + 60;
int panelWidth = 460;
DrawPanel(panelWidth, panelHeight);
// Header & Toggle Button
DrawLabel(m_prefix + "Header", "EURUSD INSTITUTIONAL NEWS PROCESS", m_x + internalPadding, m_y + internalPadding, clrWhite, InputFontSize + 2);
DrawLabel(m_prefix + "Toggle", toggleText, m_x + panelWidth - 85, m_y + internalPadding + 2, clrDodgerBlue, InputFontSize, true);
// Rules Loop
int currentY = m_y + internalPadding + 35;
for(int i = 0; i < ruleCount; i++)
{
color tagColor = GetProfileColor(m_rules[i].riskProfile);
string tagText = GetProfileTag(m_rules[i].riskProfile) + " " + m_rules[i].trigger;
DrawLabel(m_prefix + "Trig_" + IntegerToString(i), tagText, m_x + internalPadding, currentY, tagColor, InputFontSize);
DrawLabel(m_prefix + "Act_" + IntegerToString(i), " -> " + m_rules[i].action, m_x + internalPadding, currentY + 16, InputTextColor, InputFontSize);
currentY += (lineSpacing * 2);
}
ChartRedraw();
}
//--- Event Router
void CNewsDashboard::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK)
{
// If the user clicks the Toggle button, switch state
if(sparam == m_prefix + "Toggle")
{
ToggleState();
}
}
}
//+------------------------------------------------------------------+
//| GLOBAL SCOPE & INDICATOR EVENTS |
//+------------------------------------------------------------------+
CNewsDashboard *Dashboard; // Pointer for global singleton
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 1. Strict pair isolation validation
if(StringFind(_Symbol, "EURUSD") < 0)
{
PrintFormat("[SECURITY] Attempted to load EURUSD parameters onto %s. Execution blocked.", _Symbol);
MessageBox("Execution blocked.\n\nEURUSD logic cannot be applied to " + _Symbol + ".\n\nThe indicator will now detach.", "Architecture Constraint");
return(INIT_FAILED); // Tells MetaTrader to abort and remove the indicator
}
// 2. Instantiate Singleton
Dashboard = new CNewsDashboard("EURNews_", InputStartX, InputStartY);
// 3. Inject configuration data
Dashboard.AddRule(PROFILE_FORCE_FLAT, "ECB Rate Decision, US CPI, NFP", "Close all scalps 5 mins prior. No exceptions.");
Dashboard.AddRule(PROFILE_REDUCED_SIZE, "US PMI, Jobless Claims", "Allow 50% size IF spread < 0.8 pips AND 5m candle closed.");
Dashboard.AddRule(PROFILE_WATCH_ONLY, "FOMC Minutes, Lagarde Speeches", "Edge historically negative. Stand aside completely.");
Dashboard.AddRule(PROFILE_EXECUTION, "Continuation Criteria", "Wait for 15m candle close. No front-running the sweep.");
// 4. Initial Render
Dashboard.Render();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Triggers when timeframes change, chart closes, or indicator is removed
if(CheckPointer(Dashboard) != POINTER_INVALID)
{
delete Dashboard; // Invokes the destructor -> calls ClearAll() -> removes chart objects
}
}
//+------------------------------------------------------------------+
//| 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[])
{
// Required for Indicators, but we rely on OnChartEvent for UI logic
return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
// Route chart events (mouse clicks) into the Dashboard object
if(CheckPointer(Dashboard) != POINTER_INVALID)
{
Dashboard.OnChartEvent(id, lparam, dparam, sparam);
}
}
//+------------------------------------------------------------------+