using MarketData.Cache;
using MarketData.DataAccess;
using MarketData.Generator.MovingAverage;
using MarketData.MarketDataModel;
using MarketData.Numerical;
using MarketData.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MarketData.Generator.CMTrend
{
  public class CMTCandidateGenerator
  {
    private static readonly int PRICING_DAYS=252;
    public CMTCandidateGenerator()
    {
    }
    // *******************************************************************************************************************************************************************************
    // ******************************************************************* G E N E R A T E   C A N D I D A T E   - M A R C  M I N E R V I N I ****************************************
    // *******************************************************************************************************************************************************************************
    public static CMTCandidate GenerateCandidate(String symbol,DateTime tradeDate,CMTParams cmtParams,List<String> symbolsHeld=null)
    {
      CMTCandidate cmtCandidate=new CMTCandidate();

      try
      {
// Check MarketCap
        Fundamental fundamental=FundamentalDA.GetFundamentalMaxDate(symbol,tradeDate);
        if(null==fundamental)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("No fundamental for {0}.",symbol);
          return cmtCandidate;
        }
        if(!(fundamental.MarketCap>=cmtParams.MarketCapLowerLimit))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("MarketCapLowerLimit constraint violation for {0}.",symbol);
          return cmtCandidate;
        }
// Check if the symbol is held in any open positions
        if(null!=symbolsHeld&&symbolsHeld.Any(x => x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("{0} is already held.",symbol);
          return cmtCandidate;
        }
// No trade symbols
        if(null!=cmtParams.NoTradeSymbolsList&&cmtParams.NoTradeSymbolsList.Any(x => x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("{0} is in the No-Trade list.",symbol);
          return cmtCandidate;
        }
// Equity check
        CompanyProfile companyProfile=CompanyProfileDA.GetCompanyProfile(symbol);
				if(null==companyProfile)
				{
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("No company profile found for {0}.",symbol);
          return cmtCandidate;
				}
        if(!companyProfile.IsEquity)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("{0} is not an equity. {1}.",symbol,companyProfile.SecurityType);
          return cmtCandidate;
        }
// Sector Check 
        if(cmtParams.UseTradeOnlySectors)
				{
				  List<String> validSectors=Utility.ToList(cmtParams.UseTradeOnlySectorsSectors);
					if(null==companyProfile.Sector)
					{
						cmtCandidate.Violation=true;
						cmtCandidate.Reason=String.Format("Invalid sector for {0}.  Found {1} expected one of {2}.",symbol,null==companyProfile.Sector?"(Null)":companyProfile.Sector,Utility.ListToString(validSectors.ToList<String>()));
						return cmtCandidate;
					}
					if(!validSectors.Any(x => x.Equals(companyProfile.Sector)))
					{
						cmtCandidate.Violation=true;
						cmtCandidate.Reason=String.Format("Invalid sector for {0}.  Found {1} expected one of {2}.",symbol,null==companyProfile.Sector?"(Null)":companyProfile.Sector,Utility.ListToString(validSectors.ToList<String>()));
						return cmtCandidate;
					}
				}
// setup for trend analysis
        Prices prices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,PRICING_DAYS);
        if(null==prices||prices.Count<PRICING_DAYS)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Insufficient pricing history, {0} days required.",PRICING_DAYS);
          return cmtCandidate;
        }
// Current Price Check
        Price currentPrice=prices[0];
        if(currentPrice.Date.Date!=tradeDate.Date)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="pricing date!=trade date.";
          return cmtCandidate;
        }
// Liquidity check - if any day has volume < MinVolume then we reject it
        int belowThreshholdVolumeCount=(from Price xPrice in prices where xPrice.Volume<cmtParams.MinVolume select xPrice).Count();
        if(cmtParams.LiquidityCheck&&belowThreshholdVolumeCount>0)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Insufficient liquidity.");
          return cmtCandidate;
        }
// Setup for the moving averages checks
        Prices prices50=new Prices(prices.Take(50).ToList());
        Prices prices15=new Prices(prices.Take(15).ToList());
        Prices prices150=new Prices(prices.Take(150).ToList());
        Prices prices200=new Prices(prices.Take(200).ToList());

        DMAPrices dma50Prices=MovingAverageGenerator.GenerateMovingAverage(prices50,prices50.Count);
        DMAPrices dma150Prices=MovingAverageGenerator.GenerateMovingAverage(prices150,prices150.Count);
        DMAPrices dma200Prices=MovingAverageGenerator.GenerateMovingAverage(prices200,prices200.Count);

        double dma50Close=dma50Prices[0].AVGPrice;
        double dma150Close=dma150Prices[0].AVGPrice;
        double dma200Close=dma200Prices[0].AVGPrice;
        double volatility=prices15.Volatility();

