Files

817 lines
46 KiB
C#
Executable File

using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Cache;
using MarketData.Generator.Model;
using MarketData.CNNProcessing;
namespace MarketData.Generator.CMMomentum
{
public class CMBacktestResult
{
public CMBacktestResult()
{
}
public double CashBalance { get; set; }
public bool Success { get; set; }
}
// ********************************************************************************************************************************************************
public class CMMomentumBacktest
{
private double CashBalance { get; set; }
private double NonTradeableCash{get;set;}
public CMParams Parameters { get; set; }
private int HoldingPeriod { get { return Parameters.HoldingPeriod; } }
private int MaxPositions { get { return Parameters.MaxPositions; } }
private List<String> NoTradeSymbols { get { return Utility.ToList(Parameters.NoTradeSymbols); } }
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 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
{
CMSessionParams sessionParams=CMSessionManager.RestoreSession(paramPathSessionFileName);
return GetModelPerformance(sessionParams);
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
public static ModelStatistics GetModelStatistics(CMSessionParams 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(CMSessionParams sessionParams)
{
Profiler profiler=new Profiler();
ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
DateGenerator dateGenerator=new DateGenerator();
try
{
if(null==sessionParams)return null;
MarketData.Generator.CMMomentum.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.CMMomentum.Positions openPositions=new MarketData.Generator.CMMomentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
MarketData.Generator.CMMomentum.Positions closedPositions=new MarketData.Generator.CMMomentum.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.CMMomentum.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)
{
price=PricingDA.GetPrice(openPosition.Symbol,currentDate);
if(null==price)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("******************* No price for {0} on {1} *****************",openPosition.Symbol,currentDate.ToShortDateString()));
continue; // Log a message and continue otherwise upstream fails entirely.
}
LocalPriceCache.GetInstance().Add(price);
}
gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
marketValue+=(price.Close*openPosition.Shares);
}
foreach(MarketData.Generator.CMMomentum.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, 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;
CMSessionParams 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));
Parameters.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, "************** 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();
MDTrace.WriteLine(LogLevel.DEBUG, "************** A C T I V E P O S I T I O N S *************");
ActivePositions.Display();
DisplayBalanceFromPositions();
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));
double gainLoss = 0.00;
if (ActivePositions.Count > 0 && AllPositions.Count > 0)
{
RealtimeGainLoss realtimeGainLoss = GetRealtimeGainLoss(PricingDA.GetLatestDate());
gainLoss = AllPositions.Sum(x => x.GainLoss);
if (null != realtimeGainLoss) gainLoss += realtimeGainLoss.GainLoss;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else if (ActivePositions.Count > 0)
{
RealtimeGainLoss realtimeGainLoss = GetRealtimeGainLoss(PricingDA.GetLatestDate());
if (null != realtimeGainLoss) gainLoss = realtimeGainLoss.GainLoss;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Active Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else if (AllPositions.Count > 0)
{
gainLoss = AllPositions.Sum(x => x.GainLoss);
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Active Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else MDTrace.WriteLine(LogLevel.DEBUG,"There does not appear to be any trade data in this file.");
}
// ******************************************************************************************************************************************************
// ****************************************************************** C L O S E **********************************************************************
// ******************************************************************************************************************************************************
public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double sellPrice,String pathSessionFile)
{
if(null==pathSessionFile) return false;
CMSessionParams 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;
CMSessionParams 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
// paramAnalysisDate is endDate
public CMBacktestResult PerformBacktest(DateTime paramStartDate, DateTime paramAnalysisDate, String paramPathSessionFileName, CMParams cmParams)
{
CMBacktestResult backTestResult = new CMBacktestResult();
DateGenerator dateGenerator = new DateGenerator();
Parameters = cmParams;
CashBalance = Parameters.InitialCash;
ActivePositions = new ActivePositions();
AllPositions = new Positions();
StartDate = paramStartDate;
TradeDate = paramStartDate;
AnalysisDate = paramAnalysisDate;
PathSessionFileName = paramPathSessionFileName;
CMSessionParams sessionParams = null;
Cycle = 0;
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
else AnalysisDate = dateGenerator.GetPrevBusinessDay(AnalysisDate);
if (AnalysisDate.Date > Today().Date) return backTestResult;
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));
}
Parameters.DisplayConfiguration();
if(!IsCNNServerAvailable())
{
GBPriceCache.GetInstance().Dispose();
return new CMBacktestResult()
{
Success = false,
CashBalance = CashBalance
};
}
DisplayBalance();
if(TradeDate > AnalysisDate)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("*** It looks like the TradeDate {0} is greater than the AnalysisDate {1}. Please check the TradeDate in the session file. It should be the last market date of the previous month. You may have altered the system date during the last run.", TradeDate.ToShortDateString(), AnalysisDate.ToShortDateString()));
if(null!=PathSessionFileName)MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Session file: {0}",PathSessionFileName));
}
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=BuyPositions(slotIndex,TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld());
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 (1). Available cash: {0}. Requested Exposure: {1}",Utility.FormatCurrency(CashBalance),Utility.FormatCurrency(positions.Exposure)));
break;
}
ActivePositions.Add(slotIndex, positions);
CashBalance -= positions.Exposure;
DisplayBalance();
}
else
{
Positions slotPositions = ActivePositions[slotIndex];
List<String> slotSymbols = slotPositions.ConvertAll(x=>x.Symbol); // capture sell symbols to exclude from purchases to eliminate wash trades
SellPositions(slotPositions, TradeDate);
DisplaySales(slotPositions, TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
CashBalance += slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
DisplayBalance();
double cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / HoldingPeriod); // Even out the cash allocation so that no one slot eats up all the cash
Positions positions=BuyPositions(slotIndex,TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld().Concat(slotSymbols)));
DisplayPurchases(positions, TradeDate);
AddToWatchList(positions);
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 (2). Available cash: {0}. Requested Exposure: {1}", Utility.FormatCurrency(CashBalance), Utility.FormatCurrency(positions.Exposure)));
break;
}
ActivePositions[slotIndex] = positions;
CashBalance -= positions.Exposure;
DisplayBalance();
}
Cycle++;
TradeDate = dateGenerator.GetNextMonthEnd(TradeDate);
if (TradeDate > AnalysisDate) break;
} // while (true)
MDTrace.WriteLine(LogLevel.DEBUG, "RUN COMPLETE.");
DisplayBalanceFromPositions();
if (null != PathSessionFileName) SaveSession();
for (int slotIndex = 0; slotIndex < HoldingPeriod; slotIndex++)
{
if (!ActivePositions.ContainsKey(slotIndex) || 0 == ActivePositions[slotIndex].Count()) continue;
Positions slotPositions = ActivePositions[slotIndex];
DisplaySales(slotPositions, TradeDate);
SellPositions(slotPositions, AnalysisDate);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L ********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
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();
DisplayBalance();
backTestResult.Success = true;
backTestResult.CashBalance = CashBalance;
GBPriceCache.GetInstance().Dispose();
return backTestResult;
}
// **********************************************************************************************************************************************************
public List<String> SymbolsHeld()
{
return ActivePositions.GetSymbols();
}
// ***************************************************************************************************************************************************
// **************************************************************** S E L L P O S I T I O N S *****************************************************
// ***************************************************************************************************************************************************
/// <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()));
}
}
/// <summary>
/// AddToWatchList - Add purchased positions to watch list for price tracking
/// </summary>
/// <param name="positions"></param>
/// <param name="tradeDate"></param>
private static void AddToWatchList(Positions positions)
{
try
{
if(null==positions || 0==positions.Count)return;
List<string> symbols = positions.Select(x=>x.Symbol).ToList();
MDTrace.WriteLine(LogLevel.DEBUG,$"Adding {string.Join(",",symbols)} to WatchList");
WatchListDA.AddToWatchList(symbols);
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,$"{exception.ToString()}");
}
}
private void SellPositions(Positions positions, DateTime sellDate)
{
DateGenerator dateGenerator = new DateGenerator();
foreach (Position position in positions)
{
SellPosition(position, sellDate);
}
}
private void SellPosition(Position position, DateTime sellDate)
{
Price price = 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.SellDate = sellDate;
position.CurrentPrice = price.Close;
}
}
// ***************************************************************************************************************************************************
// **************************************************************** B U Y P O S I T I O N S *****************************************************
// ***************************************************************************************************************************************************
private Positions BuyPositions(int slotIndex,DateTime tradeDate, DateTime analysisDate, double cash, List<String> symbolsHeld)
{
DateGenerator dateGenerator = new DateGenerator();
Positions positions = new Positions();
int positionCount = 0;
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BUY POSITIONS (SlotIndex:{0} TradeDate:{1} AnalysisDate:{2} Cash Allocation:{3})",slotIndex,tradeDate.ToShortDateString(),analysisDate.ToShortDateString(),Utility.FormatCurrency(cash)));
CMGeneratorResult cmGeneratorResult = CMMomentumGenerator.GenerateCMCandidates(tradeDate, analysisDate, Parameters, symbolsHeld);
if (!cmGeneratorResult.Success)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("GenerateCMCandidates failed with message {0}",cmGeneratorResult.LastMessage));
return positions;
}
for (int index = 0; index < cmGeneratorResult.CMCandidates.Count;index++)
{
CMCandidate cmCandidate = cmGeneratorResult.CMCandidates[index];
Price price = GetPrice(cmCandidate.Symbol, tradeDate);
if (null == price)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", cmCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
continue;
}
Position position = new Position();
position.Symbol = cmCandidate.Symbol;
position.Beta = cmCandidate.Beta;
position.BetaMonths = cmCandidate.BetaMonths;
position.SharpeRatio = cmCandidate.SharpeRatio;
position.PurchaseDate = tradeDate;
position.PurchasePrice = price.Close;
position.CurrentPrice = double.NaN;
position.Score = cmCandidate.Score;
position.CNNPrediction = cmCandidate.CNNPrediction;
positions.Add(position);
positionCount++;
if (positionCount >= MaxPositions) break;
}
positions=PerformPositionSizing(positions,cash,tradeDate,cmGeneratorResult.InFallback);
if(Parameters.UseMaxPositionBucketWeight)
{
positions=AdjustPositionWeights(positions);
}
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("BUY POSITIONS (SlotIndex:{0} TradeDate:{1} AnalysisDate:{2} Total Exposure:{3})", slotIndex,tradeDate.ToShortDateString(), analysisDate.ToShortDateString(), Utility.FormatCurrency(positions.Sum(x=>x.Exposure))));
return positions;
}
// ***************************************************************************************************************************************************
// ************************************************************************ G E T P R I C E ******************************************************
// ***************************************************************************************************************************************************
private Price GetPrice(String symbol, DateTime priceDate)
{
DateGenerator dateGenerator = new DateGenerator();
priceDate = dateGenerator.GetPrevBusinessDay(priceDate);
Price price = GBPriceCache.GetInstance().GetPrice(symbol, priceDate);
if (null == price) price = GBPriceCache.GetInstance().GetPrice(symbol, dateGenerator.FindPrevBusinessDay(priceDate));
return price;
}
// ***************************************************************************************************************************************************
// ***************************************************************** P O S I T I O N S I Z I N G **************************************************
// ***************************************************************************************************************************************************
private Positions PerformPositionSizing(Positions positions, double cash, DateTime tradeDate,bool inFallback=false)
{
if (null == positions || 0 == positions.Count) return positions;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("PERFORM POSITION SIZING Positions:{0} Cash:{1}", positions.Count, Utility.FormatCurrency(cash)));
if (inFallback) positions = new Positions(positions.Take(1).ToList()); // if processing fallbacks then make sure we've just got a single holding
if (inFallback && !double.IsNaN(Parameters.FallbackMaxAlloc))
{
cash = cash < Parameters.FallbackMaxAlloc ? cash : Parameters.FallbackMaxAlloc;
Position position = positions[0];
position.Weight = 1.00;
position.TargetBetaOverBeta = Parameters.TargetBeta / position.Beta;
position.RiskAdjustedWeight = 1.00;
position.RiskAdjustedAllocation = cash * position.RiskAdjustedWeight;
if (position.RiskAdjustedAllocation < position.PurchasePrice) position.RiskAdjustedAllocation = position.PurchasePrice;
position.Shares = Math.Floor(position.RiskAdjustedAllocation / position.PurchasePrice);
return positions;
}
double totalTargetBetaOverBeta = 0.00;
double maxPositions = positions.Count;
foreach (Position position in positions)
{
position.Weight = 1 / maxPositions;
position.TargetBetaOverBeta = Parameters.TargetBeta / position.Beta;
}
totalTargetBetaOverBeta = (from Position position in positions select position.TargetBetaOverBeta).Sum();
foreach (Position position in positions)
{
position.RiskAdjustedWeight = position.TargetBetaOverBeta/totalTargetBetaOverBeta;
position.RiskAdjustedAllocation = cash * position.RiskAdjustedWeight;
if (position.RiskAdjustedAllocation < position.PurchasePrice) position.RiskAdjustedAllocation = position.PurchasePrice;
position.Shares = Math.Floor(position.RiskAdjustedAllocation / position.PurchasePrice);
}
if (positions.Exposure > cash) positions = new Positions(positions.Where(x => x.Shares > 1).ToList());
return positions;
}
// ***************************************************************************************************************************************************
// ***************************************************************** P O S I T I O N W E I G H T S ************************************************
// ***************************************************************************************************************************************************
// This is a post-stage to the above PositionSizing. It works by examing the weights of the positions in the bucket and ensuring that no position weight is larger than UseMaxPositionBucketWeightMaxWeight.
// If an overweight position is located then it's exposure is reduced to UseMaxPositionBucketWeightMaxWeight and the remaining positions divide the excess exposure evenly.
// This prevents any single position from eclipsing the other positions in the bucket.
private Positions AdjustPositionWeights(Positions positions)
{
if(null==positions || positions.Count<=1 || false==Parameters.UseMaxPositionBucketWeight) return positions;
double totalExposure=positions.Exposure;
Position overweightPosition=positions.Where(x=>x.Exposure/totalExposure>Parameters.UseMaxPositionBucketWeightMaxWeight).FirstOrDefault();
if(null==overweightPosition)return positions;
double weightToRedistribute=(overweightPosition.Exposure/totalExposure)-Parameters.UseMaxPositionBucketWeightMaxWeight;
foreach(Position position in positions)
{
double newWeight=double.NaN;
if(position==overweightPosition)
{
newWeight=(position.Exposure/totalExposure)-weightToRedistribute;
}
else
{
newWeight=(position.Exposure/totalExposure)+(weightToRedistribute/(double) (positions.Count-1));
}
position.Shares=Math.Floor((newWeight*totalExposure)/position.PurchasePrice);
}
return positions;
}
// **********************************************************************************************************************************************************
// **************************************************************** 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 = PricingDA.GetPrice(position.Symbol, tradeDate);
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;
}
// *********************************************************************************************************************************************************************
// *********************************************************************************************************************************************************************
// *********************************************************************************************************************************************************************
private void DisplayBalance()
{
MDTrace.WriteLine(LogLevel.DEBUG, "EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
if (!double.IsNaN(ActivePositions.GetMarketValue()))
{
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))));
}
else
{
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.GetExposure() + CashBalance))));
}
}
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;
}
// ****************************************************************************************************************************************
// ************************************************************** C N N S E R V E R ***************************************************
// ****************************************************************************************************************************************
private bool IsCNNServerAvailable()
{
if(!Parameters.UseCNN)return true;
CNNClient cnnClient=new CNNClient(Parameters.UseCNNHost);
if(!cnnClient.Ping())
{
String strMessage=String.Format("******* UseCNN=true but the server is not responding. {0} *******",Parameters.UseCNNHost);
MDTrace.WriteLine(LogLevel.DEBUG,strMessage);
return false;
}
return true;
}
// ****************************************************************************************************************************************
// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
// ****************************************************************************************************************************************
/// <summary>
/// Restore the session file from disk.
/// Take note that we do not restore AnalysisDate from the session file. The analysis date must always reflect the current analysis date.
/// </summary>
/// <returns></returns>
public CMSessionParams RestoreSession()
{
try
{
CMSessionManager sessionManager = new CMSessionManager();
if (!CMSessionManager.SessionAvailable(PathSessionFileName)) return null;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Restoring session from '{0}'", PathSessionFileName));
CMSessionParams sessionParams = CMSessionManager.RestoreSession(PathSessionFileName);
TradeDate = sessionParams.TradeDate;
if (TradeDate.Date < AnalysisDate.Date) TradeDate = AnalysisDate;
StartDate = sessionParams.StartDate;
Parameters = sessionParams.CMParams;
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));
CMSessionParams sessionParams = new CMSessionParams();
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
Parameters.TradeDate = TradeDate;
Parameters.AnalysisDate = AnalysisDate;
sessionParams.LastUpdated = Today();
sessionParams.TradeDate = TradeDate;
sessionParams.StartDate = StartDate;
sessionParams.AnalysisDate = AnalysisDate;
sessionParams.CMParams = Parameters;
sessionParams.ActivePositions = ActivePositions;
sessionParams.AllPositions = AllPositions;
sessionParams.Cycle = Cycle;
sessionParams.CashBalance = CashBalance;
sessionParams.NonTradeableCash = NonTradeableCash;
CMSessionManager.SaveSession(sessionParams, PathSessionFileName);
}
public bool BackupSession()
{
String[] parts = PathSessionFileName.Split('.');
String backupFileName = parts[0] + "_" + Utility.DateTimeToStringYYYYMMDDMMSSTT(DateTime.Now) + ".bak";
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", backupFileName));
CMSessionParams sessionParams = new CMSessionParams();
sessionParams.LastUpdated = Today();
sessionParams.TradeDate = TradeDate;
sessionParams.StartDate = StartDate;
sessionParams.AnalysisDate = AnalysisDate;
sessionParams.CMParams = Parameters;
sessionParams.ActivePositions = ActivePositions;
sessionParams.AllPositions = AllPositions;
sessionParams.Cycle = Cycle;
sessionParams.CashBalance = CashBalance;
sessionParams.NonTradeableCash = NonTradeableCash;
return CMSessionManager.SaveSession(sessionParams, backupFileName);
}
}
}