//+------------------------------------------------------------------+
//|                                                   RSI_Expert.mq5 |
//|                                      Copyright 2026, Algorithmic |
//|                                       https://www.mql5.com       |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Algorithmic Trader"
#property link      "https://www.mql5.com"
#property version   "1.01"

#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>

//--- Enums
enum ENUM_RISK_TYPE {
   RISK_PERCENT,   // Percentage of Account Capital
   RISK_MONEY      // Fixed Monetary Amount
};

//--- Input Parameters
input group "--- General ---"
input ulong            InpMagicNumber     = 123456;      // Magic Number

input group "--- RSI Settings ---"
input int              InpRSIPeriod       = 14;          // RSI Period
input ENUM_TIMEFRAMES  InpRSITF           = PERIOD_H1;   // RSI Timeframe
input double           InpRSIBuy          = 30.0;        // RSI Buy Threshold
input double           InpRSISell         = 70.0;        // RSI Sell Threshold

input group "--- MA Filter Settings ---"
input bool             InpUseMAFilter     = true;        // Use MA Filter?
input int              InpMAPeriod        = 50;          // MA Period
input ENUM_TIMEFRAMES  InpMATF            = PERIOD_D1;   // MA Timeframe
input ENUM_MA_METHOD   InpMAMethod        = MODE_SMA;    // MA Type

input group "--- Trade Settings ---"
input double           InpStopLossPct     = 2.0;         // Stop Loss (%) [0 = Disabled]
input double           InpTakeProfitPct   = 4.0;         // Take Profit (%) [0 = Disabled]

input group "--- Trailing Stop ---"
input double           InpTrailTriggerPct = 0.5;         // Trail Trigger (%) [0 = Disabled]
input double           InpTrailDistPct    = 0.3;         // Trail Distance (%)
input double           InpTrailStepPct    = 0.05;        // Trail Step (%)

input group "--- Risk Management ---"
input ENUM_RISK_TYPE   InpRiskType        = RISK_PERCENT;// Risk Mode
input double           InpRiskValue       = 1.0;         // Risk Value (%)

//--- Global Variables
CTrade         trade;
CSymbolInfo    symInfo;
CPositionInfo  posInfo;
CAccountInfo   accInfo;

int            rsi_handle = INVALID_HANDLE;
int            ma_handle  = INVALID_HANDLE;

string         gv_buy_name;
string         gv_sell_name;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Set magic number
   trade.SetExpertMagicNumber(InpMagicNumber);
   
   // Initialize symbol
   if(!symInfo.Name(_Symbol)) return(INIT_FAILED);
   symInfo.Refresh();

   // CRITICAL FIX: Set broker-compatible order filling mode
   trade.SetTypeFillingBySymbol(_Symbol);

   // Initialize Indicator Handles
   rsi_handle = iRSI(_Symbol, InpRSITF, InpRSIPeriod, PRICE_CLOSE);
   if(rsi_handle == INVALID_HANDLE) {
      Print("Failed to create RSI handle!");
      return(INIT_FAILED);
   }

   if(InpUseMAFilter) {
      ma_handle = iMA(_Symbol, InpMATF, InpMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
      if(ma_handle == INVALID_HANDLE) {
         Print("Failed to create MA handle!");
         return(INIT_FAILED);
      }
   }

   // Initialize Global Variable Names (for state restoration)
   gv_buy_name  = "RSI_EA_BUY_" + _Symbol + "_" + IntegerToString(InpMagicNumber);
   gv_sell_name = "RSI_EA_SELL_" + _Symbol + "_" + IntegerToString(InpMagicNumber);

   // Ensure global variables exist
   if(!GlobalVariableCheck(gv_buy_name))  GlobalVariableSet(gv_buy_name, 1.0);
   if(!GlobalVariableCheck(gv_sell_name)) GlobalVariableSet(gv_sell_name, 1.0);

   Print("RSI EA Initialized successfully on ", _Symbol);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   IndicatorRelease(rsi_handle);
   if(InpUseMAFilter) IndicatorRelease(ma_handle);
   
   if(reason == REASON_REMOVE) {
      GlobalVariableDel(gv_buy_name);
      GlobalVariableDel(gv_sell_name);
   }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if(!symInfo.RefreshRates()) return;

   ProcessTrailingStop();

   // Retrieve Indicator Data
   double rsi[];
   ArraySetAsSeries(rsi, true);
   if(CopyBuffer(rsi_handle, 0, 0, 1, rsi) <= 0) return;

   double ma[];
   if(InpUseMAFilter) {
      ArraySetAsSeries(ma, true);
      if(CopyBuffer(ma_handle, 0, 0, 1, ma) <= 0) return;
   }

   bool can_buy  = (bool)GlobalVariableGet(gv_buy_name);
   bool can_sell = (bool)GlobalVariableGet(gv_sell_name);

   // RSI Reset Logic
   if(!can_buy && rsi[0] > 50.0) {
      GlobalVariableSet(gv_buy_name, 1.0);
      can_buy = true;
   }
   if(!can_sell && rsi[0] < 50.0) {
      GlobalVariableSet(gv_sell_name, 1.0);
      can_sell = true;
   }

   // Trade Execution Logic
   if(can_buy && rsi[0] < InpRSIBuy) {
      if(!InpUseMAFilter || symInfo.Ask() > ma[0]) {
         ExecuteTrade(ORDER_TYPE_BUY);
      }
   }
   
   if(can_sell && rsi[0] > InpRSISell) {
      if(!InpUseMAFilter || symInfo.Bid() < ma[0]) {
         ExecuteTrade(ORDER_TYPE_SELL);
      }
   }
}

