using MarketData.DataAccess;
using MarketData.Generator.Indicators;
using MarketData.MarketDataModel;
using MarketData.Numerical;
using MarketData.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MarketData.Generator.CMMomentum
{
  public class CMCandidateGenerator
  {
    private CMCandidateGenerator()
    {
    }
    // *******************************************************************************************************************************************************************************
    // ************************************************************************* G E N E R A T E   C A N D I D A T E *****************************************************************
    // *******************************************************************************************************************************************************************************
    public static CMCandidate GenerateCandidate(String symbol, DateTime tradeDate, DateTime analysisDate, CMParams cmParams, List<String> symbolsHeld = null)
    {
      CMCandidate cmCandidate = new CMCandidate();

      // Check MarketCap
      Fundamental fundamental = FundamentalDA.GetFundamentalMaxDate(symbol, tradeDate);
      if (null == fundamental)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("No fundamental for {0}.", symbol);
        return cmCandidate;
      }
      if (!(fundamental.MarketCap >= cmParams.MarketCapLowerLimit))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("MarketCapLowerLimit constraint violation for {0}.", symbol);
        return cmCandidate;
      }
      // Check if the symbol is held in any open positions
      if (null != symbolsHeld && symbolsHeld.Any(x => x.Equals(symbol)))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("{0} is already held.", symbol);
        return cmCandidate;
      }
      // No trade symbols
      if (null!=cmParams.NoTradeSymbolsList&&cmParams.NoTradeSymbolsList.Any(x => x.Equals(symbol)))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("{0} is in the No-Trade list.", symbol);
        return cmCandidate;
      }
      // Equity check
      CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(symbol);
      if (!companyProfile.IsEquity)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("{0} is not an equity. {1}.", symbol, companyProfile.SecurityType);
        return cmCandidate;
      }
      // Moving Average check
      Prices pricesDMA = PricingDA.GetPrices(symbol, tradeDate, cmParams.MovingAverageConstraintDays + 15);
      if (null == pricesDMA || (cmParams.MovingAverageConstraintDays + 15) != pricesDMA.Count)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Insufficient pricing to generate {0} day moving average for {1}.  Required {2} days.",cmParams.MovingAverageConstraintDays,symbol,cmParams.MovingAverageConstraintDays + 15);
        return cmCandidate;
      }
      DMAPrices dmaPrices = MovingAverageGenerator.GenerateMovingAverage(pricesDMA, cmParams.MovingAverageConstraintDays);
      if (dmaPrices[0].CurrentPrice < dmaPrices[0].AVGPrice)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Current price for {0} is less than moving average.  {1}<{2} on {3}", symbol, dmaPrices[0].CurrentPrice, dmaPrices[0].AVGPrice, dmaPrices[0].Date.ToShortDateString());
        return cmCandidate;
      }
      Prices prices = PricingDA.GetPrices(symbol, tradeDate, cmParams.DayCount);   // The prices come back with the most recent date in the lowest index.  We want the earliest date in the lowest index
      if (null == prices || cmParams.DayCount != prices.Count)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Insufficient pricing data for {0}.  Required {1} days.", symbol, cmParams.DayCount);
        return cmCandidate;
      }
      // Filter penny stocks - don't trade anything less than $1.00
      if (prices[0].Close < 1.00 || prices[0].Open < 1.00)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Penny stock violation {0} Close price is {1}, Open price is ", symbol, Utility.FormatCurrency(prices[0].Close), Utility.FormatCurrency(prices[0].Open));
        return cmCandidate;
      }
      // Capture latest Volume - we'll do a min check later on
      cmCandidate.Volume = prices[0].Volume;
      // Liquidity check
      if (((from Price price in prices where price.Volume < 10000 select price).Count()) > 1)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Liquidity check violation for {0}.  Price volume must be above 10,000 ", symbol);
        return cmCandidate;
      }
      // OverExtended check
      if(cmParams.UseOverExtendedIndicator)
      {
        bool? result=OverExtendedIndicator.IsOverextended(symbol,tradeDate,cmParams.UseOverExtendedIndicatorDays,cmParams.UseOverExtendedIndicatorViolationThreshhold,cmParams.UseOverExtendedIndicatorMarginPercent);
        if(null!=result && true==result.Value)
        {
          cmCandidate.Violation=true;
          cmCandidate.Reason=String.Format("OverExtended violation for {0}.  The security is price overextended for TradeDate:{1}.",symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate));
          return cmCandidate;
        }
      }
      // Momentum Check
      float[] dailyReturns = prices.GetReturns();                          // First we build the returns (before we reverse the pricing direction)
      if (HasReturnViolation(dailyReturns,cmParams.DailyReturnLimit))      // Check the return stream.  If any daily return exceeds DailyReturnLimit then we discard.
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Daily return violation for {0}.  A daily return exceeded {1}%.", symbol, cmParams.DailyReturnLimit);
        return cmCandidate;
      }
      // check for outliers in the return stream
      if ((from float value in dailyReturns where Math.Abs(value) > cmParams.DailyReturnLimit select value).Count() > 0)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Outlier encountered in return stream for {0}.  Limit {1}", symbol, cmParams.DailyReturnLimit);
        return cmCandidate;
      }
      prices.Reverse();                                                    // Reverse the series here.
      double[] logPrices = null;
      logPrices = new double[prices.Count];
      for (int index = 0; index < prices.Count; index++)
      {
        Price price = prices[index];
        logPrices[index] = Math.Log(price.Close);
      }
      LeastSquaresResultWithR2 leastSquaresResult = LeastSquaresHelper.CalculateLeastSquaresWithR2(logPrices);
      cmCandidate = new CMCandidate();
      cmCandidate.Symbol = symbol;
      cmCandidate.AnalysisDate = analysisDate;
      cmCandidate.TradeDate = tradeDate;
      cmCandidate.DayCount = cmParams.DayCount;
      cmCandidate.Slope = leastSquaresResult.Slope;
      cmCandidate.AnnualizedReturn = Math.Pow(Math.Exp(cmCandidate.Slope), 252);        //cmCandidate.AnnualizedReturn=Math.Pow(1.00+cmCandidate.Slope,252);
      if (cmCandidate.Slope < 0) cmCandidate.AnnualizedReturn *= -1.00;                 // preserve the sign of the slope
      cmCandidate.Score = leastSquaresResult.RSquared * cmCandidate.AnnualizedReturn;   // The greater the score the higher the rank
      cmCandidate.RSquared = leastSquaresResult.RSquared;
      cmCandidate.Beta = BetaGenerator.Beta(symbol, tradeDate, cmParams.BetaMonths);
      cmCandidate.BetaMonths = cmParams.BetaMonths;
      if (double.IsNaN(cmCandidate.Beta))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Unable to calculate {0} month beta for {1} ", cmParams.BetaMonths, symbol);
        return cmCandidate;
      }
      if (cmCandidate.Beta <= 0.00)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Beta for {0} is less than or equal to zero {1}", symbol, cmCandidate.Beta);
        return cmCandidate;
      }
      if (cmParams.UseMaxBeta && cmCandidate.Beta > cmParams.MaxBeta)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Beta for {0} exceeds maximum allowed.  Candidate beta {1}, Max Beta:{2}", symbol, cmCandidate.Beta,cmParams.MaxBeta);
        return cmCandidate;
      }
      cmCandidate.SharpeRatio = SharpeRatioGenerator.GenerateSharpeRatio(cmCandidate.Symbol, tradeDate);
      if (double.IsNaN(cmCandidate.SharpeRatio))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Unable to calculate Sharpe Ratio for {0}", symbol);
        return cmCandidate;
      }
      return cmCandidate;
    }