// Trend #1 check.  Check that current price is greater than 150 day moving average and current price is greater than 200 day moving average
        if(currentPrice.Close<dma150Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#1 Violation : currentPrice.Close<=dma150Close.";
          return cmtCandidate;
        }
        if(currentPrice.Close<dma200Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason="Trend#1 Violation : currentPrice.Close<=dma200Close.";
          return cmtCandidate;
        }
// Trend #2 check.  Check that 150 day moving average is greater than the 200 day moving average
        if(dma150Close<dma200Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#2 Violation : dma150Close<=dma200Close.";
          return cmtCandidate;
        }

// Trend #4 check : The 50 day moving average must be greater than the 150 day moving average
        if(dma50Close<=dma150Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#4 Violation : (dma50Close<=dma150Close)&&(dma50Close<=dma200Close).";
          return cmtCandidate;
        }

// Trend #4a check : The 50 day moving average must be greater than the 200 day moving average
        if(dma50Close<=dma200Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#4a Violation : dma50Close<=dma200Close.";
          return cmtCandidate;
        }

// Trend #5 check : The current price must be greater than the 50 day moving average
        if(currentPrice.Close<=dma50Close)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#5 Violation : currentPrice.Close<=dma50Close.";
          return cmtCandidate;
        }

// Trend #6 check. Evaluate the MinPercentReturnOver52WeekLow
        double weekLow52=prices.Min(x => x.Close);
        double latestClose=currentPrice.Close;
        double percentReturnOver52WeekLow=((latestClose-weekLow52)/weekLow52)*100.00;
        if(percentReturnOver52WeekLow<cmtParams.MinPercentReturnOver52WeekLow)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Trend#6 Violation : percentReturnOver52WeekLow<={0}.",cmtParams.MinPercentReturnOver52WeekLow);
          return cmtCandidate;
        }

// Trend #7 check.  Evaluate the MinPercentReturnProximityTo52WeekHigh
        double weekHigh52=prices.Max(x => x.Close);
        double percentReturnProximityTo52WeekHigh=double.NaN;
        if(latestClose<weekHigh52) percentReturnProximityTo52WeekHigh=((weekHigh52-latestClose)/latestClose)*100.00;
        else percentReturnProximityTo52WeekHigh=((latestClose-weekHigh52)/weekHigh52)*100.00;
        if(latestClose<weekHigh52&&percentReturnProximityTo52WeekHigh>cmtParams.MinPercentReturnProximityTo52WeekHigh)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Trend#7 Violation :PercentReturnProximityTo52WeekHigh<{0}.",cmtParams.MinPercentReturnProximityTo52WeekHigh);
          return cmtCandidate;
        }

// Trend #8 check.  Evaluate the RSI
// generate a 14 day standard RSI with 30 days of pricing data
        Prices rsiPrices=GBPriceCache.GetInstance().GetPrices(symbol,currentPrice.Date,30);
        RSICollection rsiCollection=RSIGenerator.GenerateRSI(rsiPrices);
        double rsi=rsiCollection[rsiCollection.Count-1].RSI;

        // RSICollection rsiCollection=RSIGenerator.GenerateRSI(symbol,currentPrice.Date,30);  
        // double rsi=rsiCollection[rsiCollection.Count-1].RSI;                      
        if(null==rsiCollection||0==rsiCollection.Count||rsi<cmtParams.MinRSI)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Trend#8 Violation : rsiCollection[rsiCollection.Count-1].RSI<{0}.",cmtParams.MinRSI);
          return cmtCandidate;
        }

// Trend #3 check : check required days of increasing 200 day moving averages
        DateGenerator dateGenerator=new DateGenerator();
        List<double> dma200List=new List<double>();
        DateTime historicalDate=dateGenerator.GenerateHistoricalDate(currentPrice.Date,cmtParams.DMA200Horizon+10);
        List<DateTime> historicalDates=PricingDA.GetPricingDatesBetween(historicalDate,currentPrice.Date);
        historicalDates=historicalDates.Take(cmtParams.DMA200Horizon).ToList();
        if(historicalDates.Count<cmtParams.DMA200Horizon)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Trend#3a Violation : Insufficient data to calulate DMA(200).  Requires {0} days of DMA(200)",cmtParams.DMA200Horizon);
          return cmtCandidate;
        }
        foreach(DateTime date in historicalDates)
        {
          Prices historicalPrices=GBPriceCache.GetInstance().GetPrices(symbol,date,MovingAverageGenerator.DayCount200);
          if(null==historicalPrices||historicalPrices.Count<MovingAverageGenerator.DayCount200) continue;
          dma200Prices=MovingAverageGenerator.GenerateMovingAverage(historicalPrices,MovingAverageGenerator.DayCount200);
          dma200List.Insert(0,dma200Prices[0].AVGPrice);  // The lowest index should wind up with most historical price.  This way we calculate the proper slope
        }
        double[] averages=dma200List.ToArray();
        double slope=Numerics.Slope(ref averages);
        if(slope<=0)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason=String.Format("Trend#3 Violation : Slope of {0} days of 200DMA >0.",cmtParams.DMA200Horizon);
          return cmtCandidate;
        }
