Files
marketdata/MarketDataLib/Generator/CMMomentum/CMBacktest.cs
2024-02-22 14:52:53 -05:00

752 lines
42 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
using MarketData.Numerical;
using System.Threading;
using MarketData.Integration;
using MarketData.Generator.Momentum;
using MarketData.Cache;
using MarketData.Generator.Model;
using MarketData.CNNProcessing;
namespace MarketData.Generator.CMMomentum
{
public class CMBacktestResult
{
public CMBacktestResult()
{
}
public double CashBalance { get; set; }
public bool Success { get; set; }
}
// ********************************************************************************************************************************************************
public class CMMomentumBacktest
{
private double CashBalance { get; set; }
private double NonTradeableCash{get;set;}
public CMParams Parameters { get; set; }
private int HoldingPeriod { get { return Parameters.HoldingPeriod; } }
private int MaxPositions { get { return Parameters.MaxPositions; } }
private List<String> NoTradeSymbols { get { return Utility.ToList(Parameters.NoTradeSymbols); } }
private ActivePositions ActivePositions { get; set; }
private SMSNotifications SMSNotifications { get; set; }
private Positions AllPositions { get; set; }
private int Cycle { get; set; }
private DateTime TradeDate { get; set; }
private DateTime StartDate { get; set; }
private DateTime AnalysisDate { get; set; }
private String PathSessionFileName { get; set; }
// ******************************************************************************************************************************************************
//********************************************************** U P D A T E S E S S I O N P R I C E *****************************************************
// ******************************************************************************************************************************************************
public void DisplayGainLoss(String paramPathSessionFileName)
{
Profiler profiler=new Profiler();
ModelPerformanceSeries performanceSeries=GetModelPerformance(paramPathSessionFileName);
if(null==performanceSeries) return;
MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDOD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
foreach(ModelPerformanceItem modelPerformanceItem in performanceSeries)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
modelPerformanceItem.Date.ToShortDateString(),
Utility.FormatCurrency(modelPerformanceItem.Exposure),
Utility.FormatCurrency(modelPerformanceItem.MarketValue),
Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
Utility.FormatCurrency(modelPerformanceItem.GainLoss),
Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
Utility.FormatNumber(modelPerformanceItem.R,4),
Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
Utility.FormatNumber(modelPerformanceItem.CumProd,4),
Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4),
modelPerformanceItem.ClosedPositions));
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End()));
}
public static ModelPerformanceSeries GetModelPerformance(String paramPathSessionFileName)
{
try
{
CMSessionParams sessionParams=CMSessionManager.RestoreSession(paramPathSessionFileName);
return GetModelPerformance(sessionParams);
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
public static ModelStatistics GetModelStatistics(CMSessionParams sessionParams)
{
ModelStatistics modelStatistics=new ModelStatistics();
try
{
if(null==sessionParams||null==sessionParams.AllPositions||0==sessionParams.AllPositions.Count) return modelStatistics;
double totalTrades=sessionParams.AllPositions.Count;
double winningTrades=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Count();
double losingTrades=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Count();
double averageWinningTrade=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Average(x => x.GainLossPcnt)*100.00;
double averageLosingTrade=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Average(x => x.GainLossPcnt)*100.00;
double percentWinningTrades=(winningTrades/(double)sessionParams.AllPositions.Count)*100.00;
double percentLosingTrades=(losingTrades/(double)sessionParams.AllPositions.Count)*100.00;
double expectation=(percentWinningTrades*averageWinningTrade)/(percentLosingTrades*Math.Abs(averageLosingTrade));
modelStatistics.TotalTrades=(long)totalTrades;
modelStatistics.WinningTrades=(long)winningTrades;
modelStatistics.LosingTrades=(long)losingTrades;
modelStatistics.AverageWinningTradePercentGain=averageWinningTrade;
modelStatistics.AverageLosingTradePercentLoss=averageLosingTrade;
modelStatistics.WinningTradesPercent=percentWinningTrades;
modelStatistics.LosingTradesPercent=percentLosingTrades;
modelStatistics.Expectancy=expectation;
return modelStatistics;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return modelStatistics;
}
}
public static ModelPerformanceSeries GetModelPerformance(CMSessionParams sessionParams)
{
Profiler profiler=new Profiler();
ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
DateGenerator dateGenerator=new DateGenerator();
try
{
if(null==sessionParams)return null;
MarketData.Generator.CMMomentum.Positions combinedPositions=sessionParams.GetCombinedPositions();
// Fix purchase date/sell date fall on weekend
foreach(MarketData.Generator.CMMomentum.Position position in combinedPositions)
{
if(dateGenerator.IsWeekend(position.PurchaseDate))
{
while(true)
{
position.PurchaseDate=dateGenerator.GetPrevBusinessDay(position.PurchaseDate);
if(!HolidayDA.IsMarketHoliday(position.PurchaseDate)) break;
}
}
if(dateGenerator.IsWeekend(position.SellDate))
{
while(true)
{
position.SellDate=dateGenerator.GetNextBusinessDay(position.SellDate);
if(!HolidayDA.IsMarketHoliday(position.SellDate)) break;
}
}
}
// ********************************************************
DateTime minDate=combinedPositions.Min(x => x.PurchaseDate);
DateTime maxDate=PricingDA.GetLatestDate();
double prevGainLoss=double.NaN;
LocalPriceCache.GetInstance().RemoveDate(maxDate);
List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(minDate,maxDate);
foreach(DateTime currentDate in historicalDates)
{
MarketData.Generator.CMMomentum.Positions openPositions=new MarketData.Generator.CMMomentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
MarketData.Generator.CMMomentum.Positions closedPositions=new MarketData.Generator.CMMomentum.Positions(combinedPositions.Where(x => (!Utility.IsEpoch(x.SellDate)&&x.SellDate.Equals(currentDate))).ToList());
if(0==openPositions.Count&&0==closedPositions.Count) continue;
double gainLoss=0.00;
double gainLossClosedPositions=0.00;
double exposure=0.00;
double marketValue=0.00;
if(HolidayDA.IsMarketHoliday(currentDate)) continue;
ModelPerformanceItem performanceItem=new ModelPerformanceItem();
foreach(MarketData.Generator.CMMomentum.Position openPosition in openPositions)
{
exposure+=openPosition.Shares*openPosition.PurchasePrice;
if(!LocalPriceCache.GetInstance().ContainsPrice(openPosition.Symbol,currentDate))
{
Prices prices=PricingDA.GetPricesForward(openPosition.Symbol,currentDate,90);
LocalPriceCache.GetInstance().Add(prices);
}
Price price=LocalPriceCache.GetInstance().GetPrice(openPosition.Symbol,currentDate);
if(null==price)
{
price=PricingDA.GetPrice(openPosition.Symbol,currentDate);
if(null==price)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No price for {0} on {1}",openPosition.Symbol,currentDate.ToShortDateString()));
return null;
}
LocalPriceCache.GetInstance().Add(price);
}
gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
marketValue+=(price.Close*openPosition.Shares);
}
foreach(MarketData.Generator.CMMomentum.Position closedPosition in closedPositions)
{
double gainLossPosition=(closedPosition.CurrentPrice*closedPosition.Shares)-(closedPosition.PurchasePrice*closedPosition.Shares);
gainLossClosedPositions+=gainLossPosition;
}
performanceItem.Date=currentDate;
performanceItem.Exposure=exposure;
performanceItem.MarketValue=marketValue;
performanceItem.GainLossDOD=double.IsNaN(prevGainLoss)?gainLoss:(gainLoss-prevGainLoss)+gainLossClosedPositions;
performanceItem.GainLoss=gainLoss+gainLossClosedPositions;
performanceItem.ClosedPositions=closedPositions.Count>0?true:false;
performanceSeries.Add(performanceItem);
prevGainLoss=gainLoss;
}
performanceSeries.CalculatePerformance();
return performanceSeries;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
finally
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End()));
}
}
// ******************************************************************************************************************************************************
//********************************************************** U P D A T E S E S S I O N P R I C E *****************************************************
// ******************************************************************************************************************************************************
public void UpdateSessionPrice(String symbol, DateTime tradeDate, double price, String pathSessionFileName)
{
CMSessionParams sessionParams = null;
if (null == symbol || Utility.IsEpoch(tradeDate) || null == pathSessionFileName)
{
MDTrace.WriteLine(LogLevel.DEBUG, "UpdateSessionPrice. One or more parameters are invalid.");
return;
}
PathSessionFileName = pathSessionFileName;
if (null == (sessionParams = RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFileName));
return;
}
List<int> keys = new List<int>(ActivePositions.Keys);
bool hasChanges = false;
foreach (int key in keys)
{
Positions positions = ActivePositions[key];
foreach (Position position in positions)
{
if (!position.Symbol.Equals(symbol) || !position.PurchaseDate.Date.Equals(tradeDate.Date)) continue;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Changing purchase price for {0} on {1} from {2} to {3}",
position.Symbol,
Utility.DateTimeToStringMMHDDHYYYY(position.PurchaseDate),
Utility.FormatCurrency(position.PurchasePrice),
Utility.FormatCurrency(price)));
position.PurchasePrice = price;
if (!hasChanges) hasChanges = true;
}
}
if (hasChanges) SaveSession();
ActivePositions.Display();
DisplayBalanceFromPositions();
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("StartDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("TradeDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("AnalysisDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Next Slot:{0}", Cycle));
}
// ******************************************************************************************************************************************************
//************************************************************** D I S P L A Y S E S S I O N *****************************************************
// ******************************************************************************************************************************************************
public void DisplaySession(String paramPathSessionFileName)
{
if (null == paramPathSessionFileName) return;
PathSessionFileName = paramPathSessionFileName;
CMSessionParams sessionParams = null;
if (null == (sessionParams = RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", paramPathSessionFileName));
return;
}
Console.WriteLine(String.Format("SessionFile:{0} Last Updated:{1}", paramPathSessionFileName, sessionParams.LastUpdated));
Parameters.DisplayConfiguration();
MDTrace.WriteLine(LogLevel.DEBUG, "************** A L L P O S I T I O N S *************");
AllPositions = new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
AllPositions.Display();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P G A I N E R S *************");
AllPositions.DisplayTopFive();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P L O S E R S *************");
AllPositions.DisplayBottomFive();
MDTrace.WriteLine(LogLevel.DEBUG, "************** A C T I V E P O S I T I O N S *************");
ActivePositions.Display();
DisplayBalanceFromPositions();
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("StartDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("TradeDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("AnalysisDate:{0}", Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Next Slot:{0}", Cycle));
double gainLoss = 0.00;
if (ActivePositions.Count > 0 && AllPositions.Count > 0)
{
RealtimeGainLoss realtimeGainLoss = GetRealtimeGainLoss(PricingDA.GetLatestDate());
gainLoss = AllPositions.Sum(x => x.GainLoss);
if (null != realtimeGainLoss) gainLoss += realtimeGainLoss.GainLoss;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else if (ActivePositions.Count > 0)
{
RealtimeGainLoss realtimeGainLoss = GetRealtimeGainLoss(PricingDA.GetLatestDate());
if (null != realtimeGainLoss) gainLoss = realtimeGainLoss.GainLoss;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Active Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else if (AllPositions.Count > 0)
{
gainLoss = AllPositions.Sum(x => x.GainLoss);
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Total Active Gain/Loss {0}", Utility.FormatCurrency(gainLoss)));
}
else MDTrace.WriteLine(LogLevel.DEBUG,"There does not appear to be any trade data in this file.");
}
// ******************************************************************************************************************************************************
//******************************************************* L I Q U I D A T E A L L P O S I T I O N S ***********************************************
// ******************************************************************************************************************************************************
public void CMLiquidate(String pathSessionFile, DateTime? tradeDate)
{
if (null == pathSessionFile) return;
CMSessionParams sessionParams = null;
PathSessionFileName = pathSessionFile;
if (null == (sessionParams = RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
return;
}
MDTrace.WriteLine(LogLevel.DEBUG, "************** L I Q U I D A T E P O S I T I O N S *************");
if (null == ActivePositions || 0 == ActivePositions.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("No active positions in file {0}", pathSessionFile));
return;
}
if (null == tradeDate) tradeDate = PricingDA.GetLatestDate(ActivePositions.GetSymbols());
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Trade date:{0}", Utility.DateTimeToStringMMHDDHYYYY(tradeDate.Value)));
if (null == (sessionParams = RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
return;
}
for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
{
Positions slotPositions = ActivePositions[slotIndex];
SellPositions(slotPositions, tradeDate.Value);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
CashBalance += slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
}
GBPriceCache.GetInstance().Dispose();
SaveSession();
}
// ******************************************************************************************************************************************************
// ****************************************************************** B A C K T E S T *****************************************************************
// ******************************************************************************************************************************************************
// Ideally, startDate should be November,February,May,August
// paramStartDate is startDate
// paramAnalysisDate is endDate
public CMBacktestResult PerformBacktest(DateTime paramStartDate, DateTime paramAnalysisDate, String paramPathSessionFileName, CMParams cmParams)
{
CMBacktestResult backTestResult = new CMBacktestResult();
DateGenerator dateGenerator = new DateGenerator();
Parameters = cmParams;
CashBalance = Parameters.InitialCash;
ActivePositions = new ActivePositions();
AllPositions = new Positions();
StartDate = paramStartDate;
TradeDate = paramStartDate;
AnalysisDate = paramAnalysisDate;
PathSessionFileName = paramPathSessionFileName;
SMSNotifications = new SMSNotifications();
CMSessionParams sessionParams = null;
Cycle = 0;
if (AnalysisDate.Date > Today().Date) return backTestResult;
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = Today();
TradeDate = dateGenerator.GetCurrentMonthEnd(StartDate);
if (TradeDate > AnalysisDate)
{
int startMonth = StartDate.Month;
TimeSpan timeSpan = new TimeSpan();
if ((new int[] { 12, 3, 6, 9 }).Any(x => x.Equals(startMonth))) timeSpan = new TimeSpan(30, 0, 0, 0);
else if ((new int[] { 1, 4, 7, 10 }).Any(x => x.Equals(startMonth))) timeSpan = new TimeSpan(60, 0, 0, 0);
else if ((new int[] { 2, 5, 8, 11 }).Any(x => x.Equals(startMonth))) timeSpan = new TimeSpan(90, 0, 0, 0);
StartDate = StartDate - timeSpan;
TradeDate = dateGenerator.GetCurrentMonthEnd(StartDate);
}
if (null != PathSessionFileName) sessionParams = RestoreSession();
if (null != sessionParams)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Using session file {0}, Last updated {1}", paramPathSessionFileName, sessionParams.LastUpdated));
}
Parameters.DisplayConfiguration();
CheckCNNServerStatus();
DisplayBalance();
while (true)
{
if (TradeDate > AnalysisDate) break;
int slotIndex = (int)(((double)Cycle) % ((double)(HoldingPeriod)));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("TRADE DATE {0} , ANALYSIS DATE {1}", Utility.DateTimeToStringMMHDDHYYYY(TradeDate), Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
if (!ActivePositions.ContainsKey(slotIndex))
{
Positions positions=BuyPositions(slotIndex,TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld());
MDTrace.WriteLine(LogLevel.DEBUG,"******************** B U Y ********************");
positions.Display();
if (CashBalance - positions.Exposure < 0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("********** Insufficient funds to make additional purchases (1). Available cash: {0}. Requested Exposure: {1}",Utility.FormatCurrency(CashBalance),Utility.FormatCurrency(positions.Exposure)));
break;
}
ActivePositions.Add(slotIndex, positions);
CashBalance -= positions.Exposure;
DisplayBalance();
}
else
{
Positions slotPositions = ActivePositions[slotIndex];
SellPositions(slotPositions, TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
CashBalance += slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
DisplayBalance();
double cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / HoldingPeriod); // Even out the cash allocation so that no one slot eats up all the cash
Positions positions=BuyPositions(slotIndex,TradeDate,AnalysisDate,cashAllocation,SymbolsHeld());
MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
positions.Display();
if (CashBalance - positions.Exposure <= 0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("********** Insufficient funds to make additional purchases (2). Available cash: {0}. Requested Exposure: {1}", Utility.FormatCurrency(CashBalance), Utility.FormatCurrency(positions.Exposure)));
break;
}
ActivePositions[slotIndex] = positions;
CashBalance -= positions.Exposure;
DisplayBalance();
}
Cycle++;
TradeDate = dateGenerator.GetNextMonthEnd(TradeDate);
if (TradeDate > AnalysisDate) break;
} // while (true)
MDTrace.WriteLine(LogLevel.DEBUG, "RUN COMPLETE.");
DisplayBalanceFromPositions();
if (null != PathSessionFileName) SaveSession();
for (int slotIndex = 0; slotIndex < HoldingPeriod; slotIndex++)
{
if (!ActivePositions.ContainsKey(slotIndex) || 0 == ActivePositions[slotIndex].Count()) continue;
Positions slotPositions = ActivePositions[slotIndex];
SellPositions(slotPositions, AnalysisDate);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L ********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
CashBalance += slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
}
MDTrace.WriteLine(LogLevel.DEBUG, "************** A L L P O S I T I O N S *************");
AllPositions = new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
AllPositions.Display();
DisplayBalance();
backTestResult.Success = true;
backTestResult.CashBalance = CashBalance;
GBPriceCache.GetInstance().Dispose();
return backTestResult;
}
// **********************************************************************************************************************************************************
public List<String> SymbolsHeld()
{
return ActivePositions.GetSymbols();
}
// ***************************************************************************************************************************************************
// **************************************************************** S E L L P O S I T I O N S *****************************************************
// ***************************************************************************************************************************************************
private void SellPositions(Positions positions, DateTime sellDate)
{
DateGenerator dateGenerator = new DateGenerator();
foreach (Position position in positions)
{
SellPosition(position, sellDate);
}
}
private void SellPosition(Position position, DateTime sellDate)
{
Price price = GetPrice(position.Symbol,sellDate);
position.SellDate = sellDate;
if (null == price)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("**********Cannot locate a price for {0} on {1}**********", position.Symbol, Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
position.CurrentPrice = position.PurchasePrice;
}
else
{
position.SellDate = sellDate;
position.CurrentPrice = price.Close;
}
}
// ***************************************************************************************************************************************************
// **************************************************************** B U Y P O S I T I O N S *****************************************************
// ***************************************************************************************************************************************************
private Positions BuyPositions(int slotIndex,DateTime tradeDate, DateTime analysisDate, double cash, List<String> symbolsHeld)
{
DateGenerator dateGenerator = new DateGenerator();
Positions positions = new Positions();
int positionCount = 0;
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BUY POSITIONS (SlotIndex:{0} TradeDate:{1} AnalysisDate:{2} Cash Allocation:{3})",slotIndex,tradeDate.ToShortDateString(),analysisDate.ToShortDateString(),Utility.FormatCurrency(cash)));
CMGeneratorResult cmGeneratorResult = CMMomentumGenerator.GenerateCMCandidates(tradeDate, analysisDate, Parameters, symbolsHeld);
if (!cmGeneratorResult.Success)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("GenerateCMCandidates failed with message {0}",cmGeneratorResult.LastMessage));
return positions;
}
for (int index = 0; index < cmGeneratorResult.CMCandidates.Count;index++)
{
CMCandidate cmCandidate = cmGeneratorResult.CMCandidates[index];
Price price = GetPrice(cmCandidate.Symbol, tradeDate);
if (null == price)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", cmCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
continue;
}
Position position = new Position();
position.Symbol = cmCandidate.Symbol;
position.Beta = cmCandidate.Beta;
position.BetaMonths = cmCandidate.BetaMonths;
position.SharpeRatio = cmCandidate.SharpeRatio;
position.PurchaseDate = tradeDate;
position.PurchasePrice = price.Close;
position.CurrentPrice = double.NaN;
position.Score = cmCandidate.Score;
position.CNNPrediction = cmCandidate.CNNPrediction;
positions.Add(position);
positionCount++;
if (positionCount >= MaxPositions) break;
}
positions=PerformPositionSizing(positions,cash,tradeDate,cmGeneratorResult.InFallback);
if(Parameters.UseMaxPositionBucketWeight)
{
positions=AdjustPositionWeights(positions);
}
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("BUY POSITIONS (SlotIndex:{0} TradeDate:{1} AnalysisDate:{2} Total Exposure:{3})", slotIndex,tradeDate.ToShortDateString(), analysisDate.ToShortDateString(), Utility.FormatCurrency(positions.Sum(x=>x.Exposure))));
return positions;
}
// ***************************************************************************************************************************************************
// ************************************************************************ G E T P R I C E ******************************************************
// ***************************************************************************************************************************************************
private Price GetPrice(String symbol, DateTime priceDate)
{
DateGenerator dateGenerator = new DateGenerator();
priceDate = dateGenerator.GetPrevBusinessDay(priceDate);
Price price = GBPriceCache.GetInstance().GetPrice(symbol, priceDate);
if (null == price) price = GBPriceCache.GetInstance().GetPrice(symbol, dateGenerator.FindPrevBusinessDay(priceDate));
return price;
}
// ***************************************************************************************************************************************************
// ***************************************************************** P O S I T I O N S I Z I N G **************************************************
// ***************************************************************************************************************************************************
private Positions PerformPositionSizing(Positions positions, double cash, DateTime tradeDate,bool inFallback=false)
{
if (null == positions || 0 == positions.Count) return positions;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("PERFORM POSITION SIZING Positions:{0} Cash:{1}", positions.Count, Utility.FormatCurrency(cash)));
if (inFallback) positions = new Positions(positions.Take(1).ToList()); // if processing fallbacks then make sure we've just got a single holding
if (inFallback && !double.IsNaN(Parameters.FallbackMaxAlloc))
{
cash = cash < Parameters.FallbackMaxAlloc ? cash : Parameters.FallbackMaxAlloc;
Position position = positions[0];
position.Weight = 1.00;
position.TargetBetaOverBeta = Parameters.TargetBeta / position.Beta;
position.RiskAdjustedWeight = 1.00;
position.RiskAdjustedAllocation = cash * position.RiskAdjustedWeight;
if (position.RiskAdjustedAllocation < position.PurchasePrice) position.RiskAdjustedAllocation = position.PurchasePrice;
position.Shares = Math.Floor(position.RiskAdjustedAllocation / position.PurchasePrice);
return positions;
}
double totalTargetBetaOverBeta = 0.00;
double maxPositions = positions.Count;
foreach (Position position in positions)
{
position.Weight = 1 / maxPositions;
position.TargetBetaOverBeta = Parameters.TargetBeta / position.Beta;
}
totalTargetBetaOverBeta = (from Position position in positions select position.TargetBetaOverBeta).Sum();
foreach (Position position in positions)
{
position.RiskAdjustedWeight = position.TargetBetaOverBeta/totalTargetBetaOverBeta;
position.RiskAdjustedAllocation = cash * position.RiskAdjustedWeight;
if (position.RiskAdjustedAllocation < position.PurchasePrice) position.RiskAdjustedAllocation = position.PurchasePrice;
position.Shares = Math.Floor(position.RiskAdjustedAllocation / position.PurchasePrice);
}
if (positions.Exposure > cash) positions = new Positions(positions.Where(x => x.Shares > 1).ToList());
return positions;
}
// ***************************************************************************************************************************************************
// ***************************************************************** P O S I T I O N W E I G H T S ************************************************
// ***************************************************************************************************************************************************
// This is a post-stage to the above PositiionSizing. It works by examing the weights of the positions in the bucket and ensuring that no position weight is larger than UseMaxPositionBucketWeightMaxWeight.
// If an overweight position is located then it's exposure is reduced to UseMaxPositionBucketWeightMaxWeight and the remaining positions divide the excess exposure evenly.
// This prevents any single position from eclipsing the other positions in the bucket.
private Positions AdjustPositionWeights(Positions positions)
{
if(null==positions || positions.Count<=1 || false==Parameters.UseMaxPositionBucketWeight) return positions;
double totalExposure=positions.Exposure;
Position overweightPosition=positions.Where(x=>x.Exposure/totalExposure>Parameters.UseMaxPositionBucketWeightMaxWeight).FirstOrDefault();
if(null==overweightPosition)return positions;
double weightToRedistribute=(overweightPosition.Exposure/totalExposure)-Parameters.UseMaxPositionBucketWeightMaxWeight;
foreach(Position position in positions)
{
double newWeight=double.NaN;
if(position==overweightPosition)
{
newWeight=(position.Exposure/totalExposure)-weightToRedistribute;
}
else
{
newWeight=(position.Exposure/totalExposure)+(weightToRedistribute/(double) (positions.Count-1));
}
position.Shares=Math.Floor((newWeight*totalExposure)/position.PurchasePrice);
}
return positions;
}
// **********************************************************************************************************************************************************
// **************************************************************** G E T E X P O S U R E / M A R K E T V A L U E*****************************************
// **********************************************************************************************************************************************************
public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
{
int count = ActivePositions.Count;
double marketValue = 0.00;
double exposure = 0.00;
RealtimeGainLoss gainLoss = new RealtimeGainLoss();
for (int slotIndex = 0; slotIndex < count; slotIndex++)
{
List<Position> positions = ActivePositions[slotIndex];
if (null == positions || 0 == positions.Count) continue;
foreach (Position position in positions)
{
Price price = PricingDA.GetPrice(position.Symbol, tradeDate);
if (null == price) { MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot price {0} on {1}", position.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate))); continue; }
position.CurrentPrice = price.Close;
}
}
for (int slotIndex = 0; slotIndex < count; slotIndex++)
{
List<Position> positions = ActivePositions[slotIndex];
if (null == positions || 0 == positions.Count) continue;
exposure += (from Position position in positions select position.Exposure).Sum();
marketValue += (from Position position in positions select position.MarketValue).Sum();
}
gainLoss.Exposure = exposure;
gainLoss.MarketValue = marketValue;
return gainLoss;
}
// *********************************************************************************************************************************************************************
// *********************************************************************************************************************************************************************
// *********************************************************************************************************************************************************************
private void DisplayBalance()
{
MDTrace.WriteLine(LogLevel.DEBUG, "EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
if (!double.IsNaN(ActivePositions.GetMarketValue()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue() + CashBalance))));
}
else
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure() + CashBalance))));
}
}
private void DisplayBalanceFromPositions()
{
MDTrace.WriteLine(LogLevel.DEBUG, "EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetGainLoss())),
Utility.AddQuotes(Utility.FormatPercent(ActivePositions.GetGainLossPercent())),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue() + CashBalance))));
}
private void DisplayBalance(RealtimeGainLoss gainLoss)
{
MDTrace.WriteLine(LogLevel.DEBUG, "EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.GainLoss)),
Utility.AddQuotes(Utility.FormatPercent(gainLoss.GainLossPercent)),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.MarketValue + CashBalance))));
}
// ****************************************************************************************************************************************
// ************************************************************* C O N T R O L T O D A Y ***********************************************
// ****************************************************************************************************************************************
public DateTime Today()
{
return DateTime.Now;
}
// ****************************************************************************************************************************************
// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
// ****************************************************************************************************************************************
public CMSessionParams RestoreSession()
{
try
{
CMSessionManager sessionManager = new CMSessionManager();
if (!CMSessionManager.SessionAvailable(PathSessionFileName)) return null;
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Restoring session from '{0}'", PathSessionFileName));
CMSessionParams sessionParams = CMSessionManager.RestoreSession(PathSessionFileName);
TradeDate = sessionParams.TradeDate;
if (TradeDate.Date < AnalysisDate.Date) TradeDate = AnalysisDate;
StartDate = sessionParams.StartDate;
Parameters = sessionParams.CMParams;
ActivePositions = sessionParams.ActivePositions;
AllPositions = sessionParams.AllPositions;
Cycle = sessionParams.Cycle;
CashBalance = sessionParams.CashBalance;
NonTradeableCash = sessionParams.NonTradeableCash;
return sessionParams;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
return null;
}
}
public void SaveSession()
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", PathSessionFileName));
CMSessionParams sessionParams = new CMSessionParams();
CMSessionManager sessionManager = new CMSessionManager();
sessionParams.LastUpdated = Today();
sessionParams.TradeDate = TradeDate;
sessionParams.StartDate = StartDate;
sessionParams.AnalysisDate = AnalysisDate;
sessionParams.CMParams = Parameters;
sessionParams.ActivePositions = ActivePositions;
sessionParams.AllPositions = AllPositions;
sessionParams.Cycle = Cycle;
sessionParams.CashBalance = CashBalance;
sessionParams.NonTradeableCash = NonTradeableCash;
sessionManager.SaveSession(sessionParams, PathSessionFileName);
}
private void CheckCNNServerStatus()
{
if(Parameters.UseCNN) // ping the server here so that we don't have to do it for each request
{
CNNClient cnnClient=new CNNClient(Parameters.UseCNNHost);
if(!cnnClient.Ping())
{
String strMessage=String.Format("******* UseCNN=true but the server is not responding. {0} *******",Parameters.UseCNNHost);
Console.Beep(800,200);
throw new Exception(strMessage);
}
}
}
}
}