// *******************************************************************************************************************************************************************************
// ************************************************************* G E N E R A T E   C A N D I D A T E  F O R   F A L L B A C K  ***************************************************
// *******************************************************************************************************************************************************************************
    public static CMCandidate GenerateCandidateForFallback(String symbol, DateTime tradeDate, DateTime analysisDate, CMParams cmParams, List<String> symbolsHeld = null)
    {
      CMCandidate cmCandidate = new CMCandidate();

      // Check MarketCap
      Fundamental fundamental = FundamentalDA.GetFundamentalMaxDate(symbol, tradeDate);
      if (null == fundamental) 
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("No fundamental for {0}.", symbol);
        return cmCandidate; 
      }
      if (!(fundamental.MarketCap >= cmParams.MarketCapLowerLimit)) 
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("MarketCapLowerLimit constraint violation for {0}.", symbol);
        return cmCandidate; 
      }

      // Check if the symbol is held in any open positions
      if (null != symbolsHeld && symbolsHeld.Any(x => x.Equals(symbol)))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("{0} is already held.", symbol);
        return cmCandidate;
      }
      Prices prices = PricingDA.GetPrices(symbol, tradeDate, cmParams.DayCount);   // The prices come back with the most recent date in the lowest index.  We want the earliest date in the lowest index
      if (null == prices || cmParams.DayCount != prices.Count)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Insufficient pricing data for {0}.  Required {1} days.", symbol, cmParams.DayCount);
        return cmCandidate;
      }
      cmCandidate.Volume = prices[0].Volume;
      // Momentum Check
      float[] dailyReturns = prices.GetReturns();                          // First we build the returns (before we reverse the pricing direction)

      // check  for outliers in the return stream
      if ((from float value in dailyReturns where Math.Abs(value) > cmParams.DailyReturnLimit select value).Count() > 0) 
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Outlier encountered in return stream for {0}.  Limit {1}", symbol,cmParams.DailyReturnLimit);
        return cmCandidate;
      }
      if (HasReturnViolation(dailyReturns, cmParams.DailyReturnLimit))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Daily return violation for {0}.  A daily return exceeded {1}%.", symbol, cmParams.DailyReturnLimit);
        return cmCandidate;
      }
      prices.Reverse();                                                    // Reverse the series here.
      double[] logPrices = null;
      logPrices = new double[prices.Count];
      for (int index = 0; index < prices.Count; index++)
      {
        Price price = prices[index];
        logPrices[index] = Math.Log(price.Close);
      }
      LeastSquaresResultWithR2 leastSquaresResult = LeastSquaresHelper.CalculateLeastSquaresWithR2(logPrices);
      cmCandidate = new CMCandidate();
      cmCandidate.Symbol = symbol;
      cmCandidate.AnalysisDate = analysisDate;
      cmCandidate.TradeDate = tradeDate;
      cmCandidate.DayCount = cmParams.DayCount;
      cmCandidate.Slope = leastSquaresResult.Slope;
      cmCandidate.AnnualizedReturn = Math.Pow(Math.Exp(cmCandidate.Slope), 252);        //cmCandidate.AnnualizedReturn=Math.Pow(1.00+cmCandidate.Slope,252);
      if (cmCandidate.Slope < 0) cmCandidate.AnnualizedReturn *= -1.00;                 // preserve the sign of the slope
      cmCandidate.Score = leastSquaresResult.RSquared * cmCandidate.AnnualizedReturn;   // The greater the score the higher the rank
      cmCandidate.RSquared = leastSquaresResult.RSquared;
      cmCandidate.Beta = BetaGenerator.Beta(symbol,cmParams.BetaMonths);         // generate a 90 day Beta
      cmCandidate.BetaMonths = cmParams.BetaMonths;
      if (double.IsNaN(cmCandidate.Beta))
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Unable to calculate {0} month beta for {1} ", cmParams.BetaMonths, symbol);
        return cmCandidate;
      }
      if (cmCandidate.Beta <= 0.00)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Beta for {0} is less than or equal to zero {1}", symbol, cmCandidate.Beta);
        return cmCandidate;
      }
      if (cmParams.UseMaxBeta && cmCandidate.Beta > cmParams.MaxBeta)
      {
        cmCandidate.Violation = true;
        cmCandidate.Reason = String.Format("Beta for {0} exceeds maximum allowed.  Candidate beta {1}, Max Beta:{2}", symbol, cmCandidate.Beta, cmParams.MaxBeta);
        return cmCandidate;
      }
      cmCandidate.SharpeRatio = SharpeRatioGenerator.GenerateSharpeRatio(cmCandidate.Symbol, tradeDate);
      if (double.IsNaN(cmCandidate.SharpeRatio)) cmCandidate.SharpeRatio = 0.00;   // don't care about this for the fallback candidate
      return cmCandidate;
    }
