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; using MarketData.Generator.Indicators; using MarketData.Numerical; using StopLimit=MarketData.Generator.Model.StopLimit; using StopLimits=MarketData.Generator.Model.StopLimits; using MarketData.Generator.ModelGenerators; namespace MarketData.Generator.MGSHMomentum { public class MGSHMomentumBacktest { private double NonTradeableCash{get;set;} private double CashBalance{get;set;} private double HedgeCashBalance{get;set;} private MGSHConfiguration Configuration{get;set;} private int HoldingPeriod{get{return Configuration.HoldingPeriod;}} private int MaxPositions{get{return Configuration.MaxPositions;}} private int MaxPricingExceptions{get{return Configuration.MaxPricingExceptions;}} private MGSHActivePositions ActivePositions{get;set;} private StopLimits StopLimits{get;set;} private MGSHPositions AllPositions{get;set;} private MGSHPositions HedgePositions{get;set;} private MGSHPricingExceptions PricingExceptions{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 { MGSHSessionParams sessionParams=MGSHSessionManager.RestoreSession(paramPathSessionFileName); return GetModelPerformance(sessionParams); } catch(Exception exception) { MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString()); return null; } } // ****************************************************************************************************************************************************** // *************************************************************************** E D I T ****************************************************************** // ****************************************************************************************************************************************************** public bool EditPosition(String symbol,DateTime purchaseDate,double purchasePrice,double initialStop,double trailingStop,String sessionFile) { if (!MGSHSessionManager.IsValidSessionFile(sessionFile)) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Invalid session file '{0}'.", sessionFile)); return false; } PathSessionFileName = sessionFile; MGSHSessionParams sessionParams=MGSHSessionManager.RestoreSession(PathSessionFileName); Configuration = sessionParams.Configuration; TradeDate = sessionParams.TradeDate; StartDate = sessionParams.StartDate; AnalysisDate = sessionParams.AnalysisDate; Cycle=sessionParams.Cycle; sessionParams.LastUpdated = DateTime.Now; StopLimits=sessionParams.StopLimits; ActivePositions=sessionParams.ActivePositions; AllPositions=sessionParams.AllPositions; HedgePositions=sessionParams.HedgePositions; CashBalance=sessionParams.CashBalance; NonTradeableCash=sessionParams.NonTradeableCash; HedgeCashBalance=sessionParams.HedgeCashBalance; PricingExceptions=sessionParams.PricingExceptions; if(!BackupSession())return false; MGSHPositions activePositions = ActivePositions.GetPositions(); MGSHPosition 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.TrailingStopLimit.Equals(trailingStop)) position.TrailingStopLimit = trailingStop; if (!position.InitialStopLimit.Equals(initialStop)) position.InitialStopLimit = initialStop; 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))); MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Adjusting R for Position for symbol '{0}' purchased on {1}. Original R: {2} New R: {3} ", symbol, purchaseDate.ToShortDateString(), Utility.FormatNumber(position.R, 2), Utility.FormatNumber(position.PositionRiskPercentDecimal * purchasePrice))); CashBalance += (position.PurchasePrice - purchasePrice) * position.Shares; position.Comment = (String.IsNullOrEmpty(position.Comment) ? "" : position.Comment + ".") + String.Format("Price changed on {0} from {1} to {2}", DateTime.Now.ToShortDateString(), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(purchasePrice)); 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; } // ****************************************************************************************************************************************************** // ************************************************************************ C L O S E ****************************************************************** // ****************************************************************************************************************************************************** public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double price,String sessionFile) { if (!MGSHSessionManager.IsValidSessionFile(sessionFile)) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Invalid session file '{0}'.", sessionFile)); return false; } PathSessionFileName = sessionFile; MGSHSessionParams sessionParams=MGSHSessionManager.RestoreSession(PathSessionFileName); Configuration = sessionParams.Configuration; TradeDate = sessionParams.TradeDate; StartDate = sessionParams.StartDate; AnalysisDate = sessionParams.AnalysisDate; Cycle=sessionParams.Cycle; sessionParams.LastUpdated = DateTime.Now; StopLimits=sessionParams.StopLimits; ActivePositions=sessionParams.ActivePositions; AllPositions=sessionParams.AllPositions; HedgePositions=sessionParams.HedgePositions; CashBalance=sessionParams.CashBalance; NonTradeableCash=sessionParams.NonTradeableCash; HedgeCashBalance=sessionParams.HedgeCashBalance; PricingExceptions=sessionParams.PricingExceptions; if(!BackupSession())return false; MGSHPositions activePositions = ActivePositions.GetPositions(); MGSHPosition 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=price; CashBalance+=position.MarketValue; SaveSession(); return true; } position.SellDate=sellDate; position.CurrentPrice=price; position.Comment="Manual close."; 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; } // ****************************************************************************************************************************************************** // **************************************************************** S T A T I S T I C S ****************************************************************** // ****************************************************************************************************************************************************** // Calculates the expectation for the model ( Percent of Winning Trades * Average Gain)/(Percent Losing Trades * Average Loss) // The expectation should be above zero // Using the AllPositions collection ignored open positions and active hedge positions public static ModelStatistics GetModelStatistics(MGSHSessionParams 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(MGSHSessionParams sessionParams) { Profiler profiler=new Profiler(); ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries(); DateGenerator dateGenerator=new DateGenerator(); try { if(null==sessionParams)return null; MGSHPositions combinedPositions=sessionParams.GetCombinedPositions(); 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) { MGSHPositions openPositions=new MGSHPositions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList()); MGSHPositions closedPositions=new MGSHPositions(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(MGSHPosition 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(MGSHPosition 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; MGSHSessionParams sessionParams=null; if(null==(sessionParams=RestoreSession())) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",paramPathSessionFileName)); return; } MDTrace.WriteLine(LogLevel.DEBUG,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 MGSHPositions((from MGSHPosition 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,"************** H E D G E P O S I T I O N S *************"); HedgePositions.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(); DisplayStatistics(sessionParams); 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)); } // ************************************************************************************************************************************************************************************* // ************************************************************************************************************************************************************************************* // ************************************************************************************************************************************************************************************* // ************************************************************************************************************************************************************************************* // ****************************************************************************************************************************************************** // ****************************************************************** B A C K T E S T ***************************************************************** // ****************************************************************************************************************************************************** /// /// Ideally, startDate should be November,February,May,August /// Use this endpoint to first launch the model on the last trading day of a given month supplying the configuration with initial settings /// Subsequent daily runs are accomplished by calling UpdateDaily(startDate,endDate,pathSessionFileName) where the startDate=endDate=the trade date /// after the market closes /// /// {ORIGINAL START DATE} /// {TODAY} /// The session file name /// The configuration /// public MGSHBacktestResult PerformBacktest(DateTime paramStartDate,DateTime paramAnalysisDate,String paramPathSessionFileName,MGSHConfiguration configuration) { MGSHBacktestResult backTestResult=new MGSHBacktestResult(); DateGenerator dateGenerator=new DateGenerator(); Configuration=configuration; CashBalance=Configuration.InitialCash; HedgeCashBalance=Configuration.HedgeInitialCash; ActivePositions=new MGSHActivePositions(); AllPositions=new MGSHPositions(); HedgePositions=new MGSHPositions(); PricingExceptions=new MGSHPricingExceptions(); StartDate=paramStartDate; TradeDate=paramStartDate; AnalysisDate=paramAnalysisDate; PathSessionFileName=paramPathSessionFileName; MGSHSessionParams 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(); DateTime currentMonthEndDate = dateGenerator.GetCurrentMonthEnd(TradeDate); if(TradeDate!=currentMonthEndDate) { TradeDate=currentMonthEndDate; AnalysisDate=TradeDate; } } if(null!=sessionParams) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}, Current Trade Date: {2}",paramPathSessionFileName,sessionParams.LastUpdated.ToShortDateString(),TradeDate.ToShortDateString())); } 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)) { BuySlotPositions(slotIndex); } else { SellAndBuySlotPositions(slotIndex); } ManageHedgedPositions(TradeDate); DateTime nextBusinessDay = dateGenerator.FindNextBusinessDay(TradeDate); DateTime nextMonthEndDate = dateGenerator.GetNextMonthEnd(TradeDate); // if the TradeDate and AnalysisDate are equal then set the trade date to the next business day and exit the cycle if(TradeDate.Equals(AnalysisDate)) { TradeDate = nextBusinessDay; Cycle++; // advance the cycle so the next monthly run calculates the correct slot break; } // This will enter the daily cycle which we need for backtesting if(Configuration.UseStopLimits || Configuration.UseHedging) { if(nextMonthEndDate >= AnalysisDate) { nextMonthEndDate=dateGenerator.FindNextBusinessDay(AnalysisDate); } TradeDate = nextBusinessDay; SaveSession(); UpdateDaily(nextBusinessDay,nextMonthEndDate,AnalysisDate,paramPathSessionFileName); // This will save the session file sessionParams = RestoreSession(); } Cycle++; } // WHILE TRUE MDTrace.WriteLine(LogLevel.DEBUG,$"RUN COMPLETE FOR ANALYSIS DATE {AnalysisDate.ToShortDateString()}."); if(null!=PathSessionFileName)SaveSession(); for(int slotIndex=0;slotIndex /// Entry point for daily runs. Make sure for daily runs that startDate=endDate=Trading Date after market close /// The system does not process endDate. (i.e.) startDate < endDate /// /// {ORIGINAL START DATE} /// {TODAY} /// The session file name /// public MGSHBacktestResult UpdateDaily(DateTime startDate, DateTime endDate, DateTime analysisDate, String pathSessionFileName) { DateGenerator dateGenerator = new DateGenerator(); PathSessionFileName = pathSessionFileName; RestoreSession(); AnalysisDate=analysisDate; if(startDate != TradeDate) { MDTrace.WriteLine(LogLevel.DEBUG,ConsoleColor.Red,$"Unexpected StartDate. Start Date:{startDate.ToShortDateString()} Trade Date:{TradeDate.ToShortDateString()}"); return new MGSHBacktestResult(false, CashBalance); } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("************** U P D A T E D A I L Y **************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Trade Date:{TradeDate.ToShortDateString()} Analysis Date:{AnalysisDate.ToShortDateString()}")); while(startDate < endDate) { TradeDate=startDate; if(Configuration.UseStopLimits) { UpdateStopLimitsForActivePositions(startDate); } if(Configuration.UseHedging) { ManageHedgedPositions(startDate); } DisplayBalance(); startDate = dateGenerator.FindNextBusinessDay(startDate); TradeDate = startDate; } SaveSession(); return new MGSHBacktestResult(true, CashBalance); } // ******************************************************************************************************************************************************* // ************************************************************ H E D G E M A N A G E M E N T ******************************************************* // ******************************************************************************************************************************************************* /// /// ManageHedgedPositions /// /// /// True if any changed were made otherwise false public bool ManageHedgedPositions(DateTime analysisDate) { HedgeManager hedgeManager = new HedgeManager(){HedgeShortSymbol=Configuration.HedgeShortSymbol,Verbose=false}; bool changed = false; bool openHedgeIndicator = hedgeManager.IsOpenHedgeIndicator(analysisDate,Configuration.HedgeCloseAboveSMANDays, Configuration.HedgeBandBreakCheckDays); bool haveHedgedPositions = HedgePositions.Where(x => x.Symbol.Equals(Configuration.HedgeShortSymbol)).Count()>0?true:false; int numHedgePositions = HedgePositions.Where(x => x.Symbol.Equals(Configuration.HedgeShortSymbol)).Count(); // There are no hedged positions and the indicator is off if(!openHedgeIndicator && !haveHedgedPositions) { return changed; } // The indicator is on and we have no hedged positions else if(openHedgeIndicator && !haveHedgedPositions) { MGSHPosition shortPosition = BuyHedgePosition(analysisDate, HedgeCashBalance); if(null != shortPosition) { changed = true; MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************"); MGSHPosition.DisplayHeader(); shortPosition.Display(); HedgePositions.Add(shortPosition); HedgeCashBalance-=shortPosition.Exposure; } } else if(openHedgeIndicator && haveHedgedPositions) { changed = ManageActiveHedgePositions(analysisDate); } else if(!openHedgeIndicator && haveHedgedPositions) { changed = ManageActiveHedgePositions(analysisDate); } return changed; } /// /// Attempts to purchase the hedge with the supplied cash allocation /// /// /// /// Position on success otherwise null public MGSHPosition BuyHedgePosition(DateTime analysisDate, double cashAllocation) { Price shortPositionPrice = GBPriceCache.GetInstance().GetPrice(Configuration.HedgeShortSymbol,analysisDate); if(null == shortPositionPrice) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Cannot locate a price for {Configuration.HedgeShortSymbol} on {analysisDate.ToShortDateString()}")); return null; } MGSHPosition shortPosition = new MGSHPosition(); shortPosition.Symbol = Configuration.HedgeShortSymbol; shortPosition.PurchaseDate = analysisDate; shortPosition.PurchasePrice = shortPositionPrice.Close; shortPosition.CurrentPrice = shortPositionPrice.Close; shortPosition.InitialStopLimit = shortPosition.PurchasePrice - ( shortPosition.PurchasePrice * Configuration.HedgeRiskPercentDecimal); shortPosition.PositionRiskPercentDecimal = Configuration.HedgeRiskPercentDecimal; // The risk percent shortPosition.R = shortPosition.PositionRiskPercentDecimal * shortPosition.PurchasePrice; // PositionRiskPercentDecimal*PurchasePrice shortPosition.TrailingStopLimit = shortPosition.InitialStopLimit; shortPosition.LastStopAdjustment = Utility.Epoch; shortPosition.Shares = (int)Math.Floor(cashAllocation / shortPositionPrice.Close); if(0 == shortPosition.Shares) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Insufficient Cash Allocation to purchase shares of {Configuration.HedgeShortSymbol} on {analysisDate.ToShortDateString()}. Cash Allocation:{Utility.FormatCurrency(cashAllocation)}")); return null; } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Initial stop limit for {0} on {1} with PurchsePrice:{2} is {3}, Shares:{4} Risk:{5}%", shortPosition.Symbol, shortPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(shortPosition.PurchasePrice,2), Utility.FormatCurrency(shortPosition.InitialStopLimit,2), Utility.FormatNumber(shortPosition.Shares,2), Utility.FormatPercent(Configuration.HedgeRiskPercentDecimal) )); return shortPosition; } /// /// Manages open hedge position by checking and adjusting stop price and determining if the hedge should exit /// /// /// public bool ManageActiveHedgePositions(DateTime analysisDate) { HedgeManager hedgeManager = new HedgeManager(){HedgeShortSymbol=Configuration.HedgeShortSymbol,Verbose=false}; List closedPositions = new List(); foreach(MGSHPosition position in HedgePositions) { Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,analysisDate); if(null==price) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[ManageActiveHedgePositions] No price for {0} on {1}. Cannot evaluate stop limit.",position.Symbol,analysisDate.ToShortDateString())); continue; } position.CurrentPrice = price.Close; if(price.Low < position.TrailingStopLimit && !position.PurchaseDate.Equals(analysisDate)) { position.SellDate = analysisDate; position.CurrentPrice = position.TrailingStopLimit; position.Comment = "Closed due to trailing stop."; HedgeCashBalance+=position.MarketValue; // This tries to maintain a hedgeCashMarginPercentDecimal margin in the hedge cash. In other words some % of the hedge gain -> Cash and some -> hedge cash, not allowing hedge cash to grow some margin beyond it's initial setting double hedgeCashMarginPercentDecimal=.10; double hedgeCashTreshholdAmount=Configuration.HedgeInitialCash*(1.00+hedgeCashMarginPercentDecimal); if(HedgeCashBalance > hedgeCashTreshholdAmount) { DisplayBalance(); MDTrace.WriteLine(LogLevel.DEBUG,$"The hedge cash balance would be {Utility.FormatCurrency(HedgeCashBalance)}, setting HedgeCashBalance to {Utility.FormatCurrency(hedgeCashTreshholdAmount)} and adding {Utility.FormatCurrency((HedgeCashBalance-hedgeCashTreshholdAmount))} to CashBalance."); CashBalance += (HedgeCashBalance-hedgeCashTreshholdAmount); HedgeCashBalance = hedgeCashTreshholdAmount; DisplayBalance(); } if(HedgeCashBalance<0.00) { HedgeCashBalance=0.00; } // Retain this original code for a while until happy with the hedge cash margin approach //if(HedgeCashBalance>Configuration.HedgeInitialCash) //{ // CashBalance+=HedgeCashBalance-Configuration.HedgeInitialCash; // if we made gains on the hedge and the hedge cash would grow then put proceeds above the initial hedge cash in regualr cash // HedgeCashBalance=Configuration.HedgeInitialCash; // For example, let hedge proceeeds help the portfolio invest //} AllPositions.Add(position); closedPositions.Add(position); } else if(hedgeManager.IsLowerBandBreakIndicator(analysisDate, position)) { StopLimit stopLimit = hedgeManager.EvaluateStopPriceHedge(analysisDate, position,Configuration); if(null != stopLimit) { StopLimits.Add(stopLimit); } } } if(0!=closedPositions.Count) { foreach(MGSHPosition position in closedPositions) { MDTrace.WriteLine(LogLevel.DEBUG,""); MDTrace.WriteLine(LogLevel.DEBUG,"******************************************** S E L L S T O P L I M I T ********************************************"); position.Display(); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Gain/Loss:{Utility.FormatCurrency(position.GainLoss)} Gain/Loss(%):{Utility.FormatPercent(position.GainLossPcnt)}")); HedgePositions.Remove(position); } } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("*************************************************************************************************",analysisDate.ToShortDateString())); if(closedPositions.Count>0)return true; return false; } // ************************************************************************************************************************************************* // ************************************************************ E N D M A N A G E H E D G I N G ************************************************ // ************************************************************************************************************************************************* // ********************************************************************************************************************************************************** // ********************************************************************** S T O P L I M I T S *************************************************************** // ********************************************************************************************************************************************************** public bool SetInitialStopLimitsForNewPositions(DateTime tradeDate,MGSHPositions positions) { if(!Configuration.UseStopLimits)return true; if(null == positions || 0==positions.Count) { return true; } foreach(MGSHPosition position in positions) { position.InitialStopLimit = position.PurchasePrice - (position.PurchasePrice * Configuration.StopLimitRiskPercentDecimal); position.PositionRiskPercentDecimal = Configuration.StopLimitRiskPercentDecimal; // = position.PurchasePrice - position.InitialStopLimit; position.R = position.PositionRiskPercentDecimal * position.PurchasePrice; // PositionRiskPercentDecimal*PurchasePrice position.TrailingStopLimit = position.InitialStopLimit; position.LastStopAdjustment = Utility.Epoch; MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Initial stop limit for {0} on {1} with PurchsePrice:{2} is {3}, Shares:{4} Risk:{5}%", position.Symbol, position.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(position.PurchasePrice,2), Utility.FormatCurrency(position.InitialStopLimit,2), Utility.FormatNumber(position.Shares,2), Utility.FormatPercent(Configuration.StopLimitRiskPercentDecimal) )); } return true; } /// /// Updates stop limits for active positions and closes positions that have stop limit violations /// /// true if any positions were closed, otherwise returns false public bool UpdateStopLimitsForActivePositions(DateTime analysisDate) { if(!Configuration.UseStopLimits)return true; MGSHPositions closedPositions = new MGSHPositions(); MDTrace.WriteLine(LogLevel.DEBUG,"************************** U P D A T E S T O P L I M I T S F O R A C T I V E P O S I T I O N S ************"); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("ANALYSIS DATE:{0}",analysisDate.ToShortDateString())); List slotKeys = new List(ActivePositions.Keys); for(int slotIndex = 0; slotIndex< slotKeys.Count;slotIndex++) { MGSHPositions positions = ActivePositions[slotKeys[slotIndex]]; foreach(MGSHPosition position in positions) { Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,analysisDate); if(null==price) { int exceptionCount=AddPricingException(position.Symbol); if(exceptionCount>MaxPricingExceptions) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[UpdateStopLimitsForActivePositions] Selling {0} on {1} because price exceptions exceeds maximum of {2}",position.Symbol,analysisDate.ToShortDateString(),MaxPricingExceptions)); price=GBPriceCache.GetInstance().GetPriceOrLatestAvailable(position.Symbol,analysisDate); position.SellDate=analysisDate; position.CurrentPrice=price.Close; position.Comment="Close due to pricing exceptions."; CashBalance+=position.MarketValue; AllPositions.Add(position); closedPositions.Add(position); } else MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[UpdateStopLimitsForActivePositions] Cannot determine price for {0} on {1}",position.Symbol,analysisDate.ToShortDateString())); continue; } // This is an older position for which we never set an initial stop. We will not adjust these if (double.IsNaN(position.R) || double.IsNaN(position.TrailingStopLimit) || double.IsNaN(position.InitialStopLimit)) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("[UpdateStopLimitsForActivePositions] Position {0} on {1} is legacy will not adjust stop limit.", position.Symbol, analysisDate.ToShortDateString())); continue; } position.CurrentPrice = price.Close; RemovePricingException(position.Symbol); if (price.Low < position.TrailingStopLimit && !position.PurchaseDate.Equals(analysisDate)) { position.SellDate = analysisDate; position.CurrentPrice = position.TrailingStopLimit; position.Comment = "Closed due to trailing stop."; CashBalance += position.MarketValue; AllPositions.Add(position); closedPositions.Add(position); } else if (price.Low > position.PurchasePrice) // If the Low price is above our purchase price then re-evaluate the stop { EvaluateStopPrice(analysisDate, price, position); } else { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("[UpdateStopLimitsForActivePositions] No stop adjustment for {0} on {1} because low price {2} is less than or equal to the purchase price {3}.", position.Symbol, analysisDate.ToShortDateString(), Utility.FormatCurrency(price.Low), Utility.FormatCurrency(position.PurchasePrice))); } } } if(0!=closedPositions.Count) { foreach(MGSHPosition position in closedPositions) { MDTrace.WriteLine(LogLevel.DEBUG,""); MDTrace.WriteLine(LogLevel.DEBUG,"******************************************** S E L L S T O P L I M I T ********************************************"); position.Display(); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Gain/Loss:{Utility.FormatCurrency(position.GainLoss)} Gain/Loss(%):{Utility.FormatPercent(position.GainLossPcnt)}")); ActivePositions.Remove(position); } } if(closedPositions.Count>0)return true; return false; } /// /// Evaluates stop prices for slot positons to see if we need to perform updates /// /// true if changes were made otherwise false private bool EvaluateStopPrice(DateTime analysisDate,Price currentPrice,MGSHPosition position) { bool changed = false; MDTrace.WriteLine(LogLevel.DEBUG,String.Format("EvaluateStopPrice: {0} on {1}",position.Symbol,analysisDate.ToShortDateString())); DateGenerator dateGenerator=new DateGenerator(); // only adjust stops if we are trending up Prices prices=GBPriceCache.GetInstance().GetPrices(position.Symbol,analysisDate,Configuration.StopLimitPriceTrendDays); PriceTrendIndicatorResult priceTrendIndicatorResult=PriceTrendIndicator.IsUptrend(prices,Configuration.StopLimitPriceTrendDays); if (!priceTrendIndicatorResult.IsUpTrend) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0} does not have upward price trend (higher highs and higher lows for {1} consecutive days), will not adjust stop price", position.Symbol, Configuration.StopLimitPriceTrendDays)); return changed; } // where R = Risk Per Share in $ double trailingStop=position.InitialStopLimit+Math.Floor((currentPrice.Low-position.PurchasePrice)/position.R)*position.R; double trailingStopScaled=trailingStop; double daysHeld=Math.Abs(dateGenerator.DaysBetweenActual(position.PurchaseDate,analysisDate)); if(Utility.IsEpoch(position.LastStopAdjustment)) // we've never adjusted the stop price { if(daysHeld>=Configuration.MinDaysBetweenInitialStopAdjustment) { trailingStopScaled=GetStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop); trailingStop=Math.Max(trailingStop,trailingStopScaled); if(trailingStop>=currentPrice.Low || trailingStop>=currentPrice.Close) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(currentPrice.Low))); return changed; } if(Numerics.Round(trailingStop)<=(Numerics.Round(position.TrailingStopLimit))) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be less than or equal to the existing stop limit of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(position.TrailingStopLimit))); return changed; } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T S T O P L I M I T ************************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit after {0} days for {1} from {2} to {3}. Purchase Price: {4} Current Price:{5} Spread:{6} Shares:{7}", daysHeld, position.Symbol,Utility.FormatCurrency(position.TrailingStopLimit), Utility.FormatCurrency(trailingStop), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(currentPrice.Close), Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop), Utility.FormatNumber(position.Shares,2))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("*****************************************************************************************")); StopLimit newStopLimit=new StopLimit { StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate), Symbol=position.Symbol, AnalysisDate=analysisDate, PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit, NewStop=trailingStop, CurrentPriceLow=currentPrice.Low, CurrentPriceClose=currentPrice.Close, PriceTrendIndicatorSlope=priceTrendIndicatorResult.LowPriceSlope }; AddStopLimit(newStopLimit); position.TrailingStopLimit=trailingStop; position.LastStopAdjustment=analysisDate; changed=true; } else { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The initial stop for {0} will not be modified until the grace period is reached. Days Held:{1} MinDaysBetweenInitialStopAdjustment:{2} ", position.Symbol,daysHeld,Configuration.MinDaysBetweenInitialStopAdjustment)); } } else // we have already made prior stop adjustments { int daysSinceLastStopAdjustment=Math.Abs(dateGenerator.DaysBetweenActual(position.LastStopAdjustment,analysisDate)); if(daysSinceLastStopAdjustment>=Configuration.MinDaysBetweenStopAdjustments) { trailingStopScaled=GetStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop); trailingStop=Math.Max(trailingStop,trailingStopScaled); if(trailingStop>=currentPrice.Low||trailingStop>=currentPrice.Close) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(currentPrice.Low))); return changed; } if(Numerics.Round(position.TrailingStopLimit) < Numerics.Round(trailingStop)) // round the stop limits to fractionals don't look like differences { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T S T O P L I M I T ************************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit after {0} days for {1} from {2} to {3}. Purchase Price: {4} Current Price:{5} Spread:{6} Shares:{7}",daysHeld,position.Symbol,Utility.FormatCurrency(position.TrailingStopLimit),Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(position.PurchasePrice),Utility.FormatCurrency(currentPrice.Close),Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop),Utility.FormatNumber(position.Shares,2))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************************************************************************")); StopLimit newStopLimit=new StopLimit { StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate), Symbol=position.Symbol, AnalysisDate=analysisDate, PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit, NewStop=trailingStop, CurrentPriceLow=currentPrice.Low, CurrentPriceClose=currentPrice.Close, PriceTrendIndicatorSlope=priceTrendIndicatorResult.LowPriceSlope }; AddStopLimit(newStopLimit); position.TrailingStopLimit=trailingStop; position.LastStopAdjustment=analysisDate; changed = true; } else { double currentTrailingStopLimit=Numerics.Round(position.TrailingStopLimit); double suggestedTrailingStopLimit=Numerics.Round(trailingStop); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Not Adjusting Stop Limit for {0} because the new trailing stop limit would be less than or equal to the current trailing stop limit. Current Trailing Stop Limit:{1}, Calculated Trailing Stop Limit:{2}.",position.Symbol,Utility.FormatCurrency(currentTrailingStopLimit),Utility.FormatCurrency(suggestedTrailingStopLimit))); } } else { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The trailing stop for {0} was adjusted {1} days ago. MinDaysBetweenStopAdjustments is {2}", position.Symbol, daysSinceLastStopAdjustment, Configuration.MinDaysBetweenStopAdjustments)); } } return changed; } private double GetStopLimitWithScalingAverageTrueRange(DateTime tradeDate,Price currentPrice,MGSHPosition position,double stopLimitNonScaled) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GetStopLimitWithScalingAverageTrueRange: Symbol:{0}",position.Symbol)); double volatility=double.NaN; volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,tradeDate,Configuration.StopLimitScalingVolatilityDays, Configuration.StopLimitATRMultiplier); if(double.IsNaN(volatility)) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to calculate AverageTrueRange for {0} on {1}. Using non-scaled stop limit.",position.Symbol,tradeDate.ToShortDateString())); return stopLimitNonScaled; } double stopLimit = currentPrice.Low - volatility; // We base the stop off of the low in order to give a bit more breathing room in the stop in the event that we have a wide spread between the close and the low. Backtested currentPrice.Close vs currentPrice.Low and basing off the low yields better results. return stopLimit; } // ************************************************************************************************************************************** // ************************************************** S T O P L I M I T C O L L E C T I O N M A I N T E N A N C E ****************** // ************************************************************************************************************************************** private void AddStopLimit(StopLimit stopLimit) { if(null == StopLimits)StopLimits = new StopLimits(); StopLimits.Add(stopLimit); } // ************************************************************************************************************************************************ // ********************************************* P R I C I N G E X C E P T I O N C O L L E C T I O N M A I N T E N A N C E ****************** // ************************************************************************************************************************************************ private int AddPricingException(String symbol) { MGSHPricingException pricingException = PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault(); if (null == pricingException) { pricingException = new MGSHPricingException(symbol, 1); PricingExceptions.Add(pricingException); PricingExceptions.Add(pricingException); } else pricingException.ExceptionCount++; return pricingException.ExceptionCount; } private void RemovePricingException(String symbol) { MGSHPricingException pricingException = PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault(); if (null == pricingException) return; PricingExceptions.Remove(pricingException); } private bool HasPricingException(String symbol) { MGSHPricingException pricingException = PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault(); if (null == pricingException) return false; return true; } // ****************************************************************************************************************************************************** // ****************************************************************************************************************************************************** // ****************************************************************************************************************************************************** public List SymbolsHeld(DateTime tradeDate) { List symbolsHeld=ActivePositions.SymbolsHeld(); if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld; if(null == symbolsHeld)symbolsHeld=new List(); 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 positions=ActivePositions[slotIndex]; if(null==positions||0==positions.Count)continue; foreach(MGSHPosition 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 positions=ActivePositions[slotIndex]; if(null==positions||0==positions.Count)continue; exposure+=(from MGSHPosition position in positions select position.Exposure).Sum(); marketValue+=(from MGSHPosition 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(MGSHPositions positions,DateTime sellDate) { foreach(MGSHPosition position in positions) { SellPosition(position, sellDate); } } private void SellPosition(MGSHPosition 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 S L O T P O S I T I O N S ************************************************************ // ************************************************************************************************************************************************************ private MGSHPositions BuyPositions(int slotIndex, DateTime tradeDate, DateTime analysisDate, double cash, List symbolsHeld, double maxPositions=double.NaN) { MGSHPositions positions = new MGSHPositions(); if(double.IsNaN(maxPositions))maxPositions=MaxPositions; if(0==maxPositions)return positions; MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"BUY POSITIONS: SLOT:{slotIndex} TRADE DATE:{TradeDate.ToShortDateString()} CASH:{Utility.FormatCurrency(cash)} POSITIONS TO FILL:{maxPositions}")); int positionCount = 0; if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash); MGSHMomentumCandidates momentumCandidates = MGSHMomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration); for (int index = 0; index < momentumCandidates.Count; index++) { MGSHMomentumCandidate 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; } MGSHPosition position = new MGSHPosition(); position.Symbol = momentumCandidate.Symbol; position.CumReturn252 = momentumCandidate.CumReturn252; position.IDIndicator = momentumCandidate.IDIndicator; position.Score=momentumCandidate.Score; position.PE = momentumCandidate.PE; position.Beta = momentumCandidate.Beta; 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); // make sure the lot is evenly divided by the MaxPositions allowble for the slot 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)) { MGSHQualityIndicator qualityIndicator = new MGSHQualityIndicator(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; } MGSHPosition position = new MGSHPosition(); 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) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("************** Insufficient cash to purchase shares of {0} on {1}. Available Cash:{2} Price:{3} ************", fallbackCandidate, Utility.DateTimeToStringMMHDDHYYYY(tradeDate), Utility.FormatCurrency(cash), Utility.FormatCurrency(price.Close))); return positions; } positions.Add(position); } return positions; } /// /// Buys the benchmark mode symbol (BenchmarModeSymbol) if the configuration is set to BenchmarkMode (BenchmarkMode) /// /// Positions private MGSHPositions BuyBenchmarkPositions(DateTime tradeDate, double cash) { MGSHPositions positions = new MGSHPositions(); 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; } MGSHPosition position = new MGSHPosition(); 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 *********************************************** // ********************************************************************************************************************************************* //****************************************************************************************************************************************************** //************************************************************* S T A T I S T I C S ******************************************************************** //****************************************************************************************************************************************************** private void DisplayStatistics(MGSHSessionParams sessionParams) { ModelStatistics modelStatistics = GetModelStatistics(sessionParams); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("************** M O D E L S T A T I S T I C S **************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Trades:{modelStatistics.TotalTrades}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Winning Trades:{modelStatistics.WinningTrades}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Losing Trades:{modelStatistics.LosingTrades}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Winning Trade Percent Gain:{Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)}%")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Average Losing Trade Percent Loss:{Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)}%")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Winning Trades Percent:{Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)}%")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Losing Trades Percent:{Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)}%")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Expectancy:{Utility.FormatNumber(modelStatistics.Expectancy,2)}")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**************************************************************")); } //****************************************************************************************************************************************************** //****************************************************************** D I S P L A Y B A L A N C E ***************************************************** //****************************************************************************************************************************************************** private void DisplayBalance() { MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************"); MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE, AVAILABLE CASH, TOTAL PORTFOLIO ACCOUNT (MV+CASH), AVAILABLE HEDGE CASH, TOTAL PORTFOLIO ACCOUNT + HEDGES"); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}", Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure()+HedgePositions.Exposure)), Utility.AddQuotes(Utility.FormatCurrency(CashBalance)), Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance)), Utility.AddQuotes(Utility.FormatCurrency(HedgeCashBalance)), Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+HedgePositions.MarketValue+CashBalance)) )); List keys = new List(ActivePositions.Keys); keys.Sort(); for (int keyIndex = 0; keyIndex < keys.Count; keyIndex++) { MGSHPositions slotPositions = ActivePositions[keys[keyIndex]]; MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SLOT:{0} POSITIONS:{1} EXPOSURE:{2} SYMBOLS:{3}", keys[keyIndex],slotPositions.Count, Utility.FormatCurrency(slotPositions.Exposure), Utility.ListToString(slotPositions.Symbols(),','))); } if(HedgePositions.Count>0) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("HEDGES:{0} EXPOSURE:{1} SYMBOLS:{2}", HedgePositions.Count, Utility.FormatCurrency(HedgePositions.Exposure), Configuration.HedgeShortSymbol)); } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE: {0}",Utility.FormatCurrency(ActivePositions.GetExposure()+HedgePositions.Exposure))); MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************"); } private void DisplayBalanceFromPositions() { MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE, GAIN/LOSS, GAIN/LOSS(%), AVAILABLE CASH, TOTAL PORTFOLIO ACCOUNT (MV+CASH), AVAILABLE HEDGE CASH, TOTAL PORTFOLIO ACCOUNT + HEDGES"); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4},{5}", 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)), Utility.AddQuotes(Utility.FormatCurrency(HedgeCashBalance)), Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+HedgePositions.MarketValue+CashBalance)) )); } private void DisplayBalance(RealtimeGainLoss gainLoss) { MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE, GAIN/LOSS, GAIN/LOSS(%), AVAILABLE CASH, TOTAL PORTFOLIO ACCOUNT (MV+CASH), AVAILABLE HEDGE CASH, TOTAL PORTFOLIO ACCOUNT + HEDGES"); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4},{5}", 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(ActivePositions.GetMarketValue()+CashBalance)), Utility.AddQuotes(Utility.FormatCurrency(HedgeCashBalance)), Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+HedgePositions.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 MGSHSessionParams RestoreSession() { try { if(!MGSHSessionManager.SessionAvailable(PathSessionFileName))return null; MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName)); MGSHSessionParams sessionParams=MGSHSessionManager.RestoreSession(PathSessionFileName); TradeDate=sessionParams.TradeDate; StartDate=sessionParams.StartDate; Configuration=sessionParams.Configuration; ActivePositions=sessionParams.ActivePositions; AllPositions=sessionParams.AllPositions; HedgePositions=sessionParams.HedgePositions; StopLimits=sessionParams.StopLimits; PricingExceptions = sessionParams.PricingExceptions; Cycle=sessionParams.Cycle; CashBalance=sessionParams.CashBalance; NonTradeableCash=sessionParams.NonTradeableCash; HedgeCashBalance=sessionParams.HedgeCashBalance; 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)); MGSHSessionParams sessionParams=new MGSHSessionParams(); sessionParams.LastUpdated=Today(); sessionParams.TradeDate=TradeDate; sessionParams.StartDate=StartDate; sessionParams.AnalysisDate=AnalysisDate; sessionParams.Configuration=Configuration; sessionParams.ActivePositions=ActivePositions; sessionParams.AllPositions=AllPositions; sessionParams.HedgePositions=HedgePositions; sessionParams.Cycle=Cycle; sessionParams.StopLimits=StopLimits; sessionParams.PricingExceptions=PricingExceptions; sessionParams.CashBalance=CashBalance; sessionParams.NonTradeableCash=NonTradeableCash; sessionParams.HedgeCashBalance=HedgeCashBalance; MGSHSessionManager.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)); MGSHSessionParams sessionParams=new MGSHSessionParams(); sessionParams.LastUpdated=Today(); sessionParams.TradeDate=TradeDate; sessionParams.StartDate=StartDate; sessionParams.AnalysisDate=AnalysisDate; sessionParams.Configuration=Configuration; sessionParams.ActivePositions=ActivePositions; sessionParams.AllPositions=AllPositions; sessionParams.HedgePositions=HedgePositions; sessionParams.Cycle=Cycle; sessionParams.StopLimits=StopLimits; sessionParams.PricingExceptions=PricingExceptions; sessionParams.CashBalance=CashBalance; sessionParams.NonTradeableCash=NonTradeableCash; sessionParams.HedgeCashBalance=HedgeCashBalance; return MGSHSessionManager.SaveSession(sessionParams,backupFileName); } } }