//+------------------------------------------------------------------+
//|                                                4xTrendForce.mq5   |
//|  MT5 RenkoLive: PriceActionCandles_RenkoFix + NRP Trendforce    |
//+------------------------------------------------------------------+
#property strict
#property version   "2.20"

#include <Trade/Trade.mqh>
CTrade trade;

//---- trade settings
input int      MagicNumber          = 40095;
input double   FixedLotSize         = 0.01;
input bool     UseRiskMoneyMgmt     = false;
input double   RiskPercent          = 1.0;
input int      StopLossPips         = 150;
input int      TakeProfitPips       = 0;
input int      Slippage             = 3;
input double   MaxSpreadPips        = 5.0;
input bool     TradeOnNewBarOnly    = true;
input bool     CloseOppositeSignal  = true;

//---- Renko/custom-symbol live trading support
// Leave blank for normal charts. When attached to a custom Renko symbol, set this to
// the real broker symbol, for example EURUSD, USDJPY.FX, XAUUSD, etc.
input string   TradeSymbol          = "";
input int      SignalShift          = 1; // 1 = previous closed Renko brick/candle only

//---- startup first-order control
// 0 = Allow either first order, 1 = First new order must be BUY, 2 = First new order must be SELL
input int      FirstOrderDirection  = 0;

//---- indicator file names, without .mq5/.ex5
input string   PriceChannelName     = "PriceActionCandles_RenkoFix";
input string   TrendforceName       = "NRP_Trendforce_Histogram_TrueNRP_MT5_fixed";

//---- PriceActionCandles inputs
input int      PCC_Bars_Count        = 32;
input color    PCC_Close_Above_Color = clrDodgerBlue;
input color    PCC_Close_Below_Color = clrSlateBlue;
input color    PCC_Center_Line_Color = clrDodgerBlue;
input bool     PCC_Show_Center_Line  = true;

//---- RP_Trendforce inputs
input int      TF_SnakeRange        = 200;
input int      TF_FilterPeriod      = 250;
input double   TF_MartFiltr         = 500.0;
input int      TF_PriceConst        = 1;
input double   TF_LevelsCross       = 0.95;
input int      TF_Countbars         = 3000;
input int      TF_HistogramWidth    = 2;
input bool     TF_ShowCurrentBar    = false;
input bool     TF_RecalculateAllBars= true;

//---- internal state
datetime lastBarTime = 0;
bool     firstOrderCompleted = false;
int      priceChannelHandle = INVALID_HANDLE;
int      trendforceHandle   = INVALID_HANDLE;

