//+------------------------------------------------------------------+
//|                    Unit audcad 15m BB V1                         |
//+------------------------------------------------------------------+      
#property copyright "DarkVenus SmartRecovery"
#property link      ""
#property version   "1.01"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>

//+------------------------------------------------------------------+
//|                  Core 15m                                        |
//+------------------------------------------------------------------+ 
input group "Core"
input int InpBBPeriod = 18;
input double InpBBDeviation  = 1.3;
input group "RSI Filter"
input int InpRsiPeriod = 16;
input double InpRsiOverbought = 70;
input double InpRsiOversold = 40;
input group "Trend Filter"
input ENUM_TIMEFRAMES InpTrendTF = PERIOD_H8;
input int InpMAPeriod = 90;
input group "Recovery Settings"
input double InpRecoveryPercent = 70; 
input double InpBasketTPPips = 5;
input int InpMaxTrades = 10;

//+------------------------------------------------------------------+
//|              Grid Distances Lot Settings                         |
//+------------------------------------------------------------------+ 
input group "Grid Distances Lot Settings"
input int InpDist01 = 6;
input double InpLot01 = 0.01;
input int InpDist02 = 16;
input double InpLot02 = 0.01;
input int InpDist03 = 25;
input double InpLot03 = 0.01;
input int InpDist04 = 30;
input double InpLot04 = 0.01;
input int InpDist05 = 30;
input double InpLot05 = 0.01;
input group "Grid+Lot 2"
input int InpDist06 = 35;
input double InpLot06 = 0.01;
input int InpDist07 = 45;
input double InpLot07 = 0.01;
input int InpDist08 = 70;
input double InpLot08 = 0.01;
input int InpDist09 = 10;
input double InpLot09 = 0.01;
input int InpDist10 =95;
input double InpLot10 = 0.01;

//+------------------------------------------------------------------+
//|                         CTrade                                   |
//+------------------------------------------------------------------+ 
const ulong InpMagicNumber = 1;
CTrade         trade;
CPositionInfo  posInfo;
CSymbolInfo    symInfo;
int bb_handle;
int ma_handle;
int rsi_handle;
double bb_upper[];
double bb_lower[];
double ma_buffer[];
double rsi_buffer[];

//+------------------------------------------------------------------+
//|                         OnInit                                   |
//+------------------------------------------------------------------+ 
int OnInit()
  {
   trade.SetExpertMagicNumber(InpMagicNumber);
   if(!symInfo.Name(_Symbol)) return INIT_FAILED;
   bb_handle = iBands(_Symbol, PERIOD_CURRENT, InpBBPeriod, 0, InpBBDeviation, PRICE_CLOSE);
   ma_handle = iMA(_Symbol, InpTrendTF, InpMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, InpRsiPeriod, PRICE_CLOSE);
   if(bb_handle == INVALID_HANDLE || ma_handle == INVALID_HANDLE || rsi_handle == INVALID_HANDLE) 
      return INIT_FAILED;
   ArraySetAsSeries(bb_upper, true);
   ArraySetAsSeries(bb_lower, true);
   ArraySetAsSeries(ma_buffer, true);
   ArraySetAsSeries(rsi_buffer, true);
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//|                       OnDeinit                                   |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason){
   IndicatorRelease(bb_handle);
   IndicatorRelease(ma_handle);
   IndicatorRelease(rsi_handle);
  }
double GetPipSize(){
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   if(digits == 5 || digits == 3) return point * 10.0;
   return point;
  }
double GetGridLotSize(int grid_level){
   switch(grid_level){
      case 1:  return InpLot01;
      case 2:  return InpLot02;
      case 3:  return InpLot03;
      case 4:  return InpLot04;
      case 5:  return InpLot05;
      case 6:  return InpLot06;
      case 7:  return InpLot07;
      case 8:  return InpLot08;
      case 9:  return InpLot09;
      case 10: return InpLot10;
      default: return InpLot10;
     }
  }
double GetGridDistance(int current_count){
   switch(current_count){
      case 1:  return InpDist01;
      case 2:  return InpDist02;
      case 3:  return InpDist03;
      case 4:  return InpDist04;
      case 5:  return InpDist05;
      case 6:  return InpDist06;
      case 7:  return InpDist07;
      case 8:  return InpDist08;
      case 9:  return InpDist09;
      case 10: return InpDist10;
      default: return InpDist10;
     }
  }
  
//+------------------------------------------------------------------+
//|                RecoverSingleSide                                 |
//+------------------------------------------------------------------+ 
void RecoverSingleSide(ENUM_POSITION_TYPE type){
   int count = 0;
   ulong oldest_ticket = 0;
   ulong newest_ticket = 0;
   datetime oldest_time = INT_MAX;
   datetime newest_time = 0;
   double oldest_loss = 0;
   double newest_profit = 0;
   double oldest_vol = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      if(posInfo.SelectByIndex(i)){
         if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber && posInfo.PositionType() == type){
            count++;
            datetime open_time = (datetime)PositionGetInteger(POSITION_TIME);
            double profit = posInfo.Profit() + posInfo.Swap() + posInfo.Commission();
            if(open_time < oldest_time || oldest_ticket == 0)
              {
               oldest_time = open_time;
               oldest_ticket = posInfo.Ticket();
               oldest_loss = profit;
               oldest_vol = posInfo.Volume();
              }
            if(open_time > newest_time || newest_ticket == 0)
              {
               newest_time = open_time;
               newest_ticket = posInfo.Ticket();
               newest_profit = profit;
              }
           }
        }
     }
   if(count >= 2 && newest_ticket != oldest_ticket && newest_ticket > 0 && oldest_ticket > 0)
     {
      if(newest_profit > 0 && oldest_loss < 0)
        {
         double money_to_spend = newest_profit * (InpRecoveryPercent / 100.0);
         double loss_per_lot = MathAbs(oldest_loss) / oldest_vol;
         if(loss_per_lot > 0)
           {
            double vol_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
            double vol_min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
            double volume_to_close = MathFloor((money_to_spend / loss_per_lot) / vol_step) * vol_step;
            if(volume_to_close >= vol_min)
              {
               if(trade.PositionClose(newest_ticket))
                 {
                  if(volume_to_close >= oldest_vol)
                    {
                     trade.PositionClose(oldest_ticket);
                    }
                  else
                    {
                     trade.PositionClosePartial(oldest_ticket, volume_to_close);
                    }
                 }
              }
           }
        }
     }
  }
