diff --git a/MarketDataLib/Generator/CMTrend/CMTParams.cs b/MarketDataLib/Generator/CMTrend/CMTParams.cs index 1326b63..aefe2cc 100644 --- a/MarketDataLib/Generator/CMTrend/CMTParams.cs +++ b/MarketDataLib/Generator/CMTrend/CMTParams.cs @@ -1,9 +1,6 @@ using MarketData.Utils; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MarketData.Generator.CMTrend { @@ -16,14 +13,14 @@ namespace MarketData.Generator.CMTrend UsePriceSlopeIndicatorDays=252; // The number of pricing days to use for the slope. BetaMonths=6; // The number of months to use for Beta AnalysisDate=DateTime.Now.Date; // The analysis date of the run - MarketCapLowerLimit=500000000; // MarketCap lower limit 1,000,000,000 + MarketCapLowerLimit=500000000; // MarketCap lower limit 50,0000,000 TradeDate=DateTime.Now; // The current trade date SidewaysDetection=false; // Detect stock that are going nowhere. If we've held the stock for SidewaysAfterDays AND we've never adjusted the stop AND we can break even THEN we sell. SidewaysAfterDays=30; // Sideways detection days. - MaxDailyPositions=3; // This is the maximum number of positions to pick up per analysis date. The default is 3 - MaxOpenPositions=6; // This is the maximum number of open positions. The default is 6. -1=No Max + MaxDailyPositions=3; // This is the maximum number of positions to pick up per analysis date. The default is 3 + MaxOpenPositions=3; // This is the maximum number of open positions. I have tested this with 3 and had good results. NoTradeSymbols="CLCT,PRSC,CMD,STAY,GBTC,YOKU,PNY,RFMD,ASAZY,USMO,VNR,STB,XIV,SYNT"; // ASAZY came up as candidate during 3/30 run but not available on Robinhood - OnlyTradeSymbols=""; // This should be a comma separated list of symbols which would serve as the universe of symbols to trade. If null or empty then we trade everything in security master + OnlyTradeSymbols=""; // This should be a comma separated list of symbols which would serve as the universe of symbols to trade. If null or empty then we trade everything in security master InitialCash=10000; // The initial cash TotalRiskPercentDecimal=.05; // Total Risk of Initial Cash. The default is .02. I've been testing with .05 PositionRiskPercentDecimal=.12; // Risk per position - This will determine where the stop is placed. The default is .12 @@ -52,6 +49,7 @@ namespace MarketData.Generator.CMTrend UseStopLimitScaling=true; // When set to true this will scale (tighten) the stop limit as time progresses based upon an anticipated holding period of StopLimitScalingDays. StopLimitScalingType="AverageTrueRange"; // Acceptable types are "AverageTrueRange". StopLimitScalingVolatilityDays=30; // StopLimitScalingVolatilityDays=5 The StopLimitScaling takes volatility into account. This parameter specifies how many days of pricing to use for the volatility calculation. + EvaluateStopOnUpTrend=false; // If set to true then the stop limit is only evaluated if prices are trending up. SellOnDMABreak=true; // If true then we look lok for breaks of all DMAs listed under DMABreak DMABreakValues="200"; // The defaut value is 200. This can be a comma separated list. For instance 50,100,200 DMABreakForceBreak=false; // If this flag is set to true then we will sell a security on DMA break even if it means taking a loss on the position. @@ -142,6 +140,7 @@ namespace MarketData.Generator.CMTrend public string UseProfitMaximizationExpression{get;set;} public bool UseTradeOnlySectors{get;set;} public String UseTradeOnlySectorsSectors{get;set;} + public bool EvaluateStopOnUpTrend { get; set; } public void DisplayHeader() { @@ -213,6 +212,7 @@ namespace MarketData.Generator.CMTrend nvpCollection.Add(new NVP("UseProfitMaximizationExpression",UseProfitMaximizationExpression.ToString())); nvpCollection.Add(new NVP("UseTradeOnlySectors",UseTradeOnlySectors.ToString())); nvpCollection.Add(new NVP("UseTradeOnlySectorsSectors",UseTradeOnlySectorsSectors.ToString())); + nvpCollection.Add(new NVP("EvaluateStopOnUpTrend",EvaluateStopOnUpTrend.ToString())); return nvpCollection; } public static CMTParams FromNVPCollection(NVPCollection nvpCollection) @@ -298,6 +298,10 @@ namespace MarketData.Generator.CMTrend cmtParams.UseTradeOnlySectors=nvpDictionary["UseTradeOnlySectors"].Get(); cmtParams.UseTradeOnlySectorsSectors=nvpDictionary["UseTradeOnlySectorsSectors"].Get(); } + if(nvpDictionary.ContainsKey("EvaluateStopOnUpTrend")) + { + cmtParams.EvaluateStopOnUpTrend=nvpDictionary["EvaluateStopOnUpTrend"].Get(); + } return cmtParams; } public void DisplayConfiguration() @@ -365,6 +369,7 @@ namespace MarketData.Generator.CMTrend MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseProfitMaximizationExpression,{0}",UseProfitMaximizationExpression.ToString())); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseTradeOnlySectors,{0}",UseTradeOnlySectors.ToString())); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseTradeOnlySectorsSectors,{0}",UseTradeOnlySectorsSectors.ToString())); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format("EvaluateStopOnUpTrend,{0}",EvaluateStopOnUpTrend.ToString())); } } } diff --git a/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs b/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs index e75196d..e74322e 100644 --- a/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs +++ b/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs @@ -65,6 +65,7 @@ namespace MarketData.Generator.CMTrend } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End())); } + public static ModelPerformanceSeries GetModelPerformance(String paramPathSessionFileName) { try @@ -78,6 +79,7 @@ namespace MarketData.Generator.CMTrend return null; } } + // Calcualtes the expectation for the model ( Percent of Winning Trades * Average Gain)/(Percent Losing Trades * Average Loss) // The expectation should be above zero public static ModelStatistics GetModelStatistics(CMTSessionParams sessionParams) @@ -111,6 +113,7 @@ namespace MarketData.Generator.CMTrend return modelStatistics; } } + public static ModelPerformanceSeries GetModelPerformance(CMTSessionParams sessionParams) { Profiler profiler=new Profiler(); @@ -203,6 +206,7 @@ namespace MarketData.Generator.CMTrend MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, total took {0}(ms)",profiler.End())); } } + // ****************************************************************************************************************************************************** //************************************************************** D I S P L A Y S E S S I O N ***************************************************** // ****************************************************************************************************************************************************** @@ -350,6 +354,7 @@ namespace MarketData.Generator.CMTrend MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Average RProfit {0}R per trade (expectancy)",Utility.FormatNumber((totalRWinners+totalRLosers)/totalTrades,2,false))); GBPriceCache.GetInstance().Dispose(); } + // ****************************************************************************************************************************************************** // ****************************************************************** E N T R Y T E S T ***************************************************************** // ****************************************************************************************************************************************************** @@ -482,6 +487,7 @@ namespace MarketData.Generator.CMTrend GBPriceCache.GetInstance().Dispose(); } } + // ****************************************************************************************************************************************************** // ****************************************************************** C L O S E ********************************************************************** // ****************************************************************************************************************************************************** @@ -534,6 +540,7 @@ namespace MarketData.Generator.CMTrend SaveSession(); return true; } + // ****************************************************************************************************************************************************** // *************************************************************************** E D I T ****************************************************************** // ****************************************************************************************************************************************************** @@ -581,6 +588,7 @@ namespace MarketData.Generator.CMTrend MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} has been modified and saved.",symbol,purchaseDate.ToShortDateString())); return true; } + // ****************************************************************************************************************************************************** // ****************************************************************** D A I L Y ***************************************************************** // ****************************************************************************************************************************************************** @@ -687,6 +695,7 @@ namespace MarketData.Generator.CMTrend GBPriceCache.GetInstance().Dispose(); } } + // ****************************************************************************************************************************************************** // ****************************************************************** B A C K T E S T ***************************************************************** // ****************************************************************************************************************************************************** @@ -803,6 +812,7 @@ namespace MarketData.Generator.CMTrend GBPriceCache.GetInstance().Dispose(); } } + public void RunTrendTemplate(DateTime? analysisDate=null) { try @@ -844,6 +854,7 @@ namespace MarketData.Generator.CMTrend GBPriceCache.GetInstance().Dispose(); } } + // *********************************************************************************************************************************************************************** // *********************************************************************** M A N A G E O P E N P O S I T I O N S ***************************************************** // *********************************************************************************************************************************************************************** @@ -922,6 +933,7 @@ namespace MarketData.Generator.CMTrend } } } + // ********************************************************************************************************************************************************** // ***************************************************** M O V I N G A V E R A G E B R E A K C H E C K ************************************************* // ********************************************************************************************************************************************************** @@ -963,6 +975,7 @@ namespace MarketData.Generator.CMTrend } return false; } + // ********************************************************************************************************************************************************** // ***************************************************** S T O P L I M I T C O L L E C T I O N M A I N T E N A N C E *********************************** // ********************************************************************************************************************************************************** @@ -970,6 +983,7 @@ namespace MarketData.Generator.CMTrend { StopLimits.Add(stopLimit); } + // ********************************************************************************************************************************************************** // *********************************************** P R I C I N G E X C E P T I O N C O L L E C T I O N M A I N T E N A N C E *************************** // ********************************************************************************************************************************************************** @@ -980,18 +994,21 @@ namespace MarketData.Generator.CMTrend else pricingException.ExceptionCount++; return pricingException.ExceptionCount; } + private void RemovePricingException(String symbol) { CMTPricingException pricingException=PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault(); if(null==pricingException) return; PricingExceptions.Remove(pricingException); } + private bool HasPricingException(String symbol) { CMTPricingException pricingException=PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault(); if(null==pricingException) return false; return true; } + // *************************************************************************************************************************************************** // **************************************************************** S E L L P O S I T I O N S ***************************************************** // *************************************************************************************************************************************************** @@ -1003,6 +1020,7 @@ namespace MarketData.Generator.CMTrend SellPosition(position,sellDate,comment); } } + private void SellPosition(Position position,DateTime sellDate,String comment) { position.SellDate=sellDate; @@ -1025,6 +1043,7 @@ namespace MarketData.Generator.CMTrend } else position.CurrentPrice=price.Close; } + // *************************************************************************************************************************************************** // **************************************************************** B U Y C A N D I D A T E S ***************************************************** // *************************************************************************************************************************************************** @@ -1266,10 +1285,10 @@ namespace MarketData.Generator.CMTrend return null; } } + // *************************************************************************************************************************************************** // ***************************************************************** P O S I T I O N S I Z I N G ************************************************** // *************************************************************************************************************************************************** - private Positions PerformPositionSizing(Positions positions,double availableCash,DateTime tradeDate) { return PerformPositionSizingTotalRisk(positions, availableCash, tradeDate); @@ -1304,6 +1323,7 @@ namespace MarketData.Generator.CMTrend } return acceptedPositions; } + // *************************************************************************************************************************************************** // ************************************************************** S T O P L I M I T S ************************************************************* // *************************************************************************************************************************************************** @@ -1311,11 +1331,12 @@ namespace MarketData.Generator.CMTrend { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("EvaluateStopPrice: {0} on {1}",position.Symbol,tradeDate.ToShortDateString())); DateGenerator dateGenerator=new DateGenerator(); - Prices prices=GBPriceCache.GetInstance().GetPrices(position.Symbol,tradeDate,Parameters.PriceTrendDays); // only adjust stops if we are trending up - PriceTrendIndicatorResult priceTrendIndicatorResult=PriceTrendIndicator.IsUptrend(prices,Parameters.PriceTrendDays); - if(!priceTrendIndicatorResult.IsUpTrend) + + Prices prices = GBPriceCache.GetInstance().GetPrices(position.Symbol, tradeDate, Parameters.PriceTrendDays); // only adjust stops if we are trending up + PriceTrendIndicatorResult priceTrendIndicatorResult = PriceTrendIndicator.IsUptrend(prices, Parameters.PriceTrendDays); + if(Parameters.EvaluateStopOnUpTrend && !priceTrendIndicatorResult.IsUpTrend) { - MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} does not have upward price trend (higher highs and higher lows for {1} consecutive days), will not adjust stop price",position.Symbol,Parameters.PriceTrendDays)); + MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0} does not have upward price trend (higher highs and higher lows for {1} consecutive days), will not adjust stop price", position.Symbol, Parameters.PriceTrendDays)); return; } double trailingStop=position.InitialStopLimit+Math.Floor((currentPrice.Low-position.PurchasePrice)/position.R)*position.R; // where R = Risk Per Share in $ @@ -1414,6 +1435,7 @@ namespace MarketData.Generator.CMTrend } } } + private double GetStopLimitWithScalingAverageTrueRange(DateTime tradeDate,Price currentPrice,Position position,double stopLimitNonScaled) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GetStopLimitWithScalingAverageTrueRange: Symbol:{0} RMultiple={1}",position.Symbol,position.RMultiple)); @@ -1441,6 +1463,7 @@ namespace MarketData.Generator.CMTrend double stopLimit=currentPrice.Low-volatility; // We base the stop off of the low in order to give a bit more breathing room in the stop in the event that we have a wide spread between the close and the low. Backtested currentPrice.Close vs currentPrice.Low and basing off the low yields better results. return stopLimit; } + // *************************************************************************************************************************************************** // ************************************************************* M A N A G E S E T U P S *********************************************************** // *************************************************************************************************************************************************** @@ -1461,6 +1484,7 @@ namespace MarketData.Generator.CMTrend AddCandidate(mmCandidate); } } + private void ExpireCandidates(DateTime tradeDate) { List candidatesToRemove=new List(); @@ -1472,18 +1496,21 @@ namespace MarketData.Generator.CMTrend } foreach(CMTCandidate candidate in candidatesToRemove) Candidates.Remove(candidate); } + private void AddCandidate(CMTCandidate candidate) { if(null==Candidates) Candidates=new CMTCandidates(); if(Candidates.Any(x => x.Symbol.Equals(candidate.Symbol))) return; Candidates.Add(candidate); } + private void RemoveCandidate(CMTCandidate candidate) { if(null==Candidates) Candidates=new CMTCandidates(); if(!Candidates.Any(x => x.Symbol.Equals(candidate.Symbol))) return; Candidates.Remove(Candidates.Where(x => x.Symbol.Equals(candidate.Symbol)).FirstOrDefault()); } + // *************************************************************************************************************************************************** // ************************************************************************ M A R K E T C O N D I T I O N S *************************************** // *************************************************************************************************************************************************** @@ -1521,6 +1548,7 @@ namespace MarketData.Generator.CMTrend } return result; } + // Determine volatility based on ^VIX bollinger L band break on the close within 60 days prior private static bool IsTradeableVolatilityEnvironment(DateTime tradeDate,CMTParams cmtParams) { @@ -1540,6 +1568,7 @@ namespace MarketData.Generator.CMTrend } return result; } + // *************************************************************************************************************************************************** // ************************************************************************ G E T P R I C E ****************************************************** // *************************************************************************************************************************************************** @@ -1551,6 +1580,7 @@ namespace MarketData.Generator.CMTrend if(null==price) price=GBPriceCache.GetInstance().GetPrice(symbol,dateGenerator.FindPrevBusinessDay(priceDate)); return price; } + // ********************************************************************************************************************************************************************* // ********************************************************************************************************************************************************************* // ********************************************************************************************************************************************************************* @@ -1587,6 +1617,7 @@ namespace MarketData.Generator.CMTrend MDTrace.WriteLine(LogLevel.DEBUG,"***************************************************************************************************************************"); } } + public double GetRealtimeGainLoss(DateTime tradeDate) { int count=ActivePositions.Count; @@ -1600,6 +1631,7 @@ namespace MarketData.Generator.CMTrend } return gainLoss; } + private void DisplayBalance() { MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT"); @@ -1618,6 +1650,7 @@ namespace MarketData.Generator.CMTrend Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure()+CashBalance)))); } } + private void DisplayBalanceFromPositions() { MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT"); @@ -1628,6 +1661,7 @@ namespace MarketData.Generator.CMTrend Utility.AddQuotes(Utility.FormatCurrency(CashBalance)), Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance)))); } + private void DisplayBalanceFromAllPositions() { MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT"); @@ -1691,6 +1725,7 @@ namespace MarketData.Generator.CMTrend return null; } } + public void SaveSession() { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Saving session to '{0}'",PathSessionFileName)); @@ -1709,6 +1744,7 @@ namespace MarketData.Generator.CMTrend sessionParams.NonTradeableCash=NonTradeableCash; CMTSessionManager.SaveSession(sessionParams,PathSessionFileName); } + public bool BackupSession() { String[] parts=PathSessionFileName.Split('.');