1375 lines
78 KiB
C#
1375 lines
78 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using MarketData.MarketDataModel;
|
|
using MarketData.DataAccess;
|
|
using MarketData.Utils;
|
|
using System.Linq;
|
|
using MarketData.Cache;
|
|
using MarketData.Generator.Model;
|
|
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<DateTime> 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 *****************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="paramStartDate">{ORIGINAL START DATE}</param>
|
|
/// <param name="paramAnalysisDate">{TODAY}</param>
|
|
/// <param name="paramPathSessionFileName">The session file name</param>
|
|
/// <param name="configuration">The configuration</param>
|
|
/// <returns></returns>
|
|
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<HoldingPeriod;slotIndex++)
|
|
{
|
|
if(!ActivePositions.ContainsKey(slotIndex)||0==ActivePositions[slotIndex].Count())continue;
|
|
MGSHPositions 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();
|
|
}
|
|
DisplayStatistics(sessionParams);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
|
|
AllPositions=new MGSHPositions((from MGSHPosition position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
|
|
AllPositions.Display();
|
|
DisplayBalance();
|
|
backTestResult.Success=true;
|
|
backTestResult.CashBalance=CashBalance;
|
|
GBPriceCache.GetInstance().Dispose();
|
|
return backTestResult;
|
|
}
|
|
/// *******************************************************************************************************************************************************
|
|
/// ************************************************** S L O T S A L E S A N D P U R C H A S E S *****************************************************
|
|
/// *******************************************************************************************************************************************************
|
|
public void BuySlotPositions(int slotIndex)
|
|
{
|
|
MGSHPositions positions = null;
|
|
positions=BuyPositions(slotIndex, TradeDate, AnalysisDate, CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count), SymbolsHeld(TradeDate));
|
|
MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
|
|
MDTrace.WriteLine(LogLevel.DEBUG, $"SLOT:{slotIndex}");
|
|
if(CashBalance-positions.Exposure<0.00)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
|
|
return;
|
|
}
|
|
ActivePositions.Add(slotIndex,positions);
|
|
CashBalance-=positions.Exposure;
|
|
SetInitialStopLimitsForNewPositions(TradeDate, positions);
|
|
positions.Display();
|
|
UpdateStopLimitsForActivePositions(TradeDate);
|
|
DisplayBalance();
|
|
}
|
|
|
|
public void SellAndBuySlotPositions(int slotIndex)
|
|
{
|
|
MGSHPositions slotPositions=ActivePositions[slotIndex];
|
|
if(!Configuration.KeepSlotPositions) // if we are not configured to KeepSlotPositions then don't sell anything just buy to the max positions allowed for the slot
|
|
{
|
|
SellPositions(slotPositions,TradeDate);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
|
|
slotPositions.Display();
|
|
AllPositions.Add(slotPositions);
|
|
CashBalance+=slotPositions.MarketValue;
|
|
ActivePositions[slotIndex].Clear();
|
|
}
|
|
DisplayBalance();
|
|
|
|
int positionsToFill = MaxPositions-slotPositions.Count;
|
|
double cashAllocation = (CashBalance / ((HoldingPeriod * MaxPositions) - ActivePositions.GetCount()))*positionsToFill; // split the cash between the total positions we can own less the number of positions we have
|
|
MGSHPositions positions = null;
|
|
positions=BuyPositions(slotIndex, TradeDate,AnalysisDate,cashAllocation,SymbolsHeld(TradeDate), positionsToFill);
|
|
if(CashBalance-positions.Exposure<=0.00)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
|
|
}
|
|
else
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
|
|
if(!ActivePositions.ContainsKey(slotIndex))ActivePositions.Add(slotIndex, positions);
|
|
else ActivePositions[slotIndex].AddRange(positions);
|
|
CashBalance-=positions.Exposure;
|
|
SetInitialStopLimitsForNewPositions(TradeDate, positions);
|
|
ActivePositions[slotIndex].Display();
|
|
}
|
|
UpdateStopLimitsForActivePositions(TradeDate);
|
|
DisplayBalance();
|
|
}
|
|
|
|
/// *******************************************************************************************************************************************************
|
|
/// *******************************************************************************************************************************************************
|
|
/// *******************************************************************************************************************************************************
|
|
|
|
|
|
// ********************************************************************************************************************************************************
|
|
// ********************************************************** P O S I T I O N M A N A G E M E N T *******************************************************
|
|
// ********************************************************************************************************************************************************
|
|
/// <summary>
|
|
/// Entry point for daily runs. Make sure for daily runs that startDate=endDate=Trading Date after market close
|
|
/// The system does not process endDate. (i.e.) startDate < endDate
|
|
/// </summary>
|
|
/// <param name="startDate">{ORIGINAL START DATE}</param>
|
|
/// <param name="endDate">{TODAY}</param>
|
|
/// <param name="paramPathSessionFileName">The session file name</param>
|
|
/// <returns></returns>
|
|
public MGSHBacktestResult UpdateDaily(DateTime startDate, DateTime endDate, 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 *******************************************************
|
|
// *******************************************************************************************************************************************************
|
|
/// <summary>
|
|
/// ManageHedgedPositions
|
|
/// </summary>
|
|
/// <param name="analysisDate"></param>
|
|
/// <returns>True if any changed were made otherwise false</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to purchase the hedge with the supplied cash allocation
|
|
/// </summary>
|
|
/// <param name="analysisDate"></param>
|
|
/// <param name="cashAllocation"></param>
|
|
/// <returns>Position on success otherwise null</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manages open hedge position by checking and adjusting stop price and determining if the hedge should exit
|
|
/// </summary>
|
|
/// <param name="analysisDate"></param>
|
|
/// <returns></returns>
|
|
public bool ManageActiveHedgePositions(DateTime analysisDate)
|
|
{
|
|
HedgeManager hedgeManager = new HedgeManager(){HedgeShortSymbol=Configuration.HedgeShortSymbol,Verbose=false};
|
|
List<MGSHPosition> closedPositions = new List<MGSHPosition>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates stop limits for active positions and closes positions that have stop limit violations
|
|
/// </summary>
|
|
/// <returns>true if any positions were closed, otherwise returns false</returns>
|
|
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<int> slotKeys = new List<int>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates stop prices for slot positons to see if we need to perform updates
|
|
/// </summary>
|
|
/// <returns>true if changes were made otherwise false</returns>
|
|
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<String> SymbolsHeld(DateTime tradeDate)
|
|
{
|
|
List<String> symbolsHeld=ActivePositions.SymbolsHeld();
|
|
if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld;
|
|
if(null == symbolsHeld)symbolsHeld=new List<String>();
|
|
PortfolioTrades portfolioTrades=PortfolioDA.GetOpenTradesAsOf(tradeDate);
|
|
if(null == portfolioTrades || 0==portfolioTrades.Count)return symbolsHeld;
|
|
symbolsHeld.AddRange(portfolioTrades.Symbols.Distinct());
|
|
return symbolsHeld;
|
|
}
|
|
|
|
// **********************************************************************************************************************************************************
|
|
// **************************************************************** G E T E X P O S U R E / M A R K E T V A L U E *****************************************
|
|
// **********************************************************************************************************************************************************
|
|
public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
|
|
{
|
|
int count=ActivePositions.Count;
|
|
double marketValue=0.00;
|
|
double exposure=0.00;
|
|
RealtimeGainLoss gainLoss=new RealtimeGainLoss();
|
|
|
|
for(int slotIndex=0;slotIndex<count;slotIndex++)
|
|
{
|
|
List<MGSHPosition> 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<count;slotIndex++)
|
|
{
|
|
List<MGSHPosition> 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<String> symbolsHeld, double maxPositions=double.NaN)
|
|
{
|
|
MGSHPositions positions = new MGSHPositions();
|
|
if(double.IsNaN(maxPositions))maxPositions=MaxPositions;
|
|
if(0==maxPositions)return positions;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"BUY POSITIONS: 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buys the benchmark mode symbol (BenchmarModeSymbol) if the configuration is set to BenchmarkMode (BenchmarkMode)
|
|
/// </summary>
|
|
/// <returns>Positions</returns>
|
|
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<int> keys = new List<int>(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);
|
|
}
|
|
}
|
|
}
|