void CloseAll(ENUM_POSITION_TYPE type){
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      if(posInfo.SelectByIndex(i)){
         if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber && posInfo.PositionType() == type){
            trade.PositionClose(posInfo.Ticket());
           }
        }
     }
  }
//+------------------------------------------------------------------+
//|                        OnTick                                    |
//+------------------------------------------------------------------+ 
void OnTick(){
  static datetime last_time = 0;
   datetime current_time = TimeCurrent();
   if(current_time == last_time) return;
   last_time = current_time;
   if(!symInfo.RefreshRates()) return;
   double pip_size = GetPipSize();
   if(pip_size == 0) return;
   if(PositionsTotal() >= 2){
      RecoverSingleSide(POSITION_TYPE_BUY);
      RecoverSingleSide(POSITION_TYPE_SELL);
     }
   int buy_count = 0;
   int sell_count = 0;
   double total_buy_volume = 0;
   double last_buy_price = 0;
   double buy_profit_money = 0;
   double total_sell_volume = 0;
   double last_sell_price = 0;
   double sell_profit_money = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(posInfo.SelectByIndex(i))
        {
         if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber)
           {
            if(posInfo.PositionType() == POSITION_TYPE_BUY)
              {
               buy_count++;
               total_buy_volume += posInfo.Volume();
               buy_profit_money += posInfo.Profit() + posInfo.Swap() + posInfo.Commission();
               if(last_buy_price == 0 || posInfo.PriceOpen() < last_buy_price)
                 {
                  last_buy_price = posInfo.PriceOpen();
                 }
              }
            else if(posInfo.PositionType() == POSITION_TYPE_SELL)
              {
               sell_count++;
               total_sell_volume += posInfo.Volume();
               sell_profit_money += posInfo.Profit() + posInfo.Swap() + posInfo.Commission();
               if(last_sell_price == 0 || posInfo.PriceOpen() > last_sell_price)
                 {
                  last_sell_price = posInfo.PriceOpen();
                 }
              }
           }
        }
     }
   double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(buy_count > 0)
     {
      double target_buy_profit = (InpBasketTPPips * pip_size / tick_size) * tick_value * total_buy_volume;
      
      if(buy_profit_money >= target_buy_profit)
        {
         CloseAll(POSITION_TYPE_BUY);
         buy_count = 0;
        }
      else if(buy_count < InpMaxTrades)
        {
         double grid_distance = GetGridDistance(buy_count);
         if((last_buy_price - symInfo.Ask()) / pip_size >= grid_distance)
           {
            double new_lot = NormalizeDouble(GetGridLotSize(buy_count + 1), 2);
            trade.Buy(new_lot, _Symbol, symInfo.Ask(), 0, 0, "");
           }
        }
     }
   if(sell_count > 0)
     {
      double target_sell_profit = (InpBasketTPPips * pip_size / tick_size) * tick_value * total_sell_volume;
      
      if(sell_profit_money >= target_sell_profit)
        {
         CloseAll(POSITION_TYPE_SELL);
         sell_count = 0;
        }
      else if(sell_count < InpMaxTrades)
        {
         double grid_distance = GetGridDistance(sell_count);
         if((symInfo.Bid() - last_sell_price) / pip_size >= grid_distance)
           {
            double new_lot = NormalizeDouble(GetGridLotSize(sell_count + 1), 2);
            trade.Sell(new_lot, _Symbol, symInfo.Bid(), 0, 0, "");
           }
        }
     }
   if(buy_count == 0 && sell_count == 0)
     {
      if(CopyBuffer(bb_handle, 1, 0, 2, bb_upper) <= 0) return;
      if(CopyBuffer(bb_handle, 2, 0, 2, bb_lower) <= 0) return;
      if(CopyBuffer(ma_handle, 0, 0, 2, ma_buffer) <= 0) return;
      if(CopyBuffer(rsi_handle, 0, 0, 2, rsi_buffer) <= 0) return;
      bool is_uptrend = (symInfo.Bid() > ma_buffer[1]);
      bool is_downtrend = (symInfo.Ask() < ma_buffer[1]);
      if(symInfo.Ask() <= bb_lower[0] && rsi_buffer[0] <= InpRsiOversold && is_uptrend)
        {
         double initial_lot = NormalizeDouble(GetGridLotSize(1), 2);
         trade.Buy(initial_lot, _Symbol, symInfo.Ask(), 0, 0, "");
        }
      if(symInfo.Bid() >= bb_upper[0] && rsi_buffer[0] >= InpRsiOverbought && is_downtrend)
        {
         double initial_lot = NormalizeDouble(GetGridLotSize(1), 2);
         trade.Sell(initial_lot, _Symbol, symInfo.Bid(), 0, 0, "");
        }
     }
  }
//+------------------------------------------------------------------+