//+------------------------------------------------------------------+
//| Trade Execution Function                                         |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE order_type) {
   double price = (order_type == ORDER_TYPE_BUY) ? symInfo.Ask() : symInfo.Bid();
   double sl = 0.0;
   double tp = 0.0;
   
   double sl_dist = price * (InpStopLossPct / 100.0);
   double tp_dist = price * (InpTakeProfitPct / 100.0);

   if(order_type == ORDER_TYPE_BUY) {
      if(InpStopLossPct > 0)   sl = price - sl_dist;
      if(InpTakeProfitPct > 0) tp = price + tp_dist;
   } else {
      if(InpStopLossPct > 0)   sl = price + sl_dist;
      if(InpTakeProfitPct > 0) tp = price - tp_dist;
   }

   sl = (sl > 0) ? NormalizeDouble(sl, symInfo.Digits()) : 0;
   tp = (tp > 0) ? NormalizeDouble(tp, symInfo.Digits()) : 0;

   double volume = CalculateLotSize(price, sl, order_type);
   if(volume <= 0) {
      Print("Order Execution Cancelled: Calculated volume is 0 or invalid.");
      return;
   }

   if(order_type == ORDER_TYPE_BUY) {
      if(trade.Buy(volume, _Symbol, price, sl, tp, "RSI Buy")) {
         GlobalVariableSet(gv_buy_name, 0.0);
         Print("BUY Order Opened Successfully! Volume: ", volume);
      } else {
         Print("BUY Order Failed! Error Code: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
      }
   } else {
      if(trade.Sell(volume, _Symbol, price, sl, tp, "RSI Sell")) {
         GlobalVariableSet(gv_sell_name, 0.0);
         Print("SELL Order Opened Successfully! Volume: ", volume);
      } else {
         Print("SELL Order Failed! Error Code: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate Lot Size based on Risk Settings                        |
//+------------------------------------------------------------------+
double CalculateLotSize(double price, double sl_price, ENUM_ORDER_TYPE order_type) {
   double risk_money = 0.0;
   
   if(InpRiskType == RISK_PERCENT) {
      risk_money = accInfo.Equity() * (InpRiskValue / 100.0);
   } else {
      risk_money = InpRiskValue;
   }

   double lot = 0.0;
   
   if(InpStopLossPct > 0 && sl_price > 0) {
      double tick_value = symInfo.TickValue();
      double tick_size  = symInfo.TickSize();
      double price_dist = MathAbs(price - sl_price);

      if(tick_size > 0 && tick_value > 0) {
         double loss_per_lot = (price_dist / tick_size) * tick_value;
         if(loss_per_lot > 0) lot = risk_money / loss_per_lot;
      }
   } else {
      double margin = 0.0;
      if(OrderCalcMargin(order_type, _Symbol, 1.0, price, margin)) {
         if(margin > 0) lot = risk_money / margin;
      }
   }

   // Normalize Volume using CSymbolInfo methods
   double min_lot  = symInfo.LotsMin();
   double max_lot  = symInfo.LotsMax();
   double step_lot = symInfo.LotsStep();

   if(lot < min_lot) lot = min_lot;
   if(lot > max_lot) lot = max_lot;

   if(step_lot > 0) {
      lot = MathRound(lot / step_lot) * step_lot;
   }
   
   return lot;
}

//+------------------------------------------------------------------+
//| Trailing Stop Processor                                          |
//+------------------------------------------------------------------+
void ProcessTrailingStop() {
   if(InpTrailTriggerPct <= 0) return;

   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      if(posInfo.SelectByIndex(i)) {
         if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber) {
            
            double open_price = posInfo.PriceOpen();
            double current_sl = posInfo.StopLoss();
            double current_tp = posInfo.TakeProfit();
            ulong  ticket     = posInfo.Ticket();
            
            double trigger_dist = open_price * (InpTrailTriggerPct / 100.0);
            double trail_dist   = open_price * (InpTrailDistPct / 100.0);
            double step_dist    = open_price * (InpTrailStepPct / 100.0);

            if(posInfo.PositionType() == POSITION_TYPE_BUY) {
               double current_price = symInfo.Bid();
               
               if((current_price - open_price) >= trigger_dist) {
                  double new_sl = NormalizeDouble(current_price - trail_dist, symInfo.Digits());
                  
                  if(current_sl == 0.0 || (new_sl - current_sl) >= step_dist) {
                     trade.PositionModify(ticket, new_sl, current_tp);
                  }
               }
            } 
            else if(posInfo.PositionType() == POSITION_TYPE_SELL) {
               double current_price = symInfo.Ask();
               
               if((open_price - current_price) >= trigger_dist) {
                  double new_sl = NormalizeDouble(current_price + trail_dist, symInfo.Digits());
                  
                  if(current_sl == 0.0 || (current_sl - new_sl) >= step_dist) {
                     trade.PositionModify(ticket, new_sl, current_tp);
                  }
               }
            }
         }
      }
   }
}
//+------------------------------------------------------------------+