//+------------------------------------------------------------------+
//|                                                4xTrendForce.mq4   |
//|  Price Channel Central + NRP Trendforce FAST Expert Advisor        |
//+------------------------------------------------------------------+
#property strict
#property version   "1.20"

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

//---- indicator file names, without .mq4/.ex4
extern string   PriceChannelName     = "Price_Channel_Central_Colored_Candles";
extern string   TrendforceName       = "NRP_Trendforce_Histogram_TrueNRP_FAST";

//---- Price_Channel_Central_Colored_Candles inputs
extern int      PCC_Bars_Count        = 32;
extern color    PCC_Close_Above_Color = clrLime;
extern color    PCC_Close_Below_Color = clrRed;
extern color    PCC_Center_Line_Color = clrDodgerBlue;
extern bool     PCC_Show_Center_Line  = false;
extern bool     PCC_Show_Signal_Label = false;

//---- NRP_Trendforce_Histogram_TrueNRP_FAST inputs
// Match the optimized indicator input order exactly.
extern int      TF_SnakeRange        = 200;
extern int      TF_FilterPeriod      = 250;
extern double   TF_MartFiltr         = 500.0;
extern int      TF_PriceConst        = 1;
extern double   TF_LevelsCross       = 0.95;
extern int      TF_Countbars         = 1000;
extern int      TF_HistogramWidth    = 2;
extern bool     TF_ShowCurrentBar    = false;
extern bool     TF_FastClosedBarMode = true;

//---- internal state
static datetime lastBarTime = 0;

