Fix Backtest runs VS production run. Align dates.

This commit is contained in:
2025-02-07 15:59:27 -05:00
parent d882b10ec0
commit b7f72ef25a
2 changed files with 62 additions and 36 deletions

View File

@@ -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<HoldingPeriod;slotIndex++)
{
if(!ActivePositions.ContainsKey(slotIndex)||0==ActivePositions[slotIndex].Count())continue;
@@ -378,13 +397,10 @@ namespace MarketData.Generator.MGSHMomentum
CashBalance+=slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
}
DisplayStatistics(sessionParams);
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
AllPositions=new MGSHPositions((from MGSHPosition position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
AllPositions.Display();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P G A I N E R S *************");
AllPositions.DisplayTopFive();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P L O S E R S *************");
AllPositions.DisplayBottomFive();
DisplayBalance();
backTestResult.Success=true;
backTestResult.CashBalance=CashBalance;
@@ -397,8 +413,9 @@ namespace MarketData.Generator.MGSHMomentum
public void BuySlotPositions(int slotIndex)
{
MGSHPositions positions = null;
positions=BuyPositions(TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld(TradeDate));
positions=BuyPositions(slotIndex, TradeDate, AnalysisDate, CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count), SymbolsHeld(TradeDate));
MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
MDTrace.WriteLine(LogLevel.DEBUG, $"SLOT:{slotIndex}");
if(CashBalance-positions.Exposure<0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
@@ -429,7 +446,7 @@ namespace MarketData.Generator.MGSHMomentum
int positionsToFill = MaxPositions-slotPositions.Count;
double cashAllocation = (CashBalance / ((HoldingPeriod * MaxPositions) - ActivePositions.GetCount()))*positionsToFill; // split the cash between the total positions we can own less the number of positions we have
MGSHPositions positions = null;
positions=BuyPositions(TradeDate,AnalysisDate,cashAllocation,SymbolsHeld(TradeDate), positionsToFill);
positions=BuyPositions(slotIndex, TradeDate,AnalysisDate,cashAllocation,SymbolsHeld(TradeDate), positionsToFill);
if(CashBalance-positions.Exposure<=0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
@@ -455,36 +472,47 @@ namespace MarketData.Generator.MGSHMomentum
// ********************************************************************************************************************************************************
/// <summary>
/// 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
/// </summary>
/// <param name="startDate">{ORIGINAL START DATE}</param>
/// <param name="endDate">{TODAY}</param>
/// <param name="paramPathSessionFileName">The session file name</param>
/// <returns></returns>
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<String> symbolsHeld, double maxPositions=double.NaN)
private MGSHPositions BuyPositions(int slotIndex, DateTime tradeDate, DateTime analysisDate, double cash, List<String> 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("**************************************************************"));
}

View File

@@ -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);