From b7f72ef25ad7ee519ef34d072835aabb9f4cdbe8 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 7 Feb 2025 15:59:27 -0500 Subject: [PATCH] Fix Backtest runs VS production run. Align dates. --- .../Generator/MGSHMomentum/MGSHBacktest.cs | 95 ++++++++++++------- .../MGSHMomentum/MGSHConfiguration.cs | 3 +- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/MarketDataLib/Generator/MGSHMomentum/MGSHBacktest.cs b/MarketDataLib/Generator/MGSHMomentum/MGSHBacktest.cs index b2b7a81..24b845c 100644 --- a/MarketDataLib/Generator/MGSHMomentum/MGSHBacktest.cs +++ b/MarketDataLib/Generator/MGSHMomentum/MGSHBacktest.cs @@ -328,17 +328,26 @@ namespace MarketData.Generator.MGSHMomentum StartDate=StartDate-timeSpan; TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate); } - if(null!=PathSessionFileName)sessionParams=RestoreSession(); + if(null!=PathSessionFileName) + { + sessionParams=RestoreSession(); + DateTime currentMonthEndDate = dateGenerator.GetCurrentMonthEnd(TradeDate); + if(TradeDate!=currentMonthEndDate) + { + TradeDate=currentMonthEndDate; + AnalysisDate=TradeDate; + } + } if(null!=sessionParams) { - MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}",paramPathSessionFileName,sessionParams.LastUpdated)); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}, Current Trade Date: {2}",paramPathSessionFileName,sessionParams.LastUpdated.ToShortDateString(),TradeDate.ToShortDateString())); } Configuration.DisplayConfiguration(); DisplayBalance(); while(true) { - if(TradeDate>AnalysisDate)break; + if(TradeDate > AnalysisDate)break; int slotIndex=(int)(((double)Cycle)%((double)(HoldingPeriod))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TRADE DATE {0} , ANALYSIS DATE {1}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate),Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate))); if(!ActivePositions.ContainsKey(slotIndex)) @@ -349,24 +358,34 @@ namespace MarketData.Generator.MGSHMomentum { SellAndBuySlotPositions(slotIndex); } -// Check if we are configured to use any of the daily strategies + ManageHedgedPositions(TradeDate); + DateTime nextBusinessDay = dateGenerator.FindNextBusinessDay(TradeDate); + DateTime nextMonthEndDate = dateGenerator.GetNextMonthEnd(TradeDate); + +// if the TradeDate and AnalysisDate are equal then set the trade date to the next business day and exit the cycle + if(TradeDate.Equals(AnalysisDate)) + { + TradeDate = nextBusinessDay; + break; + } + +// This will enter the daily cycle which we need for backtesting if(Configuration.UseStopLimits || Configuration.UseHedging) { - DateTime nextMonthEndDate = dateGenerator.GetNextMonthEnd(TradeDate); - if(nextMonthEndDate > TradeDate && nextMonthEndDate<=AnalysisDate) + if(nextMonthEndDate >= AnalysisDate) { - if(null!=PathSessionFileName)SaveSession(); - UpdateDaily(dateGenerator.FindNextBusinessDay(TradeDate),nextMonthEndDate,paramPathSessionFileName); + nextMonthEndDate=dateGenerator.FindNextBusinessDay(AnalysisDate); } + TradeDate = nextBusinessDay; + SaveSession(); + UpdateDaily(nextBusinessDay,nextMonthEndDate,AnalysisDate,paramPathSessionFileName); // This will save the session file + sessionParams = RestoreSession(); } Cycle++; - TradeDate = dateGenerator.GetNextMonthEnd(TradeDate); - if (TradeDate>AnalysisDate)break; } // WHILE TRUE + MDTrace.WriteLine(LogLevel.DEBUG,$"RUN COMPLETE FOR ANALYSIS DATE {AnalysisDate.ToShortDateString()}."); if(null!=PathSessionFileName)SaveSession(); - if(null==sessionParams)sessionParams=RestoreSession(); - DisplayStatistics(sessionParams); for(int slotIndex=0;slotIndex /// Entry point for daily runs. Make sure for daily runs that startDate=endDate=Trading Date after market close + /// The system does not process endDate. (i.e.) startDate < endDate /// /// {ORIGINAL START DATE} /// {TODAY} /// The session file name /// - public MGSHBacktestResult UpdateDaily(DateTime startDate,DateTime endDate,String pathSessionFileName) + public MGSHBacktestResult UpdateDaily(DateTime startDate, DateTime endDate, DateTime analysisDate, String pathSessionFileName) { DateGenerator dateGenerator = new DateGenerator(); PathSessionFileName = pathSessionFileName; RestoreSession(); - MDTrace.WriteLine(LogLevel.DEBUG,String.Format("************** U P D A T E D A I L Y {0} < {1}",startDate.ToShortDateString(),endDate.ToShortDateString())); + AnalysisDate=analysisDate; + + if(startDate != TradeDate) + { + MDTrace.WriteLine(LogLevel.DEBUG,$"Unexpectd StartDate. Start Date:{startDate.ToShortDateString()} Trade Date:{TradeDate.ToShortDateString()}"); + return new MGSHBacktestResult(false, CashBalance); + } + + MDTrace.WriteLine(LogLevel.DEBUG,String.Format("************** U P D A T E D A I L Y **************")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Trade Date:{TradeDate.ToShortDateString()} Analysis Date:{AnalysisDate.ToShortDateString()}")); while(startDate < endDate) { - bool changed = false; + TradeDate=startDate; if(Configuration.UseStopLimits) { - changed = UpdateStopLimitsForActivePositions(startDate); + UpdateStopLimitsForActivePositions(startDate); } if(Configuration.UseHedging) { - changed = ManageHedgedPositions(startDate) ? true : changed; + ManageHedgedPositions(startDate); } -// if(changed)DisplayBalance(); + DisplayBalance(); startDate = dateGenerator.FindNextBusinessDay(startDate); + TradeDate = startDate; } - if(null!=PathSessionFileName)SaveSession(); + SaveSession(); return new MGSHBacktestResult(true, CashBalance); } // ******************************************************************************************************************************************************* @@ -567,7 +595,7 @@ namespace MarketData.Generator.MGSHMomentum MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Initial stop limit for {0} on {1} with PurchsePrice:{2} is {3}, Shares:{4} Risk:{5}%", shortPosition.Symbol, - shortPosition.PurchaseDate, + shortPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(shortPosition.PurchasePrice,2), Utility.FormatCurrency(shortPosition.InitialStopLimit,2), Utility.FormatNumber(shortPosition.Shares,2), @@ -662,7 +690,7 @@ namespace MarketData.Generator.MGSHMomentum position.LastStopAdjustment = Utility.Epoch; MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Initial stop limit for {0} on {1} with PurchsePrice:{2} is {3}, Shares:{4} Risk:{5}%", position.Symbol, - position.PurchaseDate, + position.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(position.PurchasePrice,2), Utility.FormatCurrency(position.InitialStopLimit,2), Utility.FormatNumber(position.Shares,2), @@ -731,7 +759,6 @@ namespace MarketData.Generator.MGSHMomentum ActivePositions.Remove(position); } } - MDTrace.WriteLine(LogLevel.DEBUG,String.Format("*************************************************************************************************",analysisDate.ToShortDateString())); if(closedPositions.Count>0)return true; return false; } @@ -946,12 +973,12 @@ namespace MarketData.Generator.MGSHMomentum // ************************************************************************************************************************************************************ // *********************************************************** B U Y S L O T P O S I T I O N S ************************************************************ // ************************************************************************************************************************************************************ - private MGSHPositions BuyPositions(DateTime tradeDate, DateTime analysisDate, double cash, List symbolsHeld, double maxPositions=double.NaN) + private MGSHPositions BuyPositions(int slotIndex, DateTime tradeDate, DateTime analysisDate, double cash, List symbolsHeld, double maxPositions=double.NaN) { MGSHPositions positions = new MGSHPositions(); if(double.IsNaN(maxPositions))maxPositions=MaxPositions; if(0==maxPositions)return positions; - MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"BUY POSITIONS: TRADE DATE:{TradeDate.ToShortDateString()} CASH:{Utility.FormatCurrency(cash)} POSITIONS TO FILL:{maxPositions}")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"BUY POSITIONS: SLOT:{slotIndex} TRADE DATE:{TradeDate.ToShortDateString()} CASH:{Utility.FormatCurrency(cash)} POSITIONS TO FILL:{maxPositions}")); int positionCount = 0; if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash); MGSHMomentumCandidates momentumCandidates = MGSHMomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration); @@ -1059,10 +1086,10 @@ namespace MarketData.Generator.MGSHMomentum MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Trades:{modelStatistics.TotalTrades}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Winning Trades:{modelStatistics.WinningTrades}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Losing Trades:{modelStatistics.LosingTrades}")); - MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Winning Trade Percent Gain:{modelStatistics.AverageWinningTradePercentGain}%")); - MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Losing Trade Percent Loss:{modelStatistics.AverageLosingTradePercentLoss}%")); - MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Winning Trades Percent:{modelStatistics.WinningTradesPercent}%")); - MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Losing Trades Percent:{modelStatistics.LosingTradesPercent}%")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Winning Trade Percent Gain:{Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)}%")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Losing Trade Percent Loss:{Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)}%")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Winning Trades Percent:{Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)}%")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Losing Trades Percent:{Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)}%")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Expectancy:{Utility.FormatNumber(modelStatistics.Expectancy,2)}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**************************************************************")); } diff --git a/MarketDataLib/Generator/MGSHMomentum/MGSHConfiguration.cs b/MarketDataLib/Generator/MGSHMomentum/MGSHConfiguration.cs index 50a4e16..d876f29 100644 --- a/MarketDataLib/Generator/MGSHMomentum/MGSHConfiguration.cs +++ b/MarketDataLib/Generator/MGSHMomentum/MGSHConfiguration.cs @@ -140,8 +140,7 @@ namespace MarketData.Generator.MGSHMomentum // Fallback candidate settings UseFallbackCandidate=true; // True is the default FallbackCandidate="SHV"; // "SHV" ICE U.S. Treasury Short Bond Index, "AGG" Barclays U.S. Aggregate Bond Index - AGG can have a slighty better return but is more volatile - FallbackCandidateBestOf="SHV,AGG,ACWX"; // if set then the fallback candidate is selected as the best 252 day return in this comma seperated list (i.e.) "SHV,ACWX,AGG" - // SHV,NEAR,BIL,GSY,AGG,ACWX,GSY,SCHF,IXUS,DBEF,IEFA + FallbackCandidateBestOf="SHV,NEAR,BIL,GSY,AGG,ACWX,GSY,SCHF,IXUS,DBEF,IEFA,TLT"; // if set then the fallback candidate is selected as the best 252 day return in this comma seperated list (i.e.) "SHV,ACWX,AGG,SHV,NEAR,BIL,GSY,AGG,ACWX,GSY,SCHF,IXUS,DBEF,IEFA // Set the QualityIndicator type to the ScoreIndicator by default. QualityIndicatorType=MGSHQualityIndicator.ToString(MGSHQualityIndicator.QualityType.ScoreIndicator);