752 lines
42 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|