//+------------------------------------------------------------------+
int OnInit()
{
   lastBarTime = 0;
   firstOrderCompleted = false;

   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(Slippage);

   string ts = GetTradeSymbol();
   if(!SymbolSelect(ts, true))
   {
      Print("4xTrendForce: failed to select trade symbol ", ts, ". Error=", GetLastError());
      return INIT_FAILED;
   }

   priceChannelHandle = CreatePriceChannelHandle();

   if(priceChannelHandle == INVALID_HANDLE)
   {
      Print("4xTrendForce: failed to create PriceActionCandles handle. LastError=", GetLastError(),
            ". Check that the updated PriceActionCandles_RenkoFix indicator is compiled in MQL5/Indicators.");
      return INIT_FAILED;
   }

   trendforceHandle = CreateTrendforceHandle();

   if(trendforceHandle == INVALID_HANDLE)
   {
      Print("4xTrendForce: failed to create NRP Trendforce handle. LastError=", GetLastError(),
            ". Check that the updated NRP_Trendforce_Histogram_TrueNRP_MT5_fixed indicator is compiled in MQL5/Indicators.");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+

int CreatePriceChannelHandle()
{
   string names[4];
   names[0] = PriceChannelName;
   names[1] = "PriceActionCandles_RenkoFix";
   names[2] = "PriceActionCandles_RenkoFix(1)";
   names[3] = "PriceActionCandles";

   for(int i=0; i<4; i++)
   {
      if(i > 0 && names[i] == names[0])
         continue;

      ResetLastError();
      int h = iCustom(_Symbol,
                      _Period,
                      names[i],
                      PCC_Bars_Count,
                      PCC_Close_Above_Color,
                      PCC_Close_Below_Color,
                      PCC_Center_Line_Color,
                      PCC_Show_Center_Line);

      if(h != INVALID_HANDLE)
      {
         Print("4xTrendForce: using PriceActionCandles indicator: ", names[i]);
         return h;
      }
   }

   return INVALID_HANDLE;
}
//+------------------------------------------------------------------+
int CreateTrendforceHandle()
{
   string names[4];
   names[0] = TrendforceName;
   names[1] = "NRP_Trendforce_Histogram_TrueNRP_MT5_fixed";
   names[2] = "NRP_Trendforce_Histogram_TrueNRP_MT5_fixed(1)";
   names[3] = "RP_Trendforce";

   for(int i=0; i<4; i++)
   {
      if(i > 0 && names[i] == names[0])
         continue;

      ResetLastError();
      int h = iCustom(_Symbol,
                      _Period,
                      names[i],
                      TF_SnakeRange,
                      TF_FilterPeriod,
                      TF_MartFiltr,
                      TF_PriceConst,
                      TF_LevelsCross,
                      TF_Countbars,
                      TF_HistogramWidth,
                      TF_ShowCurrentBar,
                      TF_RecalculateAllBars);

      if(h != INVALID_HANDLE)
      {
         Print("4xTrendForce: using Trendforce indicator: ", names[i]);
         return h;
      }
   }

   return INVALID_HANDLE;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(priceChannelHandle != INVALID_HANDLE)
      IndicatorRelease(priceChannelHandle);

   if(trendforceHandle != INVALID_HANDLE)
      IndicatorRelease(trendforceHandle);
}
//+------------------------------------------------------------------+
void OnTick()
{
   if(Bars(_Symbol, _Period) < 100)
      return;

   datetime currentBarTime = iTime(_Symbol, _Period, 0);

   if(TradeOnNewBarOnly)
   {
      if(currentBarTime == lastBarTime)
         return;

      lastBarTime = currentBarTime;
   }

   if(CurrentSpreadPips() > MaxSpreadPips)
      return;

   int shift = MathMax(1, SignalShift); // previous closed Renko brick/candle only

   double center = GetPCCCenter(shift);
   double tf     = GetTrendforceValue(shift);
   double close1 = iClose(_Symbol, _Period, shift);

   if(center == EMPTY_VALUE || tf == EMPTY_VALUE || close1 == 0.0)
      return;

   bool buySignal  = (close1 > center && tf >=  TF_LevelsCross);
   bool sellSignal = (close1 < center && tf <= -TF_LevelsCross);

   if(CloseOppositeSignal)
   {
      if(CountOpenPositions(POSITION_TYPE_BUY)  > 0 && sellSignal)
         ClosePositions(POSITION_TYPE_BUY);

      if(CountOpenPositions(POSITION_TYPE_SELL) > 0 && buySignal)
         ClosePositions(POSITION_TYPE_SELL);
   }

   // Hedging disabled: do not open if any trade for this symbol/magic remains open.
   // If a position already exists when the EA is attached, treat the startup first-order
   // filter as already satisfied so normal management can continue after that position closes.
   if(CountAllOpenPositions() > 0)
   {
      firstOrderCompleted = true;
      return;
   }

   if(!firstOrderCompleted)
   {
      if(FirstOrderDirection == 1 && sellSignal)
      {
         Print("4xTrendForce startup filter: SELL signal ignored. Waiting for first BUY signal.");
         return;
      }

      if(FirstOrderDirection == 2 && buySignal)
      {
         Print("4xTrendForce startup filter: BUY signal ignored. Waiting for first SELL signal.");
         return;
      }
   }

   if(buySignal)
   {
      if(OpenPosition(ORDER_TYPE_BUY))
         firstOrderCompleted = true;
   }
   else if(sellSignal)
   {
      if(OpenPosition(ORDER_TYPE_SELL))
         firstOrderCompleted = true;
   }
}
//+------------------------------------------------------------------+
double GetPCCCenter(const int shift)
{
   // PriceActionCandles buffer 8 = CenterLine.
   return CopyOneBufferValue(priceChannelHandle, 8, shift);
}
//+------------------------------------------------------------------+
double GetTrendforceValue(const int shift)
{
   // NRP Trendforce MT5 histogram uses buffer 0 = Hist value.
   return CopyOneBufferValue(trendforceHandle, 0, shift);
}
//+------------------------------------------------------------------+
double CopyOneBufferValue(const int handle, const int bufferIndex, const int shift)
{
   if(handle == INVALID_HANDLE)
      return EMPTY_VALUE;

   double value[];
   ArraySetAsSeries(value, true);

   ResetLastError();
   int copied = CopyBuffer(handle, bufferIndex, shift, 1, value);

   if(copied <= 0)
   {
      Print("4xTrendForce CopyBuffer failed. handle=", handle,
            " buffer=", bufferIndex,
            " shift=", shift,
            " error=", GetLastError());
      return EMPTY_VALUE;
   }

   if(value[0] == EMPTY_VALUE || !MathIsValidNumber(value[0]))
      return EMPTY_VALUE;

   return value[0];
}
//+------------------------------------------------------------------+
string GetTradeSymbol()
{
   string ts = TradeSymbol;
   StringTrimLeft(ts);
   StringTrimRight(ts);

   if(ts == "")
      return _Symbol;

   return ts;
}
//+------------------------------------------------------------------+
int TradeDigits()
{
   return (int)SymbolInfoInteger(GetTradeSymbol(), SYMBOL_DIGITS);
}
//+------------------------------------------------------------------+
double CurrentSpreadPips()
{
   MqlTick tick;
   if(!SymbolInfoTick(GetTradeSymbol(), tick))
      return 999999.0;

   return (tick.ask - tick.bid) / PipPoint();
}
//+------------------------------------------------------------------+
double PipPoint()
{
   double point = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(GetTradeSymbol(), SYMBOL_DIGITS);

   if(digits == 3 || digits == 5)
      return point * 10.0;

   return point;
}
//+------------------------------------------------------------------+
int VolumeDigits()
{
   double step = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_STEP);
   if(step <= 0.0)
      return 2;

   int digits = 0;
   while(step < 1.0 && digits < 8)
   {
      step *= 10.0;
      digits++;
   }

   return digits;
}
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
   double minLot  = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_STEP);

   if(lotStep <= 0.0)
      lotStep = 0.01;

   if(minLot <= 0.0)
      minLot = lotStep;

   lots = MathMax(minLot, MathMin(maxLot, lots));
   lots = MathFloor(lots / lotStep) * lotStep;

   return NormalizeDouble(lots, VolumeDigits());
}
//+------------------------------------------------------------------+
double CalculateLots()
{
   if(!UseRiskMoneyMgmt || StopLossPips <= 0)
      return NormalizeLots(FixedLotSize);

   double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;

   double tickValue = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_TRADE_TICK_SIZE);
   double pip       = PipPoint();

   if(tickValue <= 0.0 || tickSize <= 0.0 || pip <= 0.0)
      return NormalizeLots(FixedLotSize);

   double pipValuePerLot = tickValue * (pip / tickSize);

   if(pipValuePerLot <= 0.0)
      return NormalizeLots(FixedLotSize);

   double lots = riskMoney / (StopLossPips * pipValuePerLot);
   return NormalizeLots(lots);
}
//+------------------------------------------------------------------+
bool OpenPosition(const ENUM_ORDER_TYPE type)
{
   MqlTick tick;
   if(!SymbolInfoTick(GetTradeSymbol(), tick))
   {
      Print("4xTrendForce: SymbolInfoTick failed. Error=", GetLastError());
      return false;
   }

   double lots  = CalculateLots();
   double price = (type == ORDER_TYPE_BUY ? tick.ask : tick.bid);
   double sl    = 0.0;
   double tp    = 0.0;
   double pip   = PipPoint();

   if(StopLossPips > 0)
   {
      if(type == ORDER_TYPE_BUY)
         sl = price - StopLossPips * pip;
      else
         sl = price + StopLossPips * pip;

      sl = NormalizeDouble(sl, TradeDigits());
   }

   if(TakeProfitPips > 0)
   {
      if(type == ORDER_TYPE_BUY)
         tp = price + TakeProfitPips * pip;
      else
         tp = price - TakeProfitPips * pip;

      tp = NormalizeDouble(tp, TradeDigits());
   }

   bool sent = false;

   ResetLastError();

   if(type == ORDER_TYPE_BUY)
      sent = trade.Buy(lots, GetTradeSymbol(), 0.0, sl, tp, "4xTrendForce RenkoSignal");
   else if(type == ORDER_TYPE_SELL)
      sent = trade.Sell(lots, GetTradeSymbol(), 0.0, sl, tp, "4xTrendForce RenkoSignal");

   if(!sent)
   {
      Print("4xTrendForce order failed. retcode=", trade.ResultRetcode(),
            " description=", trade.ResultRetcodeDescription(),
            " type=", EnumToString(type),
            " lots=", DoubleToString(lots, VolumeDigits()),
            " sl=", DoubleToString(sl, TradeDigits()),
            " tp=", DoubleToString(tp, TradeDigits()),
            " lastError=", GetLastError());
      return false;
   }

   return true;
}
//+------------------------------------------------------------------+
void ClosePositions(const ENUM_POSITION_TYPE type)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(PositionGetString(POSITION_SYMBOL) != GetTradeSymbol())
         continue;

      if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber)
         continue;

      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type)
         continue;

      ResetLastError();

      if(!trade.PositionClose(ticket))
      {
         Print("4xTrendForce PositionClose failed. Ticket=", ticket,
               " retcode=", trade.ResultRetcode(),
               " description=", trade.ResultRetcodeDescription(),
               " error=", GetLastError());
      }
   }
}
//+------------------------------------------------------------------+
int CountOpenPositions(const ENUM_POSITION_TYPE type)
{
   int count = 0;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(PositionGetString(POSITION_SYMBOL) != GetTradeSymbol())
         continue;

      if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber)
         continue;

      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         count++;
   }

   return count;
}
//+------------------------------------------------------------------+
int CountAllOpenPositions()
{
   int count = 0;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(PositionGetString(POSITION_SYMBOL) != GetTradeSymbol())
         continue;

      if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber)
         continue;

      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      if(type == POSITION_TYPE_BUY || type == POSITION_TYPE_SELL)
         count++;
   }

   return count;
}
//+------------------------------------------------------------------+
