//+------------------------------------------------------------------+
//|                                     FXP_AccountRiskDashboard.mq5 |
//|                                                    FXPIPS ONLINE |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/account-risk-dashboard/"
#property version   "1.00"
#property description "Panel with balance, equity, floating and daily P/L, drawdown from the"
#property description "day's equity high and the money at risk to open stop-losses."
/*
   License: MIT. Copyright (c) FXPIPS ONLINE.
   Educational tool - not financial advice. Trading involves substantial risk of loss.
*/
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

enum ENUM_FXP_SCOPE
  {
   SCOPE_ALL    = 0,   // All symbols
   SCOPE_SYMBOL = 1    // Chart symbol only
  };

input group "Scope"
input ENUM_FXP_SCOPE InpScope = SCOPE_ALL; // Positions and deals to include
input long   InpMagic        = -1;        // Magic number filter (-1 = all)
input int    InpDayStartHour = 0;         // Trading day starts at (server hour)
input group "Panel"
input int    InpX            = 12;        // Panel X offset (pixels)
input int    InpY            = 24;        // Panel Y offset (pixels)
input int    InpFontSize     = 9;         // Font size
input color  InpBgColor      = C'22,26,34';   // Background
input color  InpBorderColor  = C'60,66,80';   // Border
input color  InpLabelColor   = clrDarkGray;   // Label text
input color  InpValueColor   = clrWhiteSmoke; // Value text
input color  InpPosColor     = clrMediumSeaGreen; // Positive values
input color  InpNegColor     = clrTomato;     // Negative values / warnings
input group "Alerts"
input double InpMaxDDPct     = 0.0;       // Alert when drawdown from daily high exceeds % (0 = off)
input double InpMaxRiskPct   = 0.0;       // Alert when open risk exceeds % of balance (0 = off)

const string PREFIX = "FXP_ARD_";
#define NROWS 14

datetime g_lastAlert = 0;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpDayStartHour < 0 || InpDayStartHour > 23)
      return(INIT_PARAMETERS_INCORRECT);
   IndicatorSetString(INDICATOR_SHORTNAME, "FXP Account Risk Dashboard");
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   ObjectsDeleteAll(0, PREFIX);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
void Label(string name, int x, int y, string txt, color clr)
  {
   string n = PREFIX + name;
   if(ObjectFind(0, n) < 0)
     {
      ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
      ObjectSetString(0, n, OBJPROP_FONT, "Consolas");
      ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFontSize);
     }
   ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, n, OBJPROP_YDISTANCE, y);
   ObjectSetString(0, n, OBJPROP_TEXT, txt);
   ObjectSetInteger(0, n, OBJPROP_COLOR, clr);
  }

//+------------------------------------------------------------------+
void Background(int rows)
  {
   string bg = PREFIX + "BG";
   if(ObjectFind(0, bg) < 0)
     {
      ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
     }
   ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, InpX);
   ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, InpY);
   ObjectSetInteger(0, bg, OBJPROP_XSIZE, InpFontSize * 36);
   ObjectSetInteger(0, bg, OBJPROP_YSIZE, (InpFontSize * 2 + 2) * rows + 10);
   ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, InpBgColor);
   ObjectSetInteger(0, bg, OBJPROP_COLOR, InpBorderColor);
  }

//+------------------------------------------------------------------+
bool PassFilter(string sym, long magic)
  {
   if(InpScope == SCOPE_SYMBOL && sym != _Symbol)
      return(false);
   if(InpMagic >= 0 && magic != InpMagic)
      return(false);
   return(true);
  }

//+------------------------------------------------------------------+
datetime DayStart()
  {
   datetime now = TimeTradeServer();
   if(now == 0)
      now = TimeCurrent();
   datetime start = now - (now % 86400) + InpDayStartHour * 3600;
   if(start > now)
      start -= 86400;
   return(start);
  }

//+------------------------------------------------------------------+
// closed trading result since 'from' (profit + swap + commission + fee)
double ClosedSince(datetime from, bool filtered)
  {
   if(!HistorySelect(from, TimeTradeServer() + 3600))
      return(0.0);
   double sum = 0.0;
   int total = HistoryDealsTotal();
   for(int i = 0; i < total; i++)
     {
      ulong tk = HistoryDealGetTicket(i);
      if(tk == 0)
         continue;
      ENUM_DEAL_TYPE type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(tk, DEAL_TYPE);
      if(type != DEAL_TYPE_BUY && type != DEAL_TYPE_SELL)
         continue;
      if(filtered && !PassFilter(HistoryDealGetString(tk, DEAL_SYMBOL), HistoryDealGetInteger(tk, DEAL_MAGIC)))
         continue;
      sum += HistoryDealGetDouble(tk, DEAL_PROFIT) + HistoryDealGetDouble(tk, DEAL_SWAP) +
             HistoryDealGetDouble(tk, DEAL_COMMISSION) + HistoryDealGetDouble(tk, DEAL_FEE);
     }
   return(sum);
  }

//+------------------------------------------------------------------+
color Signed(double v)
  {
   if(v > 0.0)
      return(InpPosColor);
   if(v < 0.0)
      return(InpNegColor);
   return(InpValueColor);
  }

