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, FundamentalV2 fundamental, CompanyProfile companyProfile, List historicalDates, TimeSeriesCollection epsTimeSeries, TimeSeriesCollection profitMarginTimeSeries, List symbolsHeld=null) { CMTCandidate cmtCandidate=new CMTCandidate(); try { // Check MarketCap 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 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 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())); 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())); return cmtCandidate; } } // setup for trend analysis Prices prices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,PRICING_DAYS); if(null==prices||prices.Count0) { 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 x.Close); double latestClose=currentPrice.Close; double percentReturnOver52WeekLow=((latestClose-weekLow52)/weekLow52)*100.00; if(percentReturnOver52WeekLow x.Close); double percentReturnProximityTo52WeekHigh=double.NaN; if(latestClosecmtParams.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; if(null==rsiCollection||0==rsiCollection.Count||rsi dma200List=new List(); historicalDates=historicalDates.Take(cmtParams.DMA200Horizon).ToList(); if(historicalDates.Count0.",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) { 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) { 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); // because most recent date is in the lowest valued index bucket. 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;indexcmtParams.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; } } }