using System;
using System.Collections.Generic;
using MarketData.MarketDataModel;
using MarketData.Utils;
using MarketData.Cache;
using MarketData.Numerical;
using MarketData.Generator.ModelGenerators;
using StopLimit=MarketData.Generator.Model.StopLimit;

namespace MarketData.Generator.MGSHMomentum
{
    public class HedgeManager
    {
      private static int PRICING_DAYS = 120;

      public HedgeManager()
      {
      }

      public String HedgeShortSymbol { get; set; } = "SH";

      public bool Verbose {get;set;} = false;

      private void Display(String message)
      {
        if(Verbose)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,message);
        }
      }

      public bool IsOpenHedgeIndicator(DateTime analysisDate, int hedgeCloseGreaterSMANDays=10, int hedgeBandBreakCheckDays=3) 
      {
        Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS);

        if(null==prices || !prices[0].Date.Date.Equals(analysisDate.Date))
        {
          Display(String.Format("Cannot evaluate IsOpenHedgeIndicator for candidate {0} due to lack of current price on {1}",
            HedgeShortSymbol,analysisDate.ToShortDateString()));
          return false;
        }

        BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices); // K is upper band, KL1 below that.  L is lower band, LP1 above that
        BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate();
        if(!bollingerBandElementsByDate.ContainsKey(analysisDate))
        {
          Display(String.Format("Cannot evaluate IsOpenHedgeIndicator for candidate {0} due to lack of current price on {1}",HedgeShortSymbol,analysisDate.ToShortDateString()));
          return false;
        }
        BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate];

        if(bollingerBandElement.Close < bollingerBandElement.L )
        {
          Display(String.Format($"IsOpenHedgeIndicator AnalysisDate:{analysisDate.ToShortDateString()}"));

          SlopeManager slopeManager = new SlopeManager(bollingerBandElementsByDate, analysisDate){Verbose=Verbose};

          slopeManager.DisplaySlopes();

          if(!CloseGreaterSMAN(analysisDate, bollingerBandElementsByDate, hedgeCloseGreaterSMANDays))
          {
            return false;
          }

          if(!BandBreakCheck(analysisDate, bollingerBandElementsByDate, hedgeBandBreakCheckDays))  // 3
          {
            return false;
          }

          int bucketDays=5;
          int closeLessL=GetCloseLessL(analysisDate, bollingerBandElementsByDate, bucketDays);
          int closeBetweenLAndLP1=GetCloseBetweenLAndLP1(analysisDate, bollingerBandElementsByDate, bucketDays);
          int closeBetweenLP1AndSMAN=GetCloseBetweenLP1AndSMAN(analysisDate, bollingerBandElementsByDate, bucketDays);
          int closeBetweenSMANAndKL1=GetCloseBetweenSMANAndKL1(analysisDate, bollingerBandElementsByDate, bucketDays);
          int closeBetweenKL1AndK=GetCloseBetweenKL1AndK(analysisDate, bollingerBandElementsByDate, bucketDays);
          int closeGreaterK=GetCloseGreaterK(analysisDate, bollingerBandElementsByDate, bucketDays);
          Display($"Close buckets <L:{closeLessL} L/LP1:{closeBetweenLAndLP1} LP1/SMAN:{closeBetweenLP1AndSMAN} SMAN/KL1:{closeBetweenSMANAndKL1} KL1/K:{closeBetweenKL1AndK} >K:{closeGreaterK} KLSpread:{slopeManager.GetKLSpread()}");

// if the close is spending a lot of time between L and LP1 then make sure the overall volatility spread is widening sufficiently in the 5, 10, 30 day terms
          if(closeBetweenLAndLP1 >= bucketDays && !slopeManager.IsMatchKLSpread("??+++"))
          {
            return false;
          }


        Display(String.Format("Lower band break detected for {0} on {1} Price Close:{2} LP1:{3} ",
           HedgeShortSymbol,
           analysisDate.ToShortDateString(),
           Utility.FormatCurrency(bollingerBandElement.Close),
           Utility.FormatCurrency(bollingerBandElement.L)
           ));
          Display($"TRUE");
          return true;
        }
        return false;
      }

      /// <summary>
      /// Mainains and adjusts the stop limits for teh hedge position
      /// </summary>
      /// <param name="analysisDate"></param>
      /// <param name="position"></param>
      /// <param name="configuration"></param>
      /// <returns></returns>
      public StopLimit EvaluateStopPriceHedge(DateTime analysisDate, MGSHPosition position, MGSHConfiguration configuration)
      {
        DateGenerator dateGenerator=new DateGenerator();
        Price currentPrice=GBPriceCache.GetInstance().GetPrice(position.Symbol,analysisDate);
        double trailingStop=position.InitialStopLimit+Math.Floor((currentPrice.Low-position.PurchasePrice)/position.R)*position.R; 
        double trailingStopScaled=trailingStop;
        double daysHeld=Math.Abs(dateGenerator.DaysBetweenActual(position.PurchaseDate,analysisDate));

        if(Utility.IsEpoch(position.LastStopAdjustment))          // we've never adjusted the stop price
        {
            BollingerBandElementsByDate bollingerBandElementsByDate = GetBollingerBandElementsByDate(analysisDate);
            if(null == bollingerBandElementsByDate)return null;
            trailingStopScaled=GetHedgeStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop, configuration.StopLimitScalingVolatilityDays, configuration.HedgeATRMultiplier);
            trailingStop=Math.Max(trailingStop,trailingStopScaled);
            if(trailingStop >= currentPrice.Low || trailingStop >= currentPrice.Close)
            {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.",
                position.Symbol,
                Utility.FormatCurrency(trailingStop),
                Utility.FormatCurrency(currentPrice.Low)));
              return null;
            }
            if(Numerics.Round(trailingStop) <= (Numerics.Round(position.TrailingStopLimit)))
					  {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be less than or equal to the existing stop limit of {2}.",
                position.Symbol,
                Utility.FormatCurrency(trailingStop),
                Utility.FormatCurrency(position.TrailingStopLimit)));
              return null;
					  }
            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T   S T O P  L I M I T ************************"));
            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit on {0} after {1} days for {2} from {3} to {4}.  Purchase Price: {5} Current Price:{6} Spread:{7} Shares:{8}",
              analysisDate.ToShortDateString(),
              daysHeld,
              position.Symbol,
              Utility.FormatCurrency(position.TrailingStopLimit),
              Utility.FormatCurrency(trailingStop),
              Utility.FormatCurrency(position.PurchasePrice),
              Utility.FormatCurrency(currentPrice.Close),
              Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop),
              Utility.FormatNumber(position.Shares,2)));

            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("*****************************************************************************************"));
            StopLimit newStopLimit=new StopLimit
            {
              StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate),
              Symbol=position.Symbol,
              AnalysisDate=analysisDate,
              PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit,
              NewStop=trailingStop,
              CurrentPriceLow=currentPrice.Low,
              CurrentPriceClose=currentPrice.Close,
              PriceTrendIndicatorSlope=0.00
            };
            position.TrailingStopLimit=trailingStop;
            position.LastStopAdjustment=analysisDate;
            return newStopLimit;
        }
        else  // we have already made prior stop adjustments
        {
          int daysSinceLastStopAdjustment=Math.Abs(dateGenerator.DaysBetweenActual(position.LastStopAdjustment,analysisDate));
          if(daysSinceLastStopAdjustment>=configuration.HedgeMinDaysBetweenStopAdjustments)
          {
            trailingStopScaled=GetHedgeStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop,daysSinceLastStopAdjustment,configuration.HedgeATRMultiplier);
            trailingStop=Math.Max(trailingStop,trailingStopScaled);
            if(trailingStop>=currentPrice.Low||trailingStop>=currentPrice.Close)
            {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(currentPrice.Low)));
              return null;
            }
            if(Numerics.Round(position.TrailingStopLimit) < Numerics.Round(trailingStop))  // round the stop limits to fractionals don't look like differences
            {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T   S T O P  L I M I T ************************"));
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit on {0} after {1} days for {2} from {3} to {4}.  Purchase Price: {5} Current Price:{6} Spread:{7} Shares:{8}",
                analysisDate.ToShortDateString(),
                daysHeld,
                position.Symbol,
                Utility.FormatCurrency(position.TrailingStopLimit),
                Utility.FormatCurrency(trailingStop),
                Utility.FormatCurrency(position.PurchasePrice),
                Utility.FormatCurrency(currentPrice.Close),
                Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop),
                Utility.FormatNumber(position.Shares,2)));
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************************************************************************"));
              StopLimit newStopLimit=new StopLimit
              {
                StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate),
                Symbol=position.Symbol,
                AnalysisDate=analysisDate,
                PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit,
                NewStop=trailingStop,
                CurrentPriceLow=currentPrice.Low,
                CurrentPriceClose=currentPrice.Close,
                PriceTrendIndicatorSlope=0.00
              };
              position.TrailingStopLimit=trailingStop;
              position.LastStopAdjustment=analysisDate;
              return newStopLimit;
            }
            else
            {
              double currentTrailingStopLimit=Numerics.Round(position.TrailingStopLimit);
              double suggestedTrailingStopLimit=Numerics.Round(trailingStop);
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Not Adjusting Stop Limit for {0} because the new trailing stop limit would be less than or equal to the current trailing stop limit. Current Trailing Stop Limit:{1}, Calculated Trailing Stop Limit:{2}.",
                position.Symbol,
                Utility.FormatCurrency(currentTrailingStopLimit),
                Utility.FormatCurrency(suggestedTrailingStopLimit)));
              return null;
            }
          }
          else
          {
            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The trailing stop for {0} was adjusted {1} days ago.  HedgeMinDaysBetweenStopAdjustments is {2}",
               position.Symbol,
               daysSinceLastStopAdjustment,
               configuration.HedgeMinDaysBetweenStopAdjustments));
            return null;
          }
        }
      }

      private int GetCloseLessL(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close < bollingerBandElement.L)count++;
        }
        return count;
      }

      private int GetCloseBetweenLAndLP1(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close >= bollingerBandElement.L && bollingerBandElement.Close<bollingerBandElement.LP1 )count++;
        }
        return count;
      }

      private int GetCloseBetweenLP1AndSMAN(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close >= bollingerBandElement.LP1 && bollingerBandElement.Close<bollingerBandElement.SMAN )count++;
        }
        return count;
      }

      private int GetCloseBetweenSMANAndKL1(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close >= bollingerBandElement.SMAN && bollingerBandElement.Close<bollingerBandElement.KL1 )count++;
        }
        return count;
      }

      private int GetCloseBetweenKL1AndK(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close >= bollingerBandElement.KL1 && bollingerBandElement.Close<bollingerBandElement.K )count++;
        }
        return count;
      }

      private int GetCloseGreaterK(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int count=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Close > bollingerBandElement.K )count++;
        }
        return count;
      }

      private bool BandBreakCheck(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        int lowLessLP1=0;
        int closeLessLP1=0;
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1);
        dates.RemoveAt(0);
        foreach(DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement =  bollingerBandElementsByDate[date];
          if(bollingerBandElement.Low <= bollingerBandElement.LP1)lowLessLP1++;
          if(bollingerBandElement.Close <= bollingerBandElement.LP1)closeLessLP1++;
        }
        return (lowLessLP1 >= days) && (closeLessLP1 >= days);
      }

      private bool CloseGreaterSMAN(DateTime analysisDate, BollingerBandElementsByDate bollingerBandElementsByDate, int days)
      {
        DateGenerator dateGenerator = new DateGenerator();
        List<DateTime> dates = dateGenerator.GenerateHistoricalDates(analysisDate, days + 1);
        dates.RemoveAt(0);
        foreach (DateTime date in dates)
        {
          BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date];
          if (bollingerBandElement.Close > bollingerBandElement.SMAN) return true;
        }
        return false;
      }


      /// <summary>
      /// This is used to trigger an adjustment to the stop price.
      /// </summary>
      /// <param name="analysisDate"></param>
      /// <param name="position"></param>
      /// <returns></returns>
      public bool IsLowerBandBreakIndicator(DateTime analysisDate, MGSHPosition position)
      {
        BollingerBandElementsByDate bollingerBandElementsByDate = GetBollingerBandElementsByDate(analysisDate);
        if (null == bollingerBandElementsByDate) return false;
        BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate];

        if (bollingerBandElement.High > bollingerBandElement.K)
        {
          Display(String.Format("+++ [1] Upper band break detected for {0} on {1} Purchase Date:{2} Purchase Price:{3} Price Close:{4} Price High:{5} Price High(hF):{6} KL1:{7}",
            HedgeShortSymbol,
            analysisDate.ToShortDateString(),
            position.PurchaseDate.ToShortDateString(),
            Utility.FormatCurrency(position.PurchasePrice),
            Utility.FormatCurrency(bollingerBandElement.Close),
            Utility.FormatCurrency(bollingerBandElement.High),
            Utility.FormatCurrency(bollingerBandElement.High),
            Utility.FormatCurrency(bollingerBandElement.K)
            ));
          return true;
        }
        return false;
      }

      private double GetHedgeStopLimitWithScalingAverageTrueRange(DateTime analysisDate,Price currentPrice,MGSHPosition position,double stopLimitNonScaled, int stopLimitScalingVolatilityDays, double hedgeATRMultiplier)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GetHedgeStopLimitWithScalingAverageTrueRange: Symbol:{0}",position.Symbol));      
        double volatility=double.NaN;

        volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,analysisDate,stopLimitScalingVolatilityDays, hedgeATRMultiplier);
        if(double.IsNaN(volatility))
        {
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to calculate AverageTrueRange for {0} on {1}.  Using non-scaled stop limit.",position.Symbol,analysisDate.ToShortDateString()));
          return stopLimitNonScaled;
        }
        double stopLimit = ((currentPrice.High + currentPrice.Low)/2.00) - volatility;  // We base the stop off of the midpoint of the high and the low
        return stopLimit;
      }

      private BollingerBandElementsByDate GetBollingerBandElementsByDate(DateTime analysisDate)
      {
        Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS);
        if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
        {
          Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}",
            HedgeShortSymbol,analysisDate.ToShortDateString()));
          return null;
        }
        BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices);  // K is upper band, KL1 below that.  L is lower band, LP1 above that
        BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate();
        if(!bollingerBandElementsByDate.ContainsKey(analysisDate))
        {
          Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}",
            HedgeShortSymbol,analysisDate.ToShortDateString()));
          return null;
        }
        return bollingerBandElementsByDate;
      }

      public void DisplayBandSlopes(DateTime analysisDate)
      {
        Display($"***** B A N D  S L O P E S {analysisDate.ToShortDateString()} *****");

        Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS);
        if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
        {
          Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}",
            HedgeShortSymbol,analysisDate.ToShortDateString()));
          return;
        }
        BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices);  // K is upper band, KL1 below that.  L is lower band, LP1 above that
        BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate();
        if(!bollingerBandElementsByDate.ContainsKey(analysisDate))
        {
          Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}",
            HedgeShortSymbol,analysisDate.ToShortDateString()));
          return;
        }

        BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate];

        SlopeManager slopeManager = new SlopeManager(bollingerBandElementsByDate, analysisDate){Verbose=true};

        Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        slopeManager.DisplaySlopes();
        Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        slopeManager.DisplaySlopesNumeric();
        Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
      }
    }
}