//+------------------------------------------------------------------+
void UpdatePanel()
  {
   string ccy = AccountInfoString(ACCOUNT_CURRENCY);
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   double marginLvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);

   // --- open positions
   double floating = 0.0, riskOpen = 0.0, riskNow = 0.0;
   int count = 0, noSL = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong tk = PositionGetTicket(i);
      if(tk == 0 || !PositionSelectByTicket(tk))
         continue;
      string sym = PositionGetString(POSITION_SYMBOL);
      if(!PassFilter(sym, PositionGetInteger(POSITION_MAGIC)))
         continue;
      count++;
      floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      double sl = PositionGetDouble(POSITION_SL);
      if(sl <= 0.0)
        {
         noSL++;
         continue;
        }
      ENUM_ORDER_TYPE ot = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
      double vol = PositionGetDouble(POSITION_VOLUME);
      double pOpen = PositionGetDouble(POSITION_PRICE_OPEN);
      double pNow = PositionGetDouble(POSITION_PRICE_CURRENT);
      double r1 = 0.0, r2 = 0.0;
      if(OrderCalcProfit(ot, sym, vol, pOpen, sl, r1) && r1 < 0.0)
         riskOpen += -r1;
      if(OrderCalcProfit(ot, sym, vol, pNow, sl, r2) && r2 < 0.0)
         riskNow += -r2;
     }

   // --- daily figures
   datetime ds = DayStart();
   double closedAll = ClosedSince(ds, false);
   double closedSel = (InpScope == SCOPE_ALL && InpMagic < 0) ? closedAll : ClosedSince(ds, true);
   double dayStartBal = balance - closedAll;
   double dailyPL = closedSel + floating;

   // --- equity high of the day, kept in a terminal global variable
   string gv = StringFormat("FXP_ARD_%I64d_%I64d", AccountInfoInteger(ACCOUNT_LOGIN), (long)ds);
   double eqHigh = MathMax(dayStartBal, equity);
   if(GlobalVariableCheck(gv))
      eqHigh = MathMax(eqHigh, GlobalVariableGet(gv));
   GlobalVariableSet(gv, eqHigh);
   string gvOld = StringFormat("FXP_ARD_%I64d_%I64d", AccountInfoInteger(ACCOUNT_LOGIN), (long)(ds - 86400));
   if(GlobalVariableCheck(gvOld))
      GlobalVariableDel(gvOld);
   double dd = eqHigh - equity;
   double ddPct = (eqHigh > 0.0) ? dd / eqHigh * 100.0 : 0.0;
   double riskPct = (balance > 0.0) ? riskOpen / balance * 100.0 : 0.0;
   double riskNowPct = (balance > 0.0) ? riskNow / balance * 100.0 : 0.0;

   string lbl[NROWS] =
     {
      "Account", "Balance", "Equity", "Floating P/L", "Closed today", "Daily P/L",
      "Equity high today", "Drawdown from high", "Open positions", "Risk to SL (from entry)",
      "Risk to SL (from now)", "Positions without SL", "Margin level", "Day starts"
     };
   string val[NROWS];
   color  vc[NROWS];
   ArrayInitialize(vc, InpValueColor);
   val[0] = StringFormat("%I64d  %s  1:%I64d", AccountInfoInteger(ACCOUNT_LOGIN), ccy, AccountInfoInteger(ACCOUNT_LEVERAGE));
   val[1] = StringFormat("%.2f", balance);
   val[2] = StringFormat("%.2f", equity);
   val[3] = StringFormat("%+.2f", floating);
   vc[3] = Signed(floating);
   val[4] = StringFormat("%+.2f", closedSel);
   vc[4] = Signed(closedSel);
   val[5] = StringFormat("%+.2f  (%+.2f%%)", dailyPL, dayStartBal > 0.0 ? dailyPL / dayStartBal * 100.0 : 0.0);
   vc[5] = Signed(dailyPL);
   val[6] = StringFormat("%.2f", eqHigh);
   val[7] = StringFormat("%.2f  (%.2f%%)", dd, ddPct);
   vc[7] = (dd > 0.0) ? InpNegColor : InpValueColor;
   val[8] = IntegerToString(count);
   val[9] = StringFormat("%.2f  (%.2f%%)", riskOpen, riskPct);
   val[10] = StringFormat("%.2f  (%.2f%%)", riskNow, riskNowPct);
   val[11] = IntegerToString(noSL);
   vc[11] = (noSL > 0) ? InpNegColor : InpValueColor;
   val[12] = (marginLvl > 0.0) ? StringFormat("%.0f%%", marginLvl) : "-";
   val[13] = TimeToString(ds, TIME_DATE | TIME_MINUTES);

   Background(NROWS + 1);
   int x = InpX + 8, y = InpY + 5, rh = InpFontSize * 2 + 2;
   Label("TITLE", x, y, "FXP Account Risk  (" + (InpScope == SCOPE_ALL ? "all symbols" : _Symbol) + ")", InpValueColor);
   y += rh;
   for(int r = 0; r < NROWS; r++)
     {
      Label("L" + IntegerToString(r), x, y, lbl[r], InpLabelColor);
      Label("V" + IntegerToString(r), x + InpFontSize * 17, y, val[r], vc[r]);
      y += rh;
     }

   // --- alerts, at most once per chart bar
   datetime bar = iTime(_Symbol, _Period, 0);
   if(bar != g_lastAlert)
     {
      string msg = "";
      if(InpMaxDDPct > 0.0 && ddPct >= InpMaxDDPct)
         msg = StringFormat("drawdown from daily high %.2f%% >= %.2f%%", ddPct, InpMaxDDPct);
      else
         if(InpMaxRiskPct > 0.0 && riskPct >= InpMaxRiskPct)
            msg = StringFormat("open risk to stop-losses %.2f%% >= %.2f%%", riskPct, InpMaxRiskPct);
      if(msg != "")
        {
         g_lastAlert = bar;
         Alert("FXP Account Risk: " + msg);
        }
     }
   ChartRedraw();
  }

//+------------------------------------------------------------------+
void OnTimer()
  {
   UpdatePanel();
  }

//+------------------------------------------------------------------+
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[])
  {
   if(prev_calculated == 0)
      UpdatePanel();
   return(rates_total);
  }
//+------------------------------------------------------------------+
