//+------------------------------------------------------------------+
//|                                                    RiskPanel.mq5 |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property link      ""
#property version   "2.00"

#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Label.mqh>
#include <Controls\Edit.mqh>
#include <Trade\Trade.mqh>

//--- User Inputs for Filtering
input long   InpMagicNumber       = 0;     // Target Magic Number (0 = All)
input bool   InpCurrentSymbolOnly = true;  // Filter by Current Symbol Only

//+------------------------------------------------------------------+
//| Class: CRiskPanel                                                |
//| Purpose: Interactive on-chart risk management dashboard          |
//+------------------------------------------------------------------+
class CRiskPanel : public CAppDialog
  {
private:
   CButton           m_btn_close_all;     // Button to close all filtered positions
   CButton           m_btn_close_winners; // Button to close only profitable positions
   CButton           m_btn_close_losers;  // Button to close only losing positions
   CLabel            m_lbl_drawdown;      // Label to display net floating P/L
   CEdit             m_edit_target;       // Input field for automated target profit
   CLabel            m_lbl_target_info;   // Label for the target input field
   CTrade            m_trade;             // Trade execution object

public:
                     CRiskPanel(void);
                    ~CRiskPanel(void);
   
   //--- Create the panel and its elements
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   bool              CreateButtons(void);
   bool              CreateLabels(void);
   bool              CreateInputFields(void);
   
   //--- Logic and event handlers
   void              UpdatePanel(void);
   double            CalculateRisk(void);
   void              ExecuteBasketClose(int filter_type); 
   void              CheckAutoTarget(double current_profit);
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRiskPanel::CRiskPanel(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CRiskPanel::~CRiskPanel(void)
  {
  }

//+------------------------------------------------------------------+
//| Event handler for chart and library events                       |
//+------------------------------------------------------------------+
bool CRiskPanel::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   // Captures both native chart clicks and the library's internal click events
   if(id == CHARTEVENT_OBJECT_CLICK || id == (CHARTEVENT_CUSTOM+ON_CLICK))
     {
      // Route to Close All
      if(sparam == m_btn_close_all.Name() || lparam == m_btn_close_all.Id())
        {
         ExecuteBasketClose(0);
         return(true);
        }
      // Route to Close Winners
      if(sparam == m_btn_close_winners.Name() || lparam == m_btn_close_winners.Id())
        {
         ExecuteBasketClose(1);
         return(true);
        }
      // Route to Close Losers
      if(sparam == m_btn_close_losers.Name() || lparam == m_btn_close_losers.Id())
        {
         ExecuteBasketClose(-1);
         return(true);
        }
     }
   // Call the base class method for default dialog events
   return(CAppDialog::OnEvent(id,lparam,dparam,sparam));
  }

//+------------------------------------------------------------------+
//| Creates the main dialog window and all sub-elements              |
//+------------------------------------------------------------------+
bool CRiskPanel::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   // Initialize the base dialog
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
      
   // Initialize specific UI components
   if(!CreateLabels())
      return(false);
   if(!CreateButtons())
      return(false);
   if(!CreateInputFields())
      return(false);
      
   return(true);
  }

