﻿//+------------------------------------------------------------------+
//|          Custom_Grid_Bot AUDCAD 1M V1                            |
//+------------------------------------------------------------------+
#property copyright "Quantitative Developer"
#property link      ""
#property version   "1.00"
#include <Trade\Trade.mqh>

enum ENUM_TIMEFRAME_SELECTION {
    TF_CURRENT = 0,
    TF_M1 = 1,
    TF_M5 = 5,
    TF_M15 = 15,
    TF_H1 = 60,
    TF_D1 = 1440
};

input group "=== 1. Money Management & Risk ==="
input bool               AllowNewInitialTrade = true;
input double             FixedLot = 0.01;
input group "=== 5. Core Strategy & Indicators ==="
input ENUM_TIMEFRAME_SELECTION WorkingTF_BB_RSI = TF_M5;
input int                RSIPeriod = 9;
input double             MaximumRSIValue = 38;
input group "=== 6. Take Profit & Stop Loss ==="
input double             TakeProfitInitialTradePips = 14;
input double             TakeProfitGridPips = 6;
input group "=== 7. Grid & Martingale System ==="
input double             TradeDistance = 10.0;
input double             Multiplier2ndTrade = 2.1;
input double             Multiplier3rdTo5thTrade = 1.3;
input double             Multiplier6thTrade = 2;
input int                MaximumTrades = 9;
input group "=== 9. EA & Misc Settings ==="
input string             TradeComment = "Waka_Scalper";
input long               BasicMagicNumber = 84570;

CTrade         trade;
int            rsiHandle;
double         rsiBuffer[];

ENUM_TIMEFRAMES GetIndicatorTimeframe()
  {
   switch(WorkingTF_BB_RSI)
     {
      case TF_M1:  return PERIOD_M1;
      case TF_M5:  return PERIOD_M5;
      case TF_M15: return PERIOD_M15;
      case TF_H1:  return PERIOD_H1;
      case TF_D1:  return PERIOD_D1;
      default:     return PERIOD_CURRENT;
     }
  }

int OnInit()
  {
   trade.SetExpertMagicNumber(BasicMagicNumber);
   
   ENUM_TIMEFRAMES tf = GetIndicatorTimeframe();
   rsiHandle = iRSI(_Symbol, tf, RSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
     {
      Print("Failed to create RSI handle");
      return(INIT_FAILED);
     }
     
   ArraySetAsSeries(rsiBuffer, true);
   
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(rsiHandle);
  }

void OnTick()
  {
   if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return;

   // 1 Minute OHLC Filter
   static datetime last_bar_time = 0;
   datetime current_time = iTime(_Symbol, PERIOD_M1, 0);
   if(current_time == last_bar_time) return;
   last_bar_time = current_time;

   int totalBuy = GetTotalOpenPositions(POSITION_TYPE_BUY);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // Check Virtual Exit
   if(totalBuy > 0)
     {
      double targetPrice = CalculateBasketTargetPrice(POSITION_TYPE_BUY);
      if(targetPrice > 0 && bid >= targetPrice)
        {
         CloseAllPositions(POSITION_TYPE_BUY);
         return;
        }
     }

   // Entry Logic
   if(totalBuy == 0 && AllowNewInitialTrade)
     {
      CheckInitialEntry(ask);
     }
     
   // Grid Logic
   if(totalBuy > 0 && totalBuy < MaximumTrades)
     {
      if(CheckGridDistance(POSITION_TYPE_BUY, ask))
        {
         double nextLot = CalculateNextLotSize(totalBuy, FixedLot);
         // Open without TP
         trade.Buy(nextLot, _Symbol, ask, 0, 0, TradeComment);
        }
     }
  }

void CheckInitialEntry(double ask)
  {
   if(CopyBuffer(rsiHandle, 0, 0, 2, rsiBuffer) <= 0) return;
   
   double currentRSI = rsiBuffer[0];
   
   if(currentRSI < MaximumRSIValue)
     {
      // Open without TP
      trade.Buy(FixedLot, _Symbol, ask, 0, 0, TradeComment);
     }
  }

int GetTotalOpenPositions(int type)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == BasicMagicNumber)
           {
            if(PositionGetInteger(POSITION_TYPE) == type) count++;
           }
        }
     }
   return count;
  }

void CloseAllPositions(int type)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == BasicMagicNumber)
           {
            if(PositionGetInteger(POSITION_TYPE) == type)
              {
               trade.PositionClose(PositionGetTicket(i));
              }
           }
        }
     }
  }

double CalculateNextLotSize(int positionCount, double initialLot)
  {
   double nextLot = initialLot;
   if(positionCount == 1) nextLot = initialLot * Multiplier2ndTrade;
   else if(positionCount >= 2 && positionCount <= 4) nextLot = initialLot * Multiplier3rdTo5thTrade;
   else if(positionCount >= 5) nextLot = initialLot * Multiplier6thTrade;
   return NormalizeDouble(nextLot, 2);
  }

double GetLastPositionPrice(int type)
  {
   double lastPrice = 0;
   ulong lastTime = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == BasicMagicNumber)
           {
            if(PositionGetInteger(POSITION_TYPE) == type)
              {
               ulong time = PositionGetInteger(POSITION_TIME);
               if(time > lastTime)
                 {
                  lastTime = time;
                  lastPrice = PositionGetDouble(POSITION_PRICE_OPEN);
                 }
              }
           }
        }
     }
   return lastPrice;
  }

bool CheckGridDistance(int type, double currentPrice)
  {
   double lastPrice = GetLastPositionPrice(type);
   if(lastPrice == 0) return false;
   
   double distance = lastPrice - currentPrice;
      
   if(distance >= (TradeDistance * _Point * 10)) return true;
   return false;
  }

double CalculateBasketTargetPrice(int type)
  {
   int count = 0;
   double totalVolume = 0;
   double weightedPrice = 0;
   double totalFees = 0;
   
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == BasicMagicNumber)
           {
            if(PositionGetInteger(POSITION_TYPE) == type)
              {
               double vol = PositionGetDouble(POSITION_VOLUME);
               double price = PositionGetDouble(POSITION_PRICE_OPEN);
               double swap = PositionGetDouble(POSITION_SWAP);
               
               double comm = 0;
               long pos_id = PositionGetInteger(POSITION_IDENTIFIER);
               if(HistorySelectByPosition(pos_id))
                 {
                  int deals = HistoryDealsTotal();
                  for(int d = 0; d < deals; d++)
                    {
                     ulong deal_ticket = HistoryDealGetTicket(d);
                     if(deal_ticket > 0) comm += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
                    }
                 }
               
               totalVolume += vol;
               weightedPrice += price * vol;
               totalFees += (swap + comm);
               count++;
              }
           }
        }
     }
     
   if(count > 0 && totalVolume > 0)
     {
      double averagePrice = weightedPrice / totalVolume;
      double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
      double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      
      if(tickValue > 0 && tickSize > 0)
        {
         double feeOffset = (-totalFees / (totalVolume * tickValue)) * tickSize;
         averagePrice += feeOffset;
        }

      double pips = (count == 1) ? TakeProfitInitialTradePips : TakeProfitGridPips;
      return NormalizeDouble(averagePrice + (pips * _Point * 10), _Digits);
     }
   return 0;
  }
//+------------------------------------------------------------------+