759 lines
42 KiB
C#
759 lines
42 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using MarketData.MarketDataModel;
|
|
using MarketData.DataAccess;
|
|
using MarketData.Utils;
|
|
using System.Linq;
|
|
using MarketData.Cache;
|
|
using MarketData.Generator.Model;
|
|
|
|
namespace MarketData.Generator.Momentum
|
|
{
|
|
public class MomentumBacktest
|
|
{
|
|
private double NonTradeableCash{get;set;}
|
|
private double CashBalance{get;set;}
|
|
private MGConfiguration Configuration{get;set;}
|
|
private int HoldingPeriod{get{return Configuration.HoldingPeriod;}}
|
|
private int MaxPositions{get{return Configuration.MaxPositions;}}
|
|
private ActivePositions ActivePositions{get;set;}
|
|
private Positions AllPositions{get;set;}
|
|
private int Cycle{get;set;}
|
|
private DateTime TradeDate{get;set;}
|
|
private DateTime StartDate{get;set;}
|
|
private DateTime AnalysisDate{get;set;}
|
|
private String PathSessionFileName{get;set;}
|
|
// ******************************************************************************************************************************************************
|
|
//************************************************************** D I S P L A Y G A I N L O S S *****************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public static void DisplayGainLoss(String paramPathSessionFileName)
|
|
{
|
|
Profiler profiler=new Profiler();
|
|
ModelPerformanceSeries performanceSeries=GetModelPerformance(paramPathSessionFileName);
|
|
if(null==performanceSeries)return;
|
|
MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDOD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
|
|
foreach(ModelPerformanceItem modelPerformanceItem in performanceSeries)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
|
|
modelPerformanceItem.Date.ToShortDateString(),
|
|
Utility.FormatCurrency(modelPerformanceItem.Exposure),
|
|
Utility.FormatCurrency(modelPerformanceItem.MarketValue),
|
|
Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
|
|
Utility.FormatCurrency(modelPerformanceItem.GainLoss),
|
|
Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
|
|
Utility.FormatNumber(modelPerformanceItem.R,4),
|
|
Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
|
|
Utility.FormatNumber(modelPerformanceItem.CumProd,4),
|
|
Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4),
|
|
modelPerformanceItem.ClosedPositions));
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End()));
|
|
}
|
|
|
|
public static ModelPerformanceSeries GetModelPerformance(String paramPathSessionFileName)
|
|
{
|
|
try
|
|
{
|
|
MGSessionParams sessionParams=MGSessionManager.RestoreSession(paramPathSessionFileName);
|
|
return GetModelPerformance(sessionParams);
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
return null;
|
|
}
|
|
}
|
|
// Calculates 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(MGSessionParams sessionParams)
|
|
{
|
|
ModelStatistics modelStatistics=new ModelStatistics();
|
|
try
|
|
{
|
|
if(null==sessionParams||null==sessionParams.AllPositions||0==sessionParams.AllPositions.Count) return modelStatistics;
|
|
double totalTrades=sessionParams.AllPositions.Count;
|
|
double winningTrades=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Count();
|
|
double losingTrades=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Count();
|
|
double averageWinningTrade=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Average(x => x.GainLossPcnt)*100.00;
|
|
double averageLosingTrade=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Average(x => x.GainLossPcnt)*100.00;
|
|
double percentWinningTrades=(winningTrades/(double)sessionParams.AllPositions.Count)*100.00;
|
|
double percentLosingTrades=(losingTrades/(double)sessionParams.AllPositions.Count)*100.00;
|
|
double expectation=(percentWinningTrades*averageWinningTrade)/(percentLosingTrades*Math.Abs(averageLosingTrade));
|
|
|
|
modelStatistics.TotalTrades=(long)totalTrades;
|
|
modelStatistics.WinningTrades=(long)winningTrades;
|
|
modelStatistics.LosingTrades=(long)losingTrades;
|
|
modelStatistics.AverageWinningTradePercentGain=averageWinningTrade;
|
|
modelStatistics.AverageLosingTradePercentLoss=averageLosingTrade;
|
|
modelStatistics.WinningTradesPercent=percentWinningTrades;
|
|
modelStatistics.LosingTradesPercent=percentLosingTrades;
|
|
modelStatistics.Expectancy=expectation;
|
|
return modelStatistics;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
return modelStatistics;
|
|
}
|
|
}
|
|
|
|
public static ModelPerformanceSeries GetModelPerformance(MGSessionParams sessionParams)
|
|
{
|
|
Profiler profiler=new Profiler();
|
|
ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
try
|
|
{
|
|
if(null==sessionParams)return null;
|
|
MarketData.Generator.Momentum.Positions combinedPositions=sessionParams.GetCombinedPositions();
|
|
|
|
DateTime minDate=combinedPositions.Min(x => x.PurchaseDate);
|
|
DateTime maxDate=PricingDA.GetLatestDate();
|
|
double prevGainLoss=double.NaN;
|
|
LocalPriceCache.GetInstance().RemoveDate(maxDate);
|
|
List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(minDate,maxDate);
|
|
foreach(DateTime currentDate in historicalDates)
|
|
{
|
|
MarketData.Generator.Momentum.Positions openPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
|
|
MarketData.Generator.Momentum.Positions closedPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (!Utility.IsEpoch(x.SellDate)&&x.SellDate.Equals(currentDate))).ToList());
|
|
if(0==openPositions.Count&&0==closedPositions.Count) continue;
|
|
double gainLoss=0.00;
|
|
double gainLossClosedPositions=0.00;
|
|
double exposure=0.00;
|
|
double marketValue=0.00;
|
|
ModelPerformanceItem performanceItem=new ModelPerformanceItem();
|
|
foreach(MarketData.Generator.Momentum.Position openPosition in openPositions)
|
|
{
|
|
exposure+=openPosition.Shares*openPosition.PurchasePrice;
|
|
if(!LocalPriceCache.GetInstance().ContainsPrice(openPosition.Symbol,currentDate))
|
|
{
|
|
Prices prices=PricingDA.GetPricesForward(openPosition.Symbol,currentDate,PricingDA.ForwardLookingDays);
|
|
LocalPriceCache.GetInstance().Add(prices);
|
|
}
|
|
Price price=LocalPriceCache.GetInstance().GetPrice(openPosition.Symbol,currentDate);
|
|
if(null==price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No price for {0} on {1}",openPosition.Symbol,currentDate.ToShortDateString()));
|
|
}
|
|
else
|
|
{
|
|
gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
|
|
marketValue+=(price.Close*openPosition.Shares);
|
|
}
|
|
}
|
|
foreach(MarketData.Generator.Momentum.Position closedPosition in closedPositions)
|
|
{
|
|
double gainLossPosition=(closedPosition.CurrentPrice*closedPosition.Shares)-(closedPosition.PurchasePrice*closedPosition.Shares);
|
|
gainLossClosedPositions+=gainLossPosition;
|
|
}
|
|
performanceItem.Date=currentDate;
|
|
performanceItem.Exposure=exposure;
|
|
performanceItem.MarketValue=marketValue;
|
|
performanceItem.GainLossDOD=double.IsNaN(prevGainLoss)?gainLoss:(gainLoss-prevGainLoss)+gainLossClosedPositions;
|
|
performanceItem.GainLoss=gainLoss+gainLossClosedPositions;
|
|
performanceItem.ClosedPositions=closedPositions.Count>0?true:false;
|
|
performanceSeries.Add(performanceItem);
|
|
prevGainLoss=gainLoss;
|
|
}
|
|
performanceSeries.CalculatePerformance();
|
|
return performanceSeries;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
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 *****************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public void DisplaySession(String paramPathSessionFileName)
|
|
{
|
|
if(null==paramPathSessionFileName)return;
|
|
PathSessionFileName=paramPathSessionFileName;
|
|
MGSessionParams sessionParams=null;
|
|
if(null==(sessionParams=RestoreSession()))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",paramPathSessionFileName));
|
|
return;
|
|
}
|
|
Console.WriteLine(String.Format("SessionFile:{0} Last Updated:{1}",paramPathSessionFileName,sessionParams.LastUpdated));
|
|
Configuration.DisplayConfiguration();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
|
|
AllPositions=new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
|
|
AllPositions.Display();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"************** A C T I V E P O S I T I O N S *************");
|
|
ActivePositions.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 S E R S *************");
|
|
AllPositions.DisplayBottomFive();
|
|
// DisplayBalanceFromPositions();
|
|
DisplayLatestModelPerformance(paramPathSessionFileName);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StartDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("AnalysisDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Next Slot:{0}",Cycle));
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
//******************************************************* L I Q U I D A T E A L L P O S I T I O N S ***********************************************
|
|
// ******************************************************************************************************************************************************
|
|
public void MGLiquididate(String pathSessionFile,DateTime? tradeDate)
|
|
{
|
|
if (null == pathSessionFile) return;
|
|
MGSessionParams sessionParams=null;
|
|
|
|
PathSessionFileName = pathSessionFile;
|
|
if (null == (sessionParams = RestoreSession()))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
|
|
return;
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG, "************** L I Q U I D A T E P O S I T I O N S *************");
|
|
if (null == ActivePositions || 0 == ActivePositions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("No active positions in file {0}", pathSessionFile));
|
|
return;
|
|
}
|
|
if(null==tradeDate)tradeDate = PricingDA.GetLatestDate(ActivePositions.GetSymbols());
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Trade date:{0}", Utility.DateTimeToStringMMHDDHYYYY(tradeDate.Value)));
|
|
for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
|
|
{
|
|
Positions slotPositions = ActivePositions[slotIndex];
|
|
SellPositions(slotPositions, tradeDate.Value);
|
|
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
|
|
slotPositions.Display();
|
|
AllPositions.Add(slotPositions);
|
|
DisplaySales(slotPositions, TradeDate);
|
|
CashBalance += slotPositions.MarketValue;
|
|
ActivePositions[slotIndex].Clear();
|
|
}
|
|
GBPriceCache.GetInstance().Dispose();
|
|
SaveSession();
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** C L O S E **********************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double sellPrice,String pathSessionFile)
|
|
{
|
|
if(null==pathSessionFile) return false;
|
|
MGSessionParams sessionParams=null;
|
|
|
|
PathSessionFileName=pathSessionFile;
|
|
if(null==(sessionParams=RestoreSession()))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
|
|
return false;
|
|
}
|
|
if(!BackupSession()) return false;
|
|
Positions activePositions = ActivePositions.GetPositions();
|
|
Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
|
|
if(null==position) // if it is not in the active positions then the position is already closed and we are modifying either the sell date or the sell price
|
|
{
|
|
position=AllPositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
|
|
if(null==position)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
|
|
return false;
|
|
}
|
|
position.SellDate = sellDate;
|
|
CashBalance -= position.MarketValue;
|
|
position.CurrentPrice = sellPrice;
|
|
CashBalance += position.MarketValue;
|
|
SaveSession();
|
|
return true;
|
|
}
|
|
position.SellDate = sellDate;
|
|
position.CurrentPrice = sellPrice;
|
|
CashBalance += position.MarketValue;
|
|
ActivePositions.Remove(position);
|
|
AllPositions.Add(position);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} is now closed.",symbol,purchaseDate.ToShortDateString()));
|
|
SaveSession();
|
|
return true;
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// *************************************************************************** E D I T ******************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public bool EditPosition(String symbol,DateTime purchaseDate,double purchasePrice,String pathSessionFile)
|
|
{
|
|
if(null==pathSessionFile) return false;
|
|
PathSessionFileName=pathSessionFile;
|
|
MGSessionParams sessionParams=null;
|
|
if(null==(sessionParams=RestoreSession()))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
|
|
return false;
|
|
}
|
|
if(!BackupSession()) return false;
|
|
|
|
Positions activePositions = ActivePositions.GetPositions();
|
|
|
|
Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
|
|
if(null==position)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
|
|
return false;
|
|
}
|
|
if(!position.PurchaseDate.Equals(purchaseDate)) position.PurchaseDate=purchaseDate;
|
|
if(!position.PurchasePrice.Equals(purchasePrice))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting Cash for Position for symbol '{0}' purchased on {1}. Original Price: {2} New Price: {3} Change in Cash: {4}",
|
|
symbol,purchaseDate.ToShortDateString(),
|
|
Utility.FormatCurrency(position.PurchasePrice),
|
|
Utility.FormatCurrency(purchasePrice),
|
|
Utility.FormatCurrency((position.PurchasePrice-purchasePrice)*position.Shares)));
|
|
CashBalance+=(position.PurchasePrice-purchasePrice)*position.Shares;
|
|
position.PurchasePrice=purchasePrice;
|
|
}
|
|
SaveSession();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} has been modified and saved.",symbol,purchaseDate.ToShortDateString()));
|
|
return true;
|
|
}
|
|
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** B A C K T E S T *****************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
// Ideally, startDate should be November,February,May,August
|
|
// paramStartDate is startDate {ORIGINAL START DATE}
|
|
// paramAnalysisDate is endDate {TODAY}
|
|
public BacktestResult PerformBacktest(DateTime paramStartDate,DateTime paramAnalysisDate,String paramPathSessionFileName,MGConfiguration configuration)
|
|
{
|
|
BacktestResult backTestResult=new BacktestResult();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
Configuration=configuration;
|
|
CashBalance=Configuration.InitialCash;
|
|
ActivePositions=new ActivePositions();
|
|
AllPositions=new Positions();
|
|
StartDate=paramStartDate;
|
|
TradeDate=paramStartDate;
|
|
AnalysisDate=paramAnalysisDate;
|
|
PathSessionFileName=paramPathSessionFileName;
|
|
MGSessionParams sessionParams=null;
|
|
|
|
Cycle=0;
|
|
if(AnalysisDate.Date>Today().Date)return backTestResult;
|
|
if(Utility.IsEpoch(AnalysisDate))AnalysisDate=dateGenerator.GetPrevBusinessDay(Today()); // Ensure AnalysisDate is not a weekend or holiday
|
|
TradeDate =dateGenerator.GetCurrentMonthEnd(StartDate);
|
|
if(TradeDate>AnalysisDate)
|
|
{
|
|
int startMonth=StartDate.Month;
|
|
TimeSpan timeSpan=new TimeSpan();
|
|
if((new int[]{12,3,6,9}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(30,0,0,0);
|
|
else if((new int[]{1,4,7,10}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(60,0,0,0);
|
|
else if((new int[]{2,5,8,11}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(90,0,0,0);
|
|
StartDate=StartDate-timeSpan;
|
|
TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate);
|
|
}
|
|
if(null!=PathSessionFileName)sessionParams=RestoreSession();
|
|
if(null!=sessionParams)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}",paramPathSessionFileName,sessionParams.LastUpdated));
|
|
}
|
|
Configuration.DisplayConfiguration();
|
|
DisplayBalance();
|
|
while(true)
|
|
{
|
|
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))
|
|
{
|
|
Positions positions = null;
|
|
positions=BuyPositions(TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld(TradeDate));
|
|
MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
|
|
if(CashBalance-positions.Exposure<0.00)
|
|
{
|
|
positions.Clear();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
|
|
}
|
|
positions.Display();
|
|
ActivePositions.Add(slotIndex,positions);
|
|
DisplayPurchases(positions,TradeDate);
|
|
CashBalance-=positions.Exposure;
|
|
DisplayBalance();
|
|
}
|
|
else
|
|
{
|
|
Positions slotPositions=ActivePositions[slotIndex];
|
|
List<String> closedSymbols = slotPositions.ConvertAll(x => x.Symbol); // capture the closed symbols so we don't re-enter the position (avoid wash trades)
|
|
SellPositions(slotPositions,TradeDate);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
|
|
slotPositions.Display();
|
|
AllPositions.Add(slotPositions);
|
|
DisplaySales(slotPositions, TradeDate);
|
|
CashBalance+=slotPositions.MarketValue;
|
|
ActivePositions[slotIndex].Clear();
|
|
DisplayBalance();
|
|
double cashAllocation=CashBalance;
|
|
cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / (double)HoldingPeriod);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CASH ALLOCATION:{0}",Utility.FormatCurrency(cashAllocation)));
|
|
Positions positions = null;
|
|
positions=BuyPositions(TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld(TradeDate).Concat(closedSymbols)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
|
|
positions.Display();
|
|
if(CashBalance-positions.Exposure<=0.00)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
|
|
break;
|
|
}
|
|
ActivePositions[slotIndex]=positions;
|
|
DisplayPurchases(positions, TradeDate);
|
|
CashBalance-=positions.Exposure;
|
|
DisplayBalance();
|
|
}
|
|
Cycle++;
|
|
TradeDate=dateGenerator.GetNextMonthEnd(TradeDate);
|
|
if(TradeDate>AnalysisDate)break;
|
|
} // WHILE TRUE
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"RUN COMPLETE.");
|
|
if(null!=PathSessionFileName)SaveSession();
|
|
for(int slotIndex=0;slotIndex<HoldingPeriod;slotIndex++)
|
|
{
|
|
if(!ActivePositions.ContainsKey(slotIndex)||0==ActivePositions[slotIndex].Count())continue;
|
|
Positions slotPositions=ActivePositions[slotIndex];
|
|
SellPositions(slotPositions,AnalysisDate);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L ********************");
|
|
slotPositions.Display();
|
|
AllPositions.Add(slotPositions);
|
|
DisplaySales(slotPositions, TradeDate);
|
|
CashBalance+=slotPositions.MarketValue;
|
|
ActivePositions[slotIndex].Clear();
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
|
|
AllPositions=new Positions((from Position 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;
|
|
GBPriceCache.GetInstance().Dispose();
|
|
return backTestResult;
|
|
}
|
|
|
|
public List<String> SymbolsHeld(DateTime tradeDate)
|
|
{
|
|
List<String> symbolsHeld=ActivePositions.SymbolsHeld();
|
|
if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld;
|
|
if(null == symbolsHeld)symbolsHeld=new List<String>();
|
|
PortfolioTrades portfolioTrades=PortfolioDA.GetOpenTradesAsOf(tradeDate);
|
|
if(null == portfolioTrades || 0==portfolioTrades.Count)return symbolsHeld;
|
|
symbolsHeld.AddRange(portfolioTrades.Symbols.Distinct());
|
|
return symbolsHeld;
|
|
}
|
|
// **********************************************************************************************************************************************************
|
|
// **************************************************************** G E T E X P O S U R E / M A R K E T V A L U E*****************************************
|
|
// **********************************************************************************************************************************************************
|
|
public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
|
|
{
|
|
int count=ActivePositions.Count;
|
|
double marketValue=0.00;
|
|
double exposure=0.00;
|
|
RealtimeGainLoss gainLoss=new RealtimeGainLoss();
|
|
|
|
for(int slotIndex=0;slotIndex<count;slotIndex++)
|
|
{
|
|
List<Position> positions=ActivePositions[slotIndex];
|
|
if(null==positions||0==positions.Count)continue;
|
|
foreach(Position position in positions)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetRealtimePrice(position.Symbol);
|
|
if(null==price){MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot price {0} on {1}",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));continue;}
|
|
position.CurrentPrice=price.Close;
|
|
}
|
|
}
|
|
for(int slotIndex=0;slotIndex<count;slotIndex++)
|
|
{
|
|
List<Position> positions=ActivePositions[slotIndex];
|
|
if(null==positions||0==positions.Count)continue;
|
|
exposure+=(from Position position in positions select position.Exposure).Sum();
|
|
marketValue+=(from Position position in positions select position.MarketValue).Sum();
|
|
}
|
|
gainLoss.Exposure=exposure;
|
|
gainLoss.MarketValue=marketValue;
|
|
return gainLoss;
|
|
}
|
|
// **************************************************************************************************************************************************
|
|
// **************************************************************** S E L L P O S I T I O N S *****************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private void SellPositions(Positions positions,DateTime sellDate)
|
|
{
|
|
foreach(Position position in positions)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
|
|
position.SellDate=sellDate;
|
|
if(null==price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
|
|
position.CurrentPrice=position.PurchasePrice;
|
|
}
|
|
else position.CurrentPrice=price.Close;
|
|
}
|
|
}
|
|
|
|
private void SellPosition(Position position,DateTime sellDate)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
|
|
position.SellDate=sellDate;
|
|
if(null==price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
|
|
position.CurrentPrice=position.PurchasePrice;
|
|
}
|
|
else position.CurrentPrice=price.Close;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ******************************************************** B U Y P O S I T I O N S *************************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private Positions BuyPositions(DateTime tradeDate, DateTime analysisDate, double cash, List<String> symbolsHeld)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"**BUYPOSITIONS**");
|
|
Positions positions = new Positions();
|
|
int positionCount = 0;
|
|
if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash);
|
|
MomentumCandidates momentumCandidates = MomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration);
|
|
for (int index = 0; index < momentumCandidates.Count; index++)
|
|
{
|
|
MomentumCandidate momentumCandidate = momentumCandidates[index];
|
|
Price price = GBPriceCache.GetInstance().GetPrice(momentumCandidate.Symbol, tradeDate);
|
|
if (null == price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", momentumCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
continue;
|
|
}
|
|
Position position = new Position();
|
|
position.Symbol = momentumCandidate.Symbol;
|
|
position.CumReturn252 = momentumCandidate.CumReturn252;
|
|
position.IDIndicator = momentumCandidate.IDIndicator;
|
|
position.Score=momentumCandidate.Score;
|
|
position.MaxDrawdown = momentumCandidate.MaxDrawdown;
|
|
position.MaxUpside = momentumCandidate.MaxUpside;
|
|
position.PE = momentumCandidate.PE;
|
|
position.Beta = momentumCandidate.Beta;
|
|
position.ZacksRank = momentumCandidate.ZacksRank;
|
|
position.Velocity = momentumCandidate.Velocity;
|
|
position.Volume = momentumCandidate.Volume;
|
|
position.Return1D = position.Return1D;
|
|
position.PurchaseDate = tradeDate;
|
|
position.PurchasePrice = price.Close;
|
|
position.CurrentPrice = price.Close;
|
|
position.Shares = (int)Math.Floor((cash / (double)MaxPositions) / position.PurchasePrice);
|
|
if (0 == position.Shares) continue; // if not able to purchase any shares, for example, if the share price exceeds our purchasing power then move on to the next candidate
|
|
positions.Add(position);
|
|
positionCount++;
|
|
if (positionCount >= MaxPositions) break;
|
|
}
|
|
if (0 == positions.Count && Configuration.UseFallbackCandidate) // if we don't get any signals then consider the fallback candidate options as per the configuration file
|
|
{
|
|
String fallbackCandidate = Configuration.FallbackCandidate;
|
|
if (null != Configuration.FallbackCandidateBestOf && !"".Equals(Configuration.FallbackCandidateBestOf))
|
|
{
|
|
QualityIndicator qualityIndicator=new QualityIndicator(Configuration.QualityIndicatorType);
|
|
fallbackCandidate=CandidateSelector.SelectBestCandidateSymbol(qualityIndicator,Utility.ToList(Configuration.FallbackCandidateBestOf),Configuration.FallbackCandidate,tradeDate);
|
|
}
|
|
Price price = GBPriceCache.GetInstance().GetPrice(fallbackCandidate, tradeDate);
|
|
if (null == price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", fallbackCandidate, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
return positions;
|
|
}
|
|
Position position = new Position();
|
|
position.Symbol = fallbackCandidate;
|
|
position.PurchaseDate = tradeDate;
|
|
position.PurchasePrice = price.Close;
|
|
position.CurrentPrice = price.Close;
|
|
position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
|
|
if(0 == position.Shares)return positions;
|
|
positions.Add(position);
|
|
}
|
|
return positions;
|
|
}
|
|
|
|
private Positions BuyBenchmarkPositions(DateTime tradeDate, double cash)
|
|
{
|
|
Positions positions = new Positions();
|
|
Price benchmarkPrice = GBPriceCache.GetInstance().GetPrice(Configuration.BenchmarkModeSymbol, tradeDate);
|
|
if (null == benchmarkPrice)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", Configuration.BenchmarkModeSymbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
return positions;
|
|
}
|
|
Position position = new Position();
|
|
position.Symbol = Configuration.BenchmarkModeSymbol;
|
|
position.PurchaseDate = tradeDate;
|
|
position.PurchasePrice = benchmarkPrice.Close;
|
|
position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
|
|
if (0 == position.Shares) return positions;
|
|
positions.Add(position);
|
|
return positions;
|
|
}
|
|
// *********************************************************************************************************************************************************************
|
|
// ************************************************************ E N D B U Y P O S I T I O N S ***********************************************
|
|
// *********************************************************************************************************************************************************************
|
|
private void DisplayLatestModelPerformance(String pathSessionFileName)
|
|
{
|
|
ModelPerformanceSeries performanceSeries=GetModelPerformance(pathSessionFileName);
|
|
if(null==performanceSeries || 0==performanceSeries.Count)return;
|
|
MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDoD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
|
|
ModelPerformanceItem modelPerformanceItem = performanceSeries[performanceSeries.Count-1]; // get the last record
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
|
|
modelPerformanceItem.Date.ToShortDateString(),
|
|
Utility.FormatCurrency(modelPerformanceItem.Exposure),
|
|
Utility.FormatCurrency(modelPerformanceItem.MarketValue),
|
|
Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
|
|
Utility.FormatCurrency(modelPerformanceItem.GainLoss),
|
|
Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
|
|
Utility.FormatNumber(modelPerformanceItem.R,4),
|
|
Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
|
|
Utility.FormatNumber(modelPerformanceItem.CumProd,4),
|
|
Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4),
|
|
modelPerformanceItem.ClosedPositions));
|
|
}
|
|
|
|
private void DisplayBalance()
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2}",Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
|
|
for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
|
|
{
|
|
Positions slotPositions = ActivePositions[slotIndex];
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SLOT:{0} EXPOSURE:{1}",slotIndex,Utility.FormatCurrency(slotPositions.Exposure)));
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE: {0}",Utility.FormatCurrency(ActivePositions.GetExposure())));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
|
|
}
|
|
|
|
private void DisplayBalanceFromPositions()
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
|
|
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
|
|
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetGainLoss())),
|
|
Utility.AddQuotes(Utility.FormatPercent(ActivePositions.GetGainLossPercent())),
|
|
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
|
|
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
|
|
}
|
|
|
|
private void DisplayBalance(RealtimeGainLoss gainLoss)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
|
|
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
|
|
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.GainLoss)),
|
|
Utility.AddQuotes(Utility.FormatPercent(gainLoss.GainLossPercent)),
|
|
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
|
|
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.MarketValue+CashBalance))));
|
|
}
|
|
// ****************************************************************************************************************************************
|
|
// ************************************************************* C O N T R O L T O D A Y ***********************************************
|
|
// ****************************************************************************************************************************************
|
|
public DateTime Today()
|
|
{
|
|
return DateTime.Now;
|
|
}
|
|
// ****************************************************************************************************************************************
|
|
// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
|
|
// ****************************************************************************************************************************************
|
|
public MGSessionParams RestoreSession()
|
|
{
|
|
try
|
|
{
|
|
MGSessionManager sessionManager=new MGSessionManager();
|
|
if(!MGSessionManager.SessionAvailable(PathSessionFileName))return null;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName));
|
|
MGSessionParams sessionParams=MGSessionManager.RestoreSession(PathSessionFileName);
|
|
TradeDate=sessionParams.TradeDate;
|
|
if(TradeDate.Date<AnalysisDate.Date)TradeDate=AnalysisDate; // AnalysisDate will not fall on a weekend or holiday
|
|
StartDate=sessionParams.StartDate;
|
|
Configuration=sessionParams.Configuration;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
Cycle=sessionParams.Cycle;
|
|
CashBalance=sessionParams.CashBalance;
|
|
NonTradeableCash=sessionParams.NonTradeableCash;
|
|
return sessionParams;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
return null;
|
|
}
|
|
}
|
|
public void SaveSession()
|
|
{
|
|
DateGenerator dateGenerator = new DateGenerator();
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", PathSessionFileName));
|
|
MGSessionParams sessionParams = new MGSessionParams();
|
|
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
|
|
sessionParams.LastUpdated = Today();
|
|
sessionParams.TradeDate = TradeDate;
|
|
sessionParams.StartDate = StartDate;
|
|
sessionParams.AnalysisDate = AnalysisDate;
|
|
sessionParams.Configuration = Configuration;
|
|
sessionParams.ActivePositions = ActivePositions;
|
|
sessionParams.AllPositions = AllPositions;
|
|
sessionParams.Cycle = Cycle;
|
|
sessionParams.CashBalance = CashBalance;
|
|
sessionParams.NonTradeableCash = NonTradeableCash;
|
|
MGSessionManager.SaveSession(sessionParams,PathSessionFileName);
|
|
}
|
|
|
|
public bool BackupSession()
|
|
{
|
|
DateGenerator dateGenerator = new DateGenerator();
|
|
String[] parts = PathSessionFileName.Split('.');
|
|
String backupFileName = parts[0] + "_" + Utility.DateTimeToStringYYYYMMDDMMSSTT(DateTime.Now) + ".bak";
|
|
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", backupFileName));
|
|
MGSessionParams sessionParams = new MGSessionParams();
|
|
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
|
|
sessionParams.LastUpdated = Today();
|
|
sessionParams.TradeDate = TradeDate;
|
|
sessionParams.StartDate = StartDate;
|
|
sessionParams.AnalysisDate = AnalysisDate;
|
|
sessionParams.Configuration = Configuration;
|
|
sessionParams.ActivePositions = ActivePositions;
|
|
sessionParams.AllPositions = AllPositions;
|
|
sessionParams.Cycle = Cycle;
|
|
sessionParams.CashBalance = CashBalance;
|
|
sessionParams.NonTradeableCash = NonTradeableCash;
|
|
return MGSessionManager.SaveSession(sessionParams,backupFileName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This makes for easier reading of the sales
|
|
/// </summary>
|
|
/// <param name="positions"></param>
|
|
/// <param name="tradeDate"></param>
|
|
private static void DisplaySales(Positions positions,DateTime tradeDate)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********* S E L L S *********");
|
|
foreach (Position position in positions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Sell {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.CurrentPrice,2),tradeDate.ToShortDateString()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This makes for easier reading of the purchases
|
|
/// </summary>
|
|
/// <param name="positions"></param>
|
|
/// <param name="tradeDate"></param>
|
|
private static void DisplayPurchases(Positions positions, DateTime tradeDate)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********* B U Y S *********");
|
|
foreach (Position position in positions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Buy {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.PurchasePrice,2),tradeDate.ToShortDateString()));
|
|
}
|
|
}
|
|
}
|
|
}
|