//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
   if(Bars < 100) return;

   if(TradeOnNewBarOnly)
   {
      if(Time[0] == lastBarTime) return;
      lastBarTime = Time[0];
   }

   RefreshRates();
   if(CurrentSpreadPips() > MaxSpreadPips) return;

   int shift = 1; // previous closed candle only

   double center = GetPCCCenter(shift);
   double tf     = GetTrendforceValue(shift);

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

   bool buySignal  = (Close[shift] > center && tf >=  TF_LevelsCross);
   bool sellSignal = (Close[shift] < center && tf <= -TF_LevelsCross);

   if(CloseOppositeSignal)
   {
      if(CountOpenOrders(OP_BUY)  > 0 && sellSignal) CloseOrders(OP_BUY);
      if(CountOpenOrders(OP_SELL) > 0 && buySignal)  CloseOrders(OP_SELL);
   }

   // Hedging disabled: do not open if any trade for this symbol/magic remains open.
   if(CountAllOpenOrders() > 0) return;

   if(buySignal)  OpenOrder(OP_BUY);
   if(sellSignal) OpenOrder(OP_SELL);
}
//+------------------------------------------------------------------+
double GetPCCCenter(int shift)
{
   // Center line is buffer 8 in Price_Channel_Central_Colored_Candles.
   return(iCustom(NULL, 0, PriceChannelName,
                  PCC_Bars_Count,
                  PCC_Close_Above_Color,
                  PCC_Close_Below_Color,
                  PCC_Center_Line_Color,
                  PCC_Show_Center_Line,
                  PCC_Show_Signal_Label,
                  8, shift));
}
//+------------------------------------------------------------------+
double GetTrendforceValue(int shift)
{
   // NRP_Trendforce_Histogram_TrueNRP_FAST buffers: 0=buy histogram, 1=sell histogram, 2=neutral histogram.
   double buyVal = iCustom(NULL, 0, TrendforceName,
                           TF_SnakeRange,
                           TF_FilterPeriod,
                           TF_MartFiltr,
                           TF_PriceConst,
                           TF_LevelsCross,
                           TF_Countbars,
                           TF_HistogramWidth,
                           TF_ShowCurrentBar,
                           TF_FastClosedBarMode,
                           0, shift);

   if(buyVal != EMPTY_VALUE) return(buyVal);

   double sellVal = iCustom(NULL, 0, TrendforceName,
                            TF_SnakeRange,
                            TF_FilterPeriod,
                            TF_MartFiltr,
                            TF_PriceConst,
                            TF_LevelsCross,
                            TF_Countbars,
                            TF_HistogramWidth,
                            TF_ShowCurrentBar,
                            TF_FastClosedBarMode,
                            1, shift);

   if(sellVal != EMPTY_VALUE) return(sellVal);

   double neutralVal = iCustom(NULL, 0, TrendforceName,
                               TF_SnakeRange,
                               TF_FilterPeriod,
                               TF_MartFiltr,
                               TF_PriceConst,
                               TF_LevelsCross,
                               TF_Countbars,
                               TF_HistogramWidth,
                               TF_ShowCurrentBar,
                               TF_FastClosedBarMode,
                               2, shift);

   if(neutralVal != EMPTY_VALUE) return(neutralVal);

   return(EMPTY_VALUE);
}
//+------------------------------------------------------------------+
double CurrentSpreadPips()
{
   return((Ask - Bid) / PipPoint());
}
//+------------------------------------------------------------------+
double PipPoint()
{
   if(Digits == 3 || Digits == 5) return(Point * 10.0);
   return(Point);
}
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
   double minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

   if(lotStep <= 0.0) lotStep = 0.01;

   lots = MathMax(minLot, MathMin(maxLot, lots));
   lots = MathFloor(lots / lotStep) * lotStep;
   return(NormalizeDouble(lots, 2));
}
//+------------------------------------------------------------------+
double CalculateLots()
{
   if(!UseRiskMoneyMgmt || StopLossPips <= 0)
      return(NormalizeLots(FixedLotSize));

   double riskMoney = AccountBalance() * RiskPercent / 100.0;
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   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));
}
//+------------------------------------------------------------------+
void OpenOrder(int type)
{
   RefreshRates();

   double lots  = CalculateLots();
   double price = (type == OP_BUY ? Ask : Bid);
   double sl    = 0.0;
   double tp    = 0.0;
   double pip   = PipPoint();

   if(StopLossPips > 0)
   {
      if(type == OP_BUY) sl = price - StopLossPips * pip;
      else               sl = price + StopLossPips * pip;
      sl = NormalizeDouble(sl, Digits);
   }

   if(TakeProfitPips > 0)
   {
      if(type == OP_BUY) tp = price + TakeProfitPips * pip;
      else               tp = price - TakeProfitPips * pip;
      tp = NormalizeDouble(tp, Digits);
   }

   price = NormalizeDouble(price, Digits);

   int ticket = OrderSend(Symbol(), type, lots, price, Slippage, sl, tp,
                          "4xTrendForce", MagicNumber, 0,
                          (type == OP_BUY ? clrDodgerBlue : clrSlateBlue));

   if(ticket < 0)
      Print("4xTrendForce OrderSend failed. Error=", GetLastError(),
            " type=", type, " lots=", lots, " price=", price,
            " sl=", sl, " tp=", tp);
}
//+------------------------------------------------------------------+
void CloseOrders(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol() != Symbol()) continue;
      if(OrderMagicNumber() != MagicNumber) continue;
      if(OrderType() != type) continue;

      RefreshRates();
      double closePrice = (type == OP_BUY ? Bid : Ask);
      closePrice = NormalizeDouble(closePrice, Digits);

      bool closed = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrSilver);
      if(!closed)
         Print("4xTrendForce OrderClose failed. Ticket=", OrderTicket(),
               " Error=", GetLastError());
   }
}
//+------------------------------------------------------------------+
int CountOpenOrders(int type)
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol() != Symbol()) continue;
      if(OrderMagicNumber() != MagicNumber) continue;
      if(OrderType() == type) count++;
   }
   return(count);
}
//+------------------------------------------------------------------+
int CountAllOpenOrders()
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol() != Symbol()) continue;
      if(OrderMagicNumber() != MagicNumber) continue;
      if(OrderType() == OP_BUY || OrderType() == OP_SELL) count++;
   }
   return(count);
}
//+------------------------------------------------------------------+