// Trend check ensure that prices are trending higher
        if(cmtParams.UsePriceSlopeIndicator)
				{
					int dayCount=cmtParams.UsePriceSlopeIndicatorDays;
					Prices pricesTrend=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,dayCount);
					double[] pricesLow=Numerics.ToDouble(pricesTrend.GetPricesLow());
					LeastSquaresResult lsr=LeastSquaresHelper.CalculateLeastSquares(pricesLow);
					if(lsr.Slope<=0.00)
					{
						cmtCandidate.Violation=true;
						cmtCandidate.Reason=String.Format("Price trend violation {0}. The {1} pricing slope is {2}",symbol,dayCount,Utility.FormatNumber(lsr.Slope,6));
						return cmtCandidate;
					}
				}
// Filter penny stocks - don't trade anything less than $1.00
        if(currentPrice.Close<1.00||currentPrice.Open<1.00)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Penny stock violation {0} Close price is {1}, Open price is ",symbol,Utility.FormatCurrency(currentPrice.Close),Utility.FormatCurrency(currentPrice.Open));
          return cmtCandidate;
        }
// Capture latest Volume - we'll do a min check later on
        cmtCandidate.Volume=currentPrice.Volume;

// Daily Return Check
        float[] dailyReturns=prices.GetReturns();                          // First we build the returns (before we reverse the pricing direction)
        if(HasReturnViolation(dailyReturns,cmtParams.DailyReturnLimit))      // Check the return stream.  If any daily return exceeds DailyReturnLimit then we discard.
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Daily return violation for {0}.  A daily return exceeded {1}%.",symbol,cmtParams.DailyReturnLimit);
          return cmtCandidate;
        }

// check for outliers in the return stream
        if((from float value in dailyReturns where Math.Abs(value)>cmtParams.DailyReturnLimit select value).Count()>0)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Outlier encountered in return stream for {0}.  Limit {1}",symbol,cmtParams.DailyReturnLimit);
          return cmtCandidate;
        }

// EBITDA screen
        bool UseEBITDAScreen=true;
        if(UseEBITDAScreen&&(double.IsNaN(fundamental.EBITDA)||fundamental.EBITDA<=0))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#14 Violation : EBITDA";
          return cmtCandidate;
        }

        bool UsePEScreen=true;
        if(UsePEScreen&&(double.IsNaN(fundamental.PE))||fundamental.PE<=0.00)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#14 Violation : UsePEScreen";
          return cmtCandidate;
        }

// Setup for next tests
        double profitMarginSlope=double.NaN;
        DateTime minDate=DateTime.MinValue;
        DateTime maxDate=DateTime.MinValue;
        float[] values;
// Revenue per share screen
        bool UseRevenuePerShareScreen=true;
        if(UseRevenuePerShareScreen&&(double.IsNaN(fundamental.RevenuePerShare)||fundamental.RevenuePerShare<0.00))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Symbol=symbol;
          cmtCandidate.Reason="Trend#14 Violation : Revenue Per Share";
          return cmtCandidate;
        }
// Trend#9 - My check Increasing EPS
        double epsSlope=double.NaN;
        if(companyProfile.IsEquity&&cmtParams.EPSCheck)
        {
          TimeSeriesCollection epsTimeSeries=FundamentalDA.GetEPS(symbol,currentPrice.Date);
          if(null==epsTimeSeries||epsTimeSeries.Count<3)
          {
            cmtCandidate.Violation=true;
            cmtCandidate.Symbol=symbol;
            cmtCandidate.Reason="Trend#9 Violation : No EPS time series to perform check.";
            return cmtCandidate;
          }
          epsTimeSeries=new TimeSeriesCollection(epsTimeSeries.Take(3).ToList());
          minDate=epsTimeSeries.Min(x => x.AsOf);
          maxDate=epsTimeSeries.Max(x => x.AsOf);
          values=epsTimeSeries.ToFloat();
          values=Numerics.Reverse(ref values);
          epsSlope=Numerics.Slope(values);
          if(epsSlope<=0)
          {
            cmtCandidate.Violation=true;
            cmtCandidate.Symbol=symbol;
            cmtCandidate.Reason="Trend#9 Violation : epsSlope<=0.";
            return cmtCandidate;
          }
        }

// Trend#10 - My check - Increasing profit margin
        if(companyProfile.IsEquity&&cmtParams.ProfitMarginCheck)
        {
          TimeSeriesCollection profitMarginTimeSeries=IncomeStatementDA.GetProfitMarginMaxAsOf(symbol,currentPrice.Date,IncomeStatement.PeriodType.Quarterly);
          if(null==profitMarginTimeSeries||profitMarginTimeSeries.Count<3)
          {
            cmtCandidate.Violation=true;
            cmtCandidate.Symbol=symbol;
            cmtCandidate.Reason="Trend#10 Violation : No Profit Margin series to perform check.";
            return cmtCandidate;
          }
          profitMarginTimeSeries=new TimeSeriesCollection(profitMarginTimeSeries.Take(3).ToList());
          minDate=profitMarginTimeSeries.Min(x => x.AsOf);
          maxDate=profitMarginTimeSeries.Max(x => x.AsOf);
          values=profitMarginTimeSeries.ToFloat();
          values=Numerics.Reverse(ref values);
          profitMarginSlope=Numerics.Slope(values);
          if(profitMarginSlope<=0)
          {
            cmtCandidate.Violation=true;
            cmtCandidate.Symbol=symbol;
            cmtCandidate.Reason="Trend#10 Violation : profitMarginSlope<=0.";
            return cmtCandidate;
          }
        }

// Calculate the Score
        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);
        cmtCandidate=new CMTCandidate();
        cmtCandidate.EPSSlope=epsSlope;
        cmtCandidate.PriceSlope=leastSquaresResult.Slope;
        cmtCandidate.ProfitMarginSlope=profitMarginSlope;
        cmtCandidate.Symbol=symbol;
        cmtCandidate.AnalysisDate=tradeDate;
        cmtCandidate.Slope=leastSquaresResult.Slope;
        cmtCandidate.Volatility=volatility;
        cmtCandidate.AnnualizedReturn=Math.Pow(Math.Exp(cmtCandidate.Slope),252);        //cmCandidate.AnnualizedReturn=Math.Pow(1.00+cmCandidate.Slope,252);
        if(cmtCandidate.Slope<0) cmtCandidate.AnnualizedReturn*=-1.00;                 // preserve the sign of the slope
        cmtCandidate.Score=leastSquaresResult.RSquared*cmtCandidate.AnnualizedReturn;   // The greater the score the higher the rank
        cmtCandidate.RSquared=leastSquaresResult.RSquared;
        cmtCandidate.Beta=BetaGenerator.Beta(symbol,tradeDate,cmtParams.BetaMonths);
        cmtCandidate.BetaMonths=cmtParams.BetaMonths;
        if(double.IsNaN(cmtCandidate.Beta))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Unable to calculate {0} month beta for {1} ",cmtParams.BetaMonths,symbol);
          return cmtCandidate;
        }
        if(cmtCandidate.Beta<=0.00)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Beta for {0} is less than or equal to zero {1}",symbol,cmtCandidate.Beta);
          return cmtCandidate;
        }
        if(cmtParams.UseMaxBeta&&cmtCandidate.Beta>cmtParams.MaxBeta)
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Beta for {0} exceeds maximum allowed.  Candidate beta {1}, Max Beta:{2}",symbol,cmtCandidate.Beta,cmtParams.MaxBeta);
          return cmtCandidate;
        }
        cmtCandidate.SharpeRatio=SharpeRatioGenerator.GenerateSharpeRatio(cmtCandidate.Symbol,tradeDate);
        if(double.IsNaN(cmtCandidate.SharpeRatio))
        {
          cmtCandidate.Violation=true;
          cmtCandidate.Reason=String.Format("Unable to calculate Sharpe Ratio for {0}",symbol);
          return cmtCandidate;
        }
        return cmtCandidate;
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
        cmtCandidate.Violation=true;
        cmtCandidate.Symbol=symbol;
        cmtCandidate.Reason=exception.ToString();
        return cmtCandidate;
      }
    }
    private static bool HasReturnViolation(float[] dailyReturns,double dailyReturnLimit)
    {
      foreach(float dailyReturn in dailyReturns) if(Math.Abs(dailyReturn)>dailyReturnLimit) return true;
      return false;
    }
  }
}

