//+------------------------------------------------------------------+
//|                                               LotManagerFull.mqh |
//|                        Copyright 2019, Igor Ryabchikov aka Rigal |
//|                      http://tlap.com/forum/profile/109537-rigal/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Igor Ryabchikov aka Rigal"
#property link      "http://tlap.com/forum/profile/109537-rigal/"
#property strict
#include <SymbolInfo.mqh>
#include <GlobalVariableReader.mqh>

#define GLOBAL_VARIABLE_MAX_BALANCE_REACHED "LotManagerFull_MaxBalance"
class LotManager {
   SymbolInfo* symbolInfo;
   GlobalVariableReader* globalRiskReader;
   double startLot, maxLot, amountDivider, testLot;
   bool keepTrack;
   int stepNum;
   double gridFactors[];
   double gridStartLot[2];
   int maxStep[2];
   bool useTestLot;
   bool recoilless;
   double maxBalance;
   string configStr;
   
   void InitCommon() {
      for(int type = OP_BUY; type <= OP_SELL; type++)
         gridStartLot[type] = GetStartLot();
      stepNum = ArraySize(gridFactors);
      useTestLot = testLot + DBL_EPSILON >= symbolInfo.minLot;
      maxBalance = AccountBalance();
      if(!IsTesting() && GlobalVariableCheck(GLOBAL_VARIABLE_MAX_BALANCE_REACHED))
         maxBalance = MathMax(maxBalance, GlobalVariableGet(GLOBAL_VARIABLE_MAX_BALANCE_REACHED));
      log_info("Lot Manager configuration: ");
      for(int i = 0; i < ArraySize(gridFactors); i++) {
         log_info("Order " + IntegerToString(i) + ": factor = " + DoubleToStr(gridFactors[i]) + "; lots = " + DoubleToStr(GetLots(OP_BUY, i), symbolInfo.lotPrecision));
         configStr = configStr + IntegerToString(i) + ":" + DoubleToStr(gridFactors[i], 5) + ",";
      }
      GetLots(OP_BUY, 0);
   }
   
   void Init(double const &_lotFactors[]) {
      ArrayResize(gridFactors, ArraySize(_lotFactors) + 1);
      gridFactors[0] = 1.0;
      for(int i = 0; i < ArraySize(_lotFactors); i++)
         gridFactors[i + 1] = gridFactors[i] * _lotFactors[i];
      InitCommon();
   }
   void Init(double const _factor, double const _percent, int const _changeEvery, double const _changeFactor, int _firstLevelToMultiply, int const _numberOfLevelsToMultiply) {
      if(_firstLevelToMultiply <= 0)
         _firstLevelToMultiply = 1;
      int arraySize = 1;
      if(_numberOfLevelsToMultiply > 0)
         arraySize = MathMin(_firstLevelToMultiply + _numberOfLevelsToMultiply + 1, 500);
      ArrayResize(gridFactors, arraySize);
      gridFactors[0] = 1.0;
      double increment = 0;
      double accumulatedChangeFactor = 1;
      for(int i = 1; i < ArraySize(gridFactors); i++) {
         int mStep = MathMin(MathMax(i - _firstLevelToMultiply + 1, 0), _numberOfLevelsToMultiply);
         int changeFactorStep = _changeEvery > 0 ? int(mStep/_changeEvery) : 0;
         double changeFactor = MathPow(_changeFactor, changeFactorStep);
         if(i >= _firstLevelToMultiply && i < _firstLevelToMultiply + _numberOfLevelsToMultiply) {
            accumulatedChangeFactor *= changeFactor;
            increment += _percent / 100 * changeFactor;
         }
         double factor = MathPow(_factor, mStep) * accumulatedChangeFactor;
         gridFactors[i] = factor + increment;
//         Print("Order ", i, ": mstep=", mStep, "; chFStep=", changeFactorStep, "; chF=", changeFactor, "; acc=", accumulatedChangeFactor, "; inc=", increment, "; factor=", gridFactors[i]);
      }
      InitCommon();
   }
public:   
   LotManager(SymbolInfo* _symbolInfo, double _startLot, double _amountDivider, double _maxLot, double const &_lotFactors[], double _testLot = 0.0, bool _keepTrack = true, bool _recoilless = false, GlobalVariableReader* _globalRiskReader = NULL) {
      symbolInfo = _symbolInfo;
      startLot = _startLot;
      amountDivider = _amountDivider;
      maxLot = (_maxLot < symbolInfo.minLot ? symbolInfo.maxLot : _maxLot);
      testLot = _testLot;
      keepTrack = _keepTrack;
      recoilless = _recoilless;
      globalRiskReader = _globalRiskReader;
//      configStr = DoubleToStr(_testLot) + "," + DoubleToStr(_maxLot) + ",";
      Init(_lotFactors);
   }