// Given the list of candidates select the one candidate with the most favorable slope as an absolute fallback
// *******************************************************************************************************************************************************************************
// ************************************************************ G E N E R A T E   C A N D I D A T E  O F  L A S T  R E S O R T ***************************************************
// *******************************************************************************************************************************************************************************
    public static CMCandidate GetFallbackCandidateOfLastResort(List<String> candidates, DateTime tradeDate, DateTime analysisDate, CMParams cmParams)
    {
      CMCandidates cmCandidates = new CMCandidates();
      foreach (String candidate in candidates)
      {
        Prices prices = PricingDA.GetPrices(candidate, tradeDate, cmParams.DayCount);   // The prices come back with the most recent date in the lowest index.  We want the earliest date in the lowest index
        if (null == prices || cmParams.DayCount != prices.Count) continue;
        prices.Reverse();                                                    // Reverse the series here.
        double[] logPrices = null;
        logPrices = new double[prices.Count];
        for (int index = 0; index < prices.Count; index++)
        {
          Price price = prices[index];
          logPrices[index] = Math.Log(price.Close);
        }
        LeastSquaresResultWithR2 leastSquaresResult = LeastSquaresHelper.CalculateLeastSquaresWithR2(logPrices);
        CMCandidate cmCandidate = new CMCandidate();
        cmCandidate = new CMCandidate();
        cmCandidate.Symbol = candidate;
        cmCandidate.AnalysisDate = analysisDate;
        cmCandidate.TradeDate = tradeDate;
        cmCandidate.DayCount = cmParams.DayCount;
        cmCandidate.Slope = leastSquaresResult.Slope;
        cmCandidate.AnnualizedReturn = Math.Pow(Math.Exp(cmCandidate.Slope), 252);        //cmCandidate.AnnualizedReturn=Math.Pow(1.00+cmCandidate.Slope,252);
        if (cmCandidate.Slope < 0) cmCandidate.AnnualizedReturn *= -1.00;                 // preserve the sign of the slope
        cmCandidate.Score = leastSquaresResult.RSquared * cmCandidate.AnnualizedReturn;   // The greater the score the higher the rank
        cmCandidate.RSquared = leastSquaresResult.RSquared;
        cmCandidate.Beta = BetaGenerator.Beta(candidate,tradeDate,cmParams.BetaMonths);
        cmCandidate.BetaMonths = cmParams.BetaMonths;
        if (cmCandidate.Beta <= 0) cmCandidate.Beta = .01;
        cmCandidate.SharpeRatio = SharpeRatioGenerator.GenerateSharpeRatio(cmCandidate.Symbol, tradeDate);
        if (double.IsNaN(cmCandidate.SharpeRatio)) cmCandidate.SharpeRatio = 0.00;    // don't care about this for the fallback candidate
        cmCandidates.Add(cmCandidate);
      }
      return (from CMCandidate cmCandidate in cmCandidates select cmCandidate).OrderByDescending(x => x.Slope).ToList().Take(1).FirstOrDefault();
    }
    private static bool HasReturnViolation(float[] dailyReturns,double dailyReturnLimit)
    {
      foreach (float dailyReturn in dailyReturns) if (dailyReturn > dailyReturnLimit) return true;
      return false;
    }
  }
}