//+------------------------------------------------------------------+
//| Creates text labels on the panel                                 |
//+------------------------------------------------------------------+
bool CRiskPanel::CreateLabels(void)
  {
   // Floating P/L Display
   if(!m_lbl_drawdown.Create(m_chart_id,m_name+"Drawdown",m_subwin,20,15,200,30)) return(false);
   if(!m_lbl_drawdown.Text("Net P/L: 0.00")) return(false);
   if(!Add(m_lbl_drawdown)) return(false);
   
   // Target Input Description
   if(!m_lbl_target_info.Create(m_chart_id,m_name+"TargetInfo",m_subwin,20,95,110,115)) return(false);
   if(!m_lbl_target_info.Text("Target P/L:")) return(false);
   if(!Add(m_lbl_target_info)) return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Creates interactive buttons on the panel                         |
//+------------------------------------------------------------------+
bool CRiskPanel::CreateButtons(void)
  {
   // Close All Button
   if(!m_btn_close_all.Create(m_chart_id,m_name+"CloseAll",m_subwin,20,40,110,60)) return(false);
   if(!m_btn_close_all.Text("Close All")) return(false);
   if(!Add(m_btn_close_all)) return(false);

   // Close Winners Button
   if(!m_btn_close_winners.Create(m_chart_id,m_name+"CloseWinners",m_subwin,130,40,220,60)) return(false);
   if(!m_btn_close_winners.Text("Close Winners")) return(false);
   if(!Add(m_btn_close_winners)) return(false);
      
   // Close Losers Button
   if(!m_btn_close_losers.Create(m_chart_id,m_name+"CloseLosers",m_subwin,240,40,330,60)) return(false);
   if(!m_btn_close_losers.Text("Close Losers")) return(false);
   if(!Add(m_btn_close_losers)) return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Creates input fields for automated rules                         |
//+------------------------------------------------------------------+
bool CRiskPanel::CreateInputFields(void)
  {
   // Target Profit Input
   if(!m_edit_target.Create(m_chart_id,m_name+"TargetProfit",m_subwin,130,95,220,115)) return(false);
   if(!m_edit_target.Text("100.00")) return(false);
   if(!Add(m_edit_target)) return(false);
   return(true);
  }

//+------------------------------------------------------------------+
//| Calculates absolute net exposure based on strict filters         |
//+------------------------------------------------------------------+
double CRiskPanel::CalculateRisk(void)
  {
   double net_profit = 0.0;
   int total = PositionsTotal();
   
   // Loop backwards to safely evaluate positions
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         // Apply symbol and magic number isolation
         if(InpCurrentSymbolOnly && PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
         if(InpMagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
         
         // Aggregate raw profit and swap
         net_profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
        }
     }
   return(net_profit);
  }

//+------------------------------------------------------------------+
//| Updates UI text and checks automated targets                     |
//+------------------------------------------------------------------+
void CRiskPanel::UpdatePanel(void)
  {
   double current_profit = CalculateRisk();
   m_lbl_drawdown.Text("Net P/L: " + DoubleToString(current_profit, 2));
   ChartRedraw(m_chart_id); // Refresh chart to show new text
   
   // Validate if current equity breaches the target
   CheckAutoTarget(current_profit);
  }

//+------------------------------------------------------------------+
//| Autonomously closes basket if floating reality exceeds target    |
//+------------------------------------------------------------------+
void CRiskPanel::CheckAutoTarget(double current_profit)
  {
   double target = StringToDouble(m_edit_target.Text());
   
   // Trigger closure if target is valid and breached
   if(target > 0 && current_profit >= target)
     {
      PrintFormat("Auto-Target reached! Profit: %.2f >= Target: %.2f", current_profit, target);
      ExecuteBasketClose(0); // 0 = Close all filtered positions
     }
  }

//+------------------------------------------------------------------+
//| Executes the liquidation sequence and handles server errors      |
//+------------------------------------------------------------------+
void CRiskPanel::ExecuteBasketClose(int filter_type)
  {
   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         // Apply security filters
         if(InpCurrentSymbolOnly && PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
         if(InpMagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
         
         double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         
         // Apply profitability condition (1=Winners, -1=Losers)
         if(filter_type == 1 && profit <= 0) continue;
         if(filter_type == -1 && profit >= 0) continue;
         
         // Execute trade and intercept server rejections
         if(!m_trade.PositionClose(ticket))
           {
            PrintFormat("Close failed for ticket %I64u. Code: %d, Desc: %s", ticket, m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription());
           }
        }
     }
  }

//--- Global Environment
CRiskPanel ExtPanel;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Instantiate the panel object
   if(!ExtPanel.Create(0,"Risk Dashboard PRO",0,50,50,410,210))
      return(INIT_FAILED);
      
   ExtPanel.Run();
   
   // Establish polling frequency
   EventSetTimer(1); 
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Destroy the panel and release resources
   ExtPanel.Destroy(reason);
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   // Route chart interactions to the panel event listener
   ExtPanel.ChartEvent(id,lparam,dparam,sparam);
  }

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   // Trigger the panel update sequence
   ExtPanel.UpdatePanel(); 
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // The panel is primarily event and timer driven.
   // OnTick is formally included to define the program as an Expert Advisor.
  }
//+------------------------------------------------------------------+