   LotManager(SymbolInfo* _symbolInfo, double _startLot, double _amountDivider, double _maxLot, string _lotFactors, double _testLot = 0.0, bool _keepTrack = true, bool _recoilless = false, GlobalVariableReader* _globalRiskReader = NULL) {
      symbolInfo = _symbolInfo;
      string strFactors[];
      if(StringLen(_lotFactors) > 0)
         StringSplit(_lotFactors, ',', strFactors);
      double inputFactors[];
      ArrayResize(inputFactors, ArraySize(strFactors));
      for(int i = 0; i < ArraySize(strFactors); i++)
         inputFactors[i] = StringToDouble(strFactors[i]);
      startLot = _startLot;
      amountDivider = _amountDivider;
      maxLot = (_maxLot < symbolInfo.minLot ? symbolInfo.maxLot : _maxLot);
      testLot = _testLot;
      keepTrack = _keepTrack;
      recoilless = _recoilless;
      globalRiskReader = _globalRiskReader;
//      configStr = DoubleToStr(_testLot) + "," + DoubleToStr(_maxLot) + ",";
      Init(inputFactors);
   }
   
   LotManager(SymbolInfo* _symbolInfo, double _startLot, double _amountDivider, double _maxLot, double _factor = 1.0, double _percent = 0.0, 
                  int _changeEvery = 0, double _changeFactor = 1.0, int _firstLevelToMultiply = 1, int _numberOfLevelsToMultiply = 500, double _testLot = 0.0, bool _keepTrack = true, bool _recoilless = false, GlobalVariableReader* _globalRiskReader = NULL) {
      symbolInfo = _symbolInfo;
      startLot = _startLot;
      amountDivider = _amountDivider;
      maxLot = (_maxLot < symbolInfo.minLot ? symbolInfo.maxLot : _maxLot);
      testLot = _testLot;
      keepTrack = _keepTrack;
      recoilless = _recoilless;
      globalRiskReader = _globalRiskReader;
//      configStr = DoubleToStr(_testLot) + "," + DoubleToStr(_maxLot) + ",";
      Init(_factor, _percent, _changeEvery, _changeFactor, _firstLevelToMultiply, _numberOfLevelsToMultiply);
   }
   //half-initialization, to be followed by the special "init" method invocation
   LotManager(SymbolInfo* _symbolInfo, double _maxLot, double _factor, double _percent = 0.0, 
                  int _changeEvery = 0, double _changeFactor = 1.0, int _firstLevelToMultiply = 1, int _numberOfLevelsToMultiply = 500, double _testLot = 0.0, bool _keepTrack = true, bool _recoilless = false, GlobalVariableReader* _globalRiskReader = NULL) {
      symbolInfo = _symbolInfo;
      maxLot = (_maxLot < symbolInfo.minLot ? symbolInfo.maxLot : _maxLot);
      testLot = _testLot;
      keepTrack = _keepTrack;
      recoilless = _recoilless;
      globalRiskReader = _globalRiskReader;
//      configStr = DoubleToStr(_testLot) + "," + DoubleToStr(_maxLot) + ",";
      Init(_factor, _percent, _changeEvery, _changeFactor, _firstLevelToMultiply, _numberOfLevelsToMultiply);
   }
   void Init(double _startLot, double _amountDivider) {
      startLot = _startLot;
      amountDivider = _amountDivider;
      for(int type = OP_BUY; type <= OP_SELL; type++)
         gridStartLot[type] = GetStartLot();
   } 
   ~LotManager() {
      if(!IsTesting()) {
         if(!GlobalVariableCheck(GLOBAL_VARIABLE_MAX_BALANCE_REACHED)) {
            GlobalVariableSet(GLOBAL_VARIABLE_MAX_BALANCE_REACHED, maxBalance);
         } else {
            int attempt = 10;
            while(attempt-- > 0) {
               double check = GlobalVariableGet(GLOBAL_VARIABLE_MAX_BALANCE_REACHED);
               if(maxBalance > check) {
                  if(GlobalVariableSetOnCondition(GLOBAL_VARIABLE_MAX_BALANCE_REACHED, maxBalance, check))
                     break;
               } else {
                  break;
               }
            }
         }
      }
      if(CheckPointer(globalRiskReader) == POINTER_DYNAMIC)
         delete (globalRiskReader);

   }
   string GetConfigStr() {
      return(configStr);
   }
   void SetAmountDivider(double _value) {
      amountDivider = _value;
   }              
   void SetStartLot(double _value) {
      startLot = _value;
   }              
   double GetStartLot() {
      double result = startLot;
      if(amountDivider >= 0.01) {
         double balance = AccountBalance();
         if(recoilless) {
            maxBalance = MathMax(balance, maxBalance);
            if(!IsTesting() && GlobalVariableCheck(GLOBAL_VARIABLE_MAX_BALANCE_REACHED))
               maxBalance = MathMax(maxBalance, GlobalVariableGet(GLOBAL_VARIABLE_MAX_BALANCE_REACHED));
            balance = maxBalance;
         }
         result *= MathMax(1.0, MathFloor(balance / amountDivider));
      }
      if(CheckPointer(globalRiskReader) != POINTER_INVALID)
         result *= globalRiskReader.GetValue();
      return (NormalizeLots(result));
   }
   
