//+------------------------------------------------------------------+ //| Dashboard.mqh | //| Visual Dashboard Module | //+------------------------------------------------------------------+ #ifndef DASHBOARD_MQH #define DASHBOARD_MQH #include "DataTypes.mqh" //+------------------------------------------------------------------+ //| Dashboard Class | //+------------------------------------------------------------------+ class CDashboard { private: //--- Display settings int m_x_pos; int m_y_pos; color m_text_color; color m_profit_color; color m_loss_color; color m_header_color; color m_bg_color; int m_font_size; string m_font_name; //--- Object names string m_prefix; string m_bg_name; //--- Layout settings int m_line_height; int m_panel_width; int m_section_gap; //--- Display sections bool m_show_account; bool m_show_performance; bool m_show_trades; bool m_show_risk; bool m_show_market; //--- Helper methods void CreateBackground(); void CreateTextLabel(string name, int x, int y, string text, color clr, int size = 0, string font = ""); void UpdateTextLabel(string name, string text, color clr = clrNONE); string FormatCurrency(double value); string FormatPercent(double value); string FormatRMultiple(double value); color GetColorForValue(double value, bool inverse = false); void CreateSection(string title, int &y_offset); public: CDashboard(); ~CDashboard(); //--- Main methods bool Create(int x_pos, int y_pos, color text_color, color profit_color, color loss_color); void Destroy(); void Update(const PerformanceMetrics &performance, const ManagedTrade &trades[], const MarketConditions &market); //--- Settings void SetColors(color text, color profit, color loss, color header, color bg); void SetFont(string font_name, int font_size); void SetSections(bool account, bool perf, bool trades, bool risk, bool market); void Show(); void Hide(); void Move(int x, int y); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CDashboard::CDashboard() { m_prefix = "RMEA_DASH_"; m_bg_name = m_prefix + "BG"; m_x_pos = 20; m_y_pos = 30; m_text_color = clrWhiteSmoke; m_profit_color = clrLime; m_loss_color = clrRed; m_header_color = clrGold; m_bg_color = C'20,20,20'; m_font_size = 9; m_font_name = "Arial"; m_line_height = 16; m_panel_width = 350; m_section_gap = 20; //--- Show all sections by default m_show_account = true; m_show_performance = true; m_show_trades = true; m_show_risk = true; m_show_market = true; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CDashboard::~CDashboard() { Destroy(); } //+------------------------------------------------------------------+ //| Create dashboard | //+------------------------------------------------------------------+ bool CDashboard::Create(int x_pos, int y_pos, color text_color, color profit_color, color loss_color) { m_x_pos = x_pos; m_y_pos = y_pos; m_text_color = text_color; m_profit_color = profit_color; m_loss_color = loss_color; //--- Create background CreateBackground(); //--- Create initial labels int y_offset = m_y_pos + 10; //--- Header CreateTextLabel(m_prefix + "HEADER", m_x_pos + 10, y_offset, "RISK MANAGEMENT OVERLAY v6.6", m_header_color, 11, "Arial Bold"); y_offset += 25; //--- Create sections if(m_show_account) { CreateSection("ACCOUNT STATUS", y_offset); CreateTextLabel(m_prefix + "ACC_BALANCE", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "ACC_EQUITY", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "ACC_MARGIN", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "ACC_FLOAT", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_section_gap; } if(m_show_performance) { CreateSection("PERFORMANCE", y_offset); CreateTextLabel(m_prefix + "PERF_PROFIT", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "PERF_TRADES", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "PERF_WINRATE", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "PERF_PF", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "PERF_EXPECT", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_section_gap; } if(m_show_trades) { CreateSection("ACTIVE TRADES", y_offset); for(int i = 0; i < 5; i++) // Show up to 5 trades { CreateTextLabel(m_prefix + "TRADE_" + IntegerToString(i), m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; } y_offset += m_section_gap; } if(m_show_risk) { CreateSection("RISK METRICS", y_offset); CreateTextLabel(m_prefix + "RISK_DD", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "RISK_EXPOSURE", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "RISK_DAILY", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "RISK_SHARPE", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_section_gap; } if(m_show_market) { CreateSection("MARKET CONDITIONS", y_offset); CreateTextLabel(m_prefix + "MKT_TREND", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "MKT_VOL", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; CreateTextLabel(m_prefix + "MKT_SPREAD", m_x_pos + 10, y_offset, "", m_text_color); y_offset += m_line_height; } //--- Update background height ObjectSetInteger(0, m_bg_name, OBJPROP_YSIZE, y_offset - m_y_pos + 10); return true; } //+------------------------------------------------------------------+ //| Update dashboard | //+------------------------------------------------------------------+ void CDashboard::Update(const PerformanceMetrics &performance, const ManagedTrade &trades[], const MarketConditions &market) { //--- Account section if(m_show_account) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double floating = equity - balance; UpdateTextLabel(m_prefix + "ACC_BALANCE", "Balance: " + FormatCurrency(balance), m_text_color); UpdateTextLabel(m_prefix + "ACC_EQUITY", "Equity: " + FormatCurrency(equity), GetColorForValue(equity - balance)); UpdateTextLabel(m_prefix + "ACC_MARGIN", "Margin: " + FormatCurrency(margin) + " / Free: " + FormatCurrency(free_margin), m_text_color); UpdateTextLabel(m_prefix + "ACC_FLOAT", "Floating P/L: " + FormatCurrency(floating), GetColorForValue(floating)); } //--- Performance section if(m_show_performance) { UpdateTextLabel(m_prefix + "PERF_PROFIT", "Net Profit: " + FormatCurrency(performance.net_profit) + " (" + FormatPercent(performance.net_profit / AccountInfoDouble(ACCOUNT_BALANCE) * 100) + ")", GetColorForValue(performance.net_profit)); UpdateTextLabel(m_prefix + "PERF_TRADES", StringFormat("Trades: %d (W:%d / L:%d)", performance.total_trades, performance.winning_trades, performance.losing_trades), m_text_color); UpdateTextLabel(m_prefix + "PERF_WINRATE", "Win Rate: " + FormatPercent(performance.win_rate) + " | Consec: W" + IntegerToString(performance.consecutive_wins) + " L" + IntegerToString(performance.consecutive_losses), GetColorForValue(performance.win_rate - 50)); UpdateTextLabel(m_prefix + "PERF_PF", "Profit Factor: " + DoubleToString(performance.profit_factor, 2) + " | RRR: " + DoubleToString(performance.risk_reward_ratio, 2), GetColorForValue(performance.profit_factor - 1)); UpdateTextLabel(m_prefix + "PERF_EXPECT", "Expectancy: " + FormatCurrency(performance.expectancy) + " | Sharpe: " + DoubleToString(performance.sharpe_ratio, 2), GetColorForValue(performance.expectancy)); } //--- Active trades section if(m_show_trades) { int trade_count = ArraySize(trades); int display_count = MathMin(trade_count, 5); for(int i = 0; i < 5; i++) { if(i < display_count) { string trade_info = StringFormat("#%d %s %.2f | P/L: %s | R: %s", trades[i].ticket, (trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL", trades[i].volume, FormatCurrency(trades[i].profit), FormatRMultiple(trades[i].r_multiple)); UpdateTextLabel(m_prefix + "TRADE_" + IntegerToString(i), trade_info, GetColorForValue(trades[i].profit)); } else { UpdateTextLabel(m_prefix + "TRADE_" + IntegerToString(i), "", m_text_color); } } } //--- Risk metrics section if(m_show_risk) { double current_risk = 0; for(int i = 0; i < ArraySize(trades); i++) { current_risk += trades[i].risk_percent; } UpdateTextLabel(m_prefix + "RISK_DD", "Max DD: " + FormatPercent(performance.max_drawdown_percent) + " | Current: " + FormatPercent(performance.current_drawdown), GetColorForValue(-performance.current_drawdown)); UpdateTextLabel(m_prefix + "RISK_EXPOSURE", "Risk Exposure: " + FormatPercent(current_risk) + " | Trades: " + IntegerToString(ArraySize(trades)), GetColorForValue(10 - current_risk)); // Inverse - less is better UpdateTextLabel(m_prefix + "RISK_DAILY", "Daily P/L: " + FormatCurrency(performance.daily_profit) + " | Trades: " + IntegerToString(performance.daily_trades), GetColorForValue(performance.daily_profit)); UpdateTextLabel(m_prefix + "RISK_SHARPE", StringFormat("Sharpe: %.2f | Sortino: %.2f | Calmar: %.2f", performance.sharpe_ratio, performance.sortino_ratio, performance.calmar_ratio), m_text_color); } //--- Market conditions section if(m_show_market) { string trend_text = "UNKNOWN"; color trend_color = m_text_color; switch(market.condition) { case MARKET_RANGING: trend_text = "RANGING"; trend_color = clrYellow; break; case MARKET_TRENDING_UP: trend_text = "TRENDING UP"; trend_color = m_profit_color; break; case MARKET_TRENDING_DOWN: trend_text = "TRENDING DOWN"; trend_color = m_loss_color; break; case MARKET_VOLATILE: trend_text = "VOLATILE"; trend_color = clrOrange; break; case MARKET_QUIET: trend_text = "QUIET"; trend_color = clrGray; break; } UpdateTextLabel(m_prefix + "MKT_TREND", "Market: " + trend_text + " | Strength: " + DoubleToString(market.trend_strength, 1), trend_color); UpdateTextLabel(m_prefix + "MKT_VOL", "Volatility: " + DoubleToString(market.volatility, _Digits) + " | Momentum: " + DoubleToString(market.momentum, 1), m_text_color); UpdateTextLabel(m_prefix + "MKT_SPREAD", "Spread: " + DoubleToString(market.spread, 0) + " pts" + (market.news_event ? " | NEWS EVENT" : ""), market.news_event ? clrRed : m_text_color); } } //+------------------------------------------------------------------+ //| Create background rectangle | //+------------------------------------------------------------------+ void CDashboard::CreateBackground() { ObjectCreate(0, m_bg_name, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, m_bg_name, OBJPROP_XDISTANCE, m_x_pos); ObjectSetInteger(0, m_bg_name, OBJPROP_YDISTANCE, m_y_pos); ObjectSetInteger(0, m_bg_name, OBJPROP_XSIZE, m_panel_width); ObjectSetInteger(0, m_bg_name, OBJPROP_YSIZE, 600); // Initial height ObjectSetInteger(0, m_bg_name, OBJPROP_BGCOLOR, m_bg_color); ObjectSetInteger(0, m_bg_name, OBJPROP_BORDER_TYPE, BORDER_RAISED); ObjectSetInteger(0, m_bg_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, m_bg_name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, m_bg_name, OBJPROP_WIDTH, 2); ObjectSetInteger(0, m_bg_name, OBJPROP_BACK, false); ObjectSetInteger(0, m_bg_name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, m_bg_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, m_bg_name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, m_bg_name, OBJPROP_ZORDER, 0); } //+------------------------------------------------------------------+ //| Create text label | //+------------------------------------------------------------------+ void CDashboard::CreateTextLabel(string name, int x, int y, string text, color clr, int size = 0, string font = "") { if(size == 0) size = m_font_size; if(font == "") font = m_font_name; ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetString(0, name, OBJPROP_FONT, font); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_ZORDER, 1); } //+------------------------------------------------------------------+ //| Update text label | //+------------------------------------------------------------------+ void CDashboard::UpdateTextLabel(string name, string text, color clr = clrNONE) { if(!ObjectFind(0, name) == 0) return; ObjectSetString(0, name, OBJPROP_TEXT, text); if(clr != clrNONE) ObjectSetInteger(0, name, OBJPROP_COLOR, clr); } //+------------------------------------------------------------------+ //| Create section header | //+------------------------------------------------------------------+ void CDashboard::CreateSection(string title, int &y_offset) { CreateTextLabel(m_prefix + "SEC_" + title, m_x_pos + 10, y_offset, "--- " + title + " ---", m_header_color, 10, "Arial Bold"); y_offset += m_line_height + 5; } //+------------------------------------------------------------------+ //| Format currency value | //+------------------------------------------------------------------+ string CDashboard::FormatCurrency(double value) { string sign = value >= 0 ? "" : "-"; return sign + "$" + DoubleToString(MathAbs(value), 2); } //+------------------------------------------------------------------+ //| Format percentage value | //+------------------------------------------------------------------+ string CDashboard::FormatPercent(double value) { return DoubleToString(value, 2) + "%"; } //+------------------------------------------------------------------+ //| Format R-multiple value | //+------------------------------------------------------------------+ string CDashboard::FormatRMultiple(double value) { string sign = value >= 0 ? "+" : ""; return sign + DoubleToString(value, 2) + "R"; } //+------------------------------------------------------------------+ //| Get color based on value | //+------------------------------------------------------------------+ color CDashboard::GetColorForValue(double value, bool inverse = false) { if(inverse) value = -value; if(value > 0) return m_profit_color; else if(value < 0) return m_loss_color; else return m_text_color; } //+------------------------------------------------------------------+ //| Destroy dashboard | //+------------------------------------------------------------------+ void CDashboard::Destroy() { //--- Delete all objects with prefix int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i); if(StringFind(name, m_prefix) == 0) ObjectDelete(0, name); } } //+------------------------------------------------------------------+ //| Show dashboard | //+------------------------------------------------------------------+ void CDashboard::Show() { int total = ObjectsTotal(0); for(int i = 0; i < total; i++) { string name = ObjectName(0, i); if(StringFind(name, m_prefix) == 0) ObjectSetInteger(0, name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); } } //+------------------------------------------------------------------+ //| Hide dashboard | //+------------------------------------------------------------------+ void CDashboard::Hide() { int total = ObjectsTotal(0); for(int i = 0; i < total; i++) { string name = ObjectName(0, i); if(StringFind(name, m_prefix) == 0) ObjectSetInteger(0, name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); } } //+------------------------------------------------------------------+ //| Move dashboard | //+------------------------------------------------------------------+ void CDashboard::Move(int x, int y) { int dx = x - m_x_pos; int dy = y - m_y_pos; m_x_pos = x; m_y_pos = y; //--- Move all objects int total = ObjectsTotal(0); for(int i = 0; i < total; i++) { string name = ObjectName(0, i); if(StringFind(name, m_prefix) == 0) { int curr_x = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); int curr_y = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, curr_x + dx); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, curr_y + dy); } } } //+------------------------------------------------------------------+ //| Set display colors | //+------------------------------------------------------------------+ void CDashboard::SetColors(color text, color profit, color loss, color header, color bg) { m_text_color = text; m_profit_color = profit; m_loss_color = loss; m_header_color = header; m_bg_color = bg; //--- Update background color ObjectSetInteger(0, m_bg_name, OBJPROP_BGCOLOR, m_bg_color); } //+------------------------------------------------------------------+ //| Set font settings | //+------------------------------------------------------------------+ void CDashboard::SetFont(string font_name, int font_size) { m_font_name = font_name; m_font_size = font_size; m_line_height = (int)(font_size * 1.8); } //+------------------------------------------------------------------+ //| Set which sections to display | //+------------------------------------------------------------------+ void CDashboard::SetSections(bool account, bool perf, bool trades, bool risk, bool market) { m_show_account = account; m_show_performance = perf; m_show_trades = trades; m_show_risk = risk; m_show_market = market; } #endif // DASHBOARD_MQH //+------------------------------------------------------------------+