using MarketData.DataAccess; using MarketData.Generator.Indicators; using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; using MarketData.Numerical; using MarketData.Utils; 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 symbolsHeld = null) { CMCandidate cmCandidate = new CMCandidate(); // No trade symbols if (null!=cmParams.NoTradeSymbolsList&&cmParams.NoTradeSymbolsList.Any(x => x.Equals(symbol))) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("Candidate in the No-Trade list."); cmCandidate.ReasonCategory = String.Format("Candidate in the No-Trade list."); return cmCandidate; } // Check if the symbol is held in any open positions if (null != symbolsHeld && symbolsHeld.Any(x => x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase))) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("Already held."); cmCandidate.ReasonCategory = String.Format("Already held."); return cmCandidate; } Fundamental fundamental = FundamentalDA.GetFundamentalMaxDate(symbol, tradeDate); if (null == fundamental) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("No fundamental."); cmCandidate.ReasonCategory = String.Format("No fundamental."); return cmCandidate; } if (!(fundamental.MarketCap >= cmParams.MarketCapLowerLimit)) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("MarketCapLowerLimit constraint violation."); cmCandidate.ReasonCategory = String.Format("MarketCapLowerLimit constraint violation."); return cmCandidate; } // Equity check CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(symbol); if (!companyProfile.IsEquity) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("Candidate is not an equity."); cmCandidate.ReasonCategory = String.Format("Candidate is not an equity."); 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 moving average."); cmCandidate.ReasonCategory = String.Format("Insufficient pricing to generate moving average."); 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 is less than moving average."); cmCandidate.ReasonCategory = String.Format("Current price is less than moving average."); 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."); cmCandidate.ReasonCategory = String.Format("Insufficient pricing data."); 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)); cmCandidate.ReasonCategory = String.Format("Penny stock violation."); 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); cmCandidate.ReasonCategory = String.Format("Liquidity check violation."); 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)); cmCandidate.ReasonCategory=String.Format("OverExtended violation. The security is price overextended."); return cmCandidate; } } // Momentum Check if (HasReturnViolation(prices,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); cmCandidate.ReasonCategory = String.Format("Daily return violation. A daily return exceeded expectations."); 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); cmCandidate.ReasonCategory = String.Format("Beta cannot be calculated."); 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); cmCandidate.ReasonCategory = String.Format("Beta is less than or equal to zero."); 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); cmCandidate.ReasonCategory = String.Format("Beta exceeds maximum allowed."); 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); cmCandidate.ReasonCategory = String.Format("Unable to calculate Sharpe Ratio."); 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 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); cmCandidate.ReasonCategory = String.Format("No fundamental."); return cmCandidate; } if (!(fundamental.MarketCap >= cmParams.MarketCapLowerLimit)) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("MarketCapLowerLimit constraint violation for {0}.", symbol); cmCandidate.ReasonCategory = String.Format("MarketCapLowerLimit constraint violation."); 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); cmCandidate.ReasonCategory = String.Format("Candidate already held."); 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); cmCandidate.ReasonCategory = String.Format("Insufficient pricing data."); return cmCandidate; } cmCandidate.Volume = prices[0].Volume; // Momentum Check if (HasReturnViolation(prices, cmParams.DailyReturnLimit)) { cmCandidate.Violation = true; cmCandidate.Reason = String.Format("Daily return violation for {0}. A daily return exceeded {1}%.", symbol, cmParams.DailyReturnLimit); cmCandidate.ReasonCategory = String.Format("Daily return violation. A daily return exceeded the limit."); 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); cmCandidate.ReasonCategory = String.Format("Beta could not be calculated."); 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); cmCandidate.ReasonCategory = String.Format("Beta is less than or equal to zero."); 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); cmCandidate.ReasonCategory = String.Format("Beta exceeds maximum allowed."); 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 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; //} private static bool HasReturnViolation(Prices prices, double dailyReturnLimit) { float[] dailyReturns = prices.GetReturns(); int index=0; String symbol = prices.Take(1).First().Symbol; for(;index dailyReturnLimit) { return true; } } return false; } } }