   void SetGridStartLot(int _type, double _lot) {
      gridStartLot[_type] = NormalizeDouble(_lot, symbolInfo.lotPrecision);
   } 
   string ToString() const {
      string lotFactorsStr;
      
      for(int i = 1; i < stepNum; i++)
         lotFactorsStr = lotFactorsStr + DoubleToStr(gridFactors[i], 2) + (i == stepNum - 1 ? "" : ",");

      return ("LotFactors: [" + lotFactorsStr + "]");
   }

   double GetLots(int _type, int _orderCount) {
      if(_orderCount == 0){
         gridStartLot[_type] = GetStartLot();
         maxStep[_type] = 0;
      } else if(_orderCount > maxStep[_type]) {
         maxStep[_type] = _orderCount;
      }
      if(keepTrack)
         _orderCount = maxStep[_type];
      if(useTestLot)
         return (NormalizeLots(NormalizeLots(testLot * gridFactors[_orderCount >= stepNum ? stepNum - 1 : _orderCount]) * gridStartLot[_type] / testLot));
      return (NormalizeLots(gridStartLot[_type] * gridFactors[_orderCount >= stepNum ? stepNum - 1 : _orderCount]));
   }   
   double GetStartLot(int _type) {
      return (gridStartLot[_type]);
   }
   double GetStartLotScale(int _type) {
      return (gridStartLot[_type] / startLot);
   }
   double NormalizeLots(double _lots) const {
      double result = NormalizeDouble(_lots / symbolInfo.lotStep, 0) * symbolInfo.lotStep;
      if (result < symbolInfo.minLot)
         result = symbolInfo.minLot;
      if (result > maxLot)
         result = maxLot;
      return NormalizeDouble(result, symbolInfo.lotPrecision);
   }
   
};

