using System; using System.Collections.Generic; using System.Text; using MarketData.MarketDataModel; using MarketData.DataAccess; using MarketData.Utils; using System.Linq; using MarketData.Helper; using MarketData.Numerical; using System.Threading; using MarketData.Integration; using MarketData.Generator.Momentum; 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 NoTradeSymbols { get { return Utility.ToList(Parameters.NoTradeSymbols); } } private ActivePositions ActivePositions { get; set; } private SMSNotifications SMSNotifications { 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; } // ****************************************************************************************************************************************************** //********************************************************** U P D A T E S E S S I O N P R I C E ***************************************************** // ****************************************************************************************************************************************************** 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(); // Fix purchase date/sell date fall on weekend foreach(MarketData.Generator.CMMomentum.Position position in combinedPositions) { if(dateGenerator.IsWeekend(position.PurchaseDate)) { while(true) { position.PurchaseDate=dateGenerator.GetPrevBusinessDay(position.PurchaseDate); if(!HolidayDA.IsMarketHoliday(position.PurchaseDate)) break; } } if(dateGenerator.IsWeekend(position.SellDate)) { while(true) { position.SellDate=dateGenerator.GetNextBusinessDay(position.SellDate); if(!HolidayDA.IsMarketHoliday(position.SellDate)) break; } } } // ******************************************************** DateTime minDate=combinedPositions.Min(x => x.PurchaseDate); DateTime maxDate=PricingDA.GetLatestDate(); double prevGainLoss=double.NaN; LocalPriceCache.GetInstance().RemoveDate(maxDate); List 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; if(HolidayDA.IsMarketHoliday(currentDate)) continue; 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,90); 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())); return null; } 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())); } } // ****************************************************************************************************************************************************** //********************************************************** U P D A T E S E S S I O N P R I C E ***************************************************** // ****************************************************************************************************************************************************** public void UpdateSessionPrice(String symbol, DateTime tradeDate, double price, String pathSessionFileName) { CMSessionParams sessionParams = null; if (null == symbol || Utility.IsEpoch(tradeDate) || null == pathSessionFileName) { MDTrace.WriteLine(LogLevel.DEBUG, "UpdateSessionPrice. One or more parameters are invalid."); return; } PathSessionFileName = pathSessionFileName; if (null == (sessionParams = RestoreSession())) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFileName)); return; } List keys = new List(ActivePositions.Keys); bool hasChanges = false; foreach (int key in keys) { Positions positions = ActivePositions[key]; foreach (Position position in positions) { if (!position.Symbol.Equals(symbol) || !position.PurchaseDate.Date.Equals(tradeDate.Date)) continue; MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Changing purchase price for {0} on {1} from {2} to {3}", position.Symbol, Utility.DateTimeToStringMMHDDHYYYY(position.PurchaseDate), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(price))); position.PurchasePrice = price; if (!hasChanges) hasChanges = true; } } if (hasChanges) SaveSession(); 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)); } // ****************************************************************************************************************************************************** //************************************************************** 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."); } // ****************************************************************************************************************************************************** //******************************************************* L I Q U I D A T E A L L P O S I T I O N S *********************************************** // ****************************************************************************************************************************************************** public void CMLiquidate(String pathSessionFile, DateTime? tradeDate) { if (null == pathSessionFile) return; CMSessionParams 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))); if (null == (sessionParams = RestoreSession())) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile)); return; } 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); CashBalance += slotPositions.MarketValue; ActivePositions[slotIndex].Clear(); } GBPriceCache.GetInstance().Dispose(); SaveSession(); } // ****************************************************************************************************************************************************** // ****************************************************************** 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; SMSNotifications = new SMSNotifications(); CMSessionParams sessionParams = null; Cycle = 0; if (AnalysisDate.Date > Today().Date) return backTestResult; if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = Today(); 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(); CheckCNNServerStatus(); 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=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]; SellPositions(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,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 (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]; 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 SymbolsHeld() { return ActivePositions.GetSymbols(); } // *************************************************************************************************************************************************** // **************************************************************** S E L L P O S I T I O N S ***************************************************** // *************************************************************************************************************************************************** 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 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 PositiionSizing. 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 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 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; } // **************************************************************************************************************************************** // **************************************************************** S E S S I O N M A N A G E M E N T *********************************** // **************************************************************************************************************************************** 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() { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", PathSessionFileName)); CMSessionParams sessionParams = new CMSessionParams(); CMSessionManager sessionManager = new CMSessionManager(); 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; sessionManager.SaveSession(sessionParams, PathSessionFileName); } private void CheckCNNServerStatus() { if(Parameters.UseCNN) // ping the server here so that we don't have to do it for each request { CNNClient cnnClient=new CNNClient(Parameters.UseCNNHost); if(!cnnClient.Ping()) { String strMessage=String.Format("******* UseCNN=true but the server is not responding. {0} *******",Parameters.UseCNNHost); Console.Beep(800,200); throw new Exception(strMessage); } } } } }