1727 lines
102 KiB
C#
1727 lines
102 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Windows;
|
|
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.Cache;
|
|
using System.IO;
|
|
using MarketData.Generator.Indicators;
|
|
using MarketData.Generator.Model;
|
|
using StopLimit=MarketData.Generator.Model.StopLimit;
|
|
using StopLimits=MarketData.Generator.Model.StopLimits;
|
|
using MarketData.Generator.ModelGenerators;
|
|
using Axiom.Interpreter;
|
|
|
|
namespace MarketData.Generator.CMTrend
|
|
{
|
|
public class CMTTrendModel
|
|
{
|
|
public enum RunType { BacktestResult, Daily };
|
|
private double CashBalance { get; set; }
|
|
private double NonTradeableCash{get;set;}
|
|
public CMTParams Parameters { get; set; }
|
|
private int MaxDailyPositions { get { return Parameters.MaxDailyPositions; } }
|
|
private int MaxOpenPositions { get { return Parameters.MaxOpenPositions; } }
|
|
private List<String> NoTradeSymbols { get { return Utility.ToList(Parameters.NoTradeSymbols); } }
|
|
private int MaxPricingExceptions { get { return Parameters.MaxPricingExceptions; } }
|
|
private ActivePositions ActivePositions { get; set; }
|
|
private Positions AllPositions { get; set; }
|
|
private CMTCandidates Candidates { get; set; }
|
|
private CMTPricingExceptions PricingExceptions { get; set; }
|
|
private StopLimits StopLimits { 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();
|
|
if(null!=paramPathSessionFileName) paramPathSessionFileName=paramPathSessionFileName.Trim();
|
|
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
|
|
{
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(paramPathSessionFileName);
|
|
return GetModelPerformance(sessionParams);
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
return null;
|
|
}
|
|
}
|
|
// Calcualtes the expectation for the model ( Percent of Winning Trades * Average Gain)/(Percent Losing Trades * Average Loss)
|
|
// The expectation should be above zero
|
|
public static ModelStatistics GetModelStatistics(CMTSessionParams 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(CMTSessionParams sessionParams)
|
|
{
|
|
Profiler profiler=new Profiler();
|
|
ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
try
|
|
{
|
|
if(null==sessionParams) return null;
|
|
MarketData.Generator.CMTrend.Positions combinedPositions=sessionParams.GetCombinedPositions();
|
|
|
|
// Fix purchase date/sell date fall on weekend
|
|
foreach(MarketData.Generator.CMTrend.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.CMTrend.Positions openPositions=new MarketData.Generator.CMTrend.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
|
|
MarketData.Generator.CMTrend.Positions closedPositions=new MarketData.Generator.CMTrend.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.CMTrend.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)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("******************* No price for {0} on {1} *****************",openPosition.Symbol,currentDate.ToShortDateString()));
|
|
return performanceSeries;
|
|
}
|
|
gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
|
|
marketValue+=(price.Close*openPosition.Shares);
|
|
}
|
|
foreach(MarketData.Generator.CMTrend.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, 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;
|
|
CMTSessionParams 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.DisplayTop(10);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"************** T O P L O S E R S *************");
|
|
AllPositions.DisplayBottom(10);
|
|
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)));
|
|
|
|
double gainLoss=0.00;
|
|
double winners=0.00;
|
|
double losers=0.00;
|
|
double averageWinner=0.00;
|
|
double averageLoser=0.00;
|
|
double totalTrades=0.00;
|
|
|
|
winners=ActivePositions.Where(x => x.GainLoss>=0).Count()+AllPositions.Where(x => x.GainLoss>=0).Count();
|
|
losers=ActivePositions.Where(x => x.GainLoss<0).Count()+AllPositions.Where(x => x.GainLoss<0).Count();
|
|
averageWinner=(ActivePositions.Count>0&&ActivePositions.Where(x => x.GainLoss>=0).Count()>0?ActivePositions.Where(x => x.GainLoss>=0).Select(x => x.GainLoss).Average():0+AllPositions.Count>0&&AllPositions.Where(x => x.GainLoss>=0).Count()>0?AllPositions.Where(x => x.GainLoss>=0).Select(x => x.GainLoss).Average():0)/2.00;
|
|
averageLoser=(ActivePositions.Count>0&&ActivePositions.Where(x => x.GainLoss<0).Count()>0?ActivePositions.Where(x => x.GainLoss<0).Select(x => x.GainLoss).Average():0+AllPositions.Count>0&&AllPositions.Where(x => x.GainLoss<0).Count()>0?AllPositions.Where(x => x.GainLoss<0).Select(x => x.GainLoss).Average():0)/2.00;
|
|
totalTrades=AllPositions.Count+ActivePositions.Count;
|
|
double netProfit;
|
|
|
|
double totalRWinners=ActivePositions.Count>0?ActivePositions.Where(x => x.GainLoss>=0).Select(x => x.RMultiple).Sum():0+AllPositions.Count>0?AllPositions.Where(x => x.GainLoss>=0).Select(x => x.RMultiple).Sum():0;
|
|
double totalRLosers=ActivePositions.Count>0?ActivePositions.Where(x => x.GainLoss<0).Select(x => x.RMultiple).Sum():0+AllPositions.Count>0?AllPositions.Where(x => x.GainLoss<0).Select(x => x.RMultiple).Sum():0;
|
|
|
|
if(0==ActivePositions.Count&&0==AllPositions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"There does not appear to be any trade data in this file.");
|
|
}
|
|
|
|
if(null!=Candidates&&0!=Candidates.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"*********************************** C A N D I D A T E S *********************************");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,CMTCandidate.Header());
|
|
foreach(CMTCandidate candidate in Candidates) MDTrace.WriteLine(LogLevel.DEBUG,candidate.ToString());
|
|
}
|
|
|
|
// ********************************************************************************************************************************************************************************
|
|
// ************************************************************************* S T O P L I M I T S A N D P R I C I N G *********************************************************
|
|
// ********************************************************************************************************************************************************************************
|
|
if(null!=AllPositions&&0!=AllPositions.Count)
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
foreach(Position position in AllPositions)
|
|
{
|
|
double stopPrice=position.InitialStopLimit;
|
|
List<DateTime> dates=dateGenerator.GenerateHistoricalDates(position.SellDate,position.PurchaseDate);
|
|
StringBuilder sb=new StringBuilder();
|
|
sb.Append("Symbol,Date,StopLimit,High,Low,Close,Gain/Loss,Gain/Loss (%),PriceTrend,Slope,Purchase");
|
|
MDTrace.WriteLine(LogLevel.DEBUG,sb.ToString());
|
|
|
|
double stopLimitValue=0.00;
|
|
double priceTrendIndicatorSlope=double.NaN;
|
|
for(int dateIndex=dates.Count-1;dateIndex>=0;dateIndex--)
|
|
{
|
|
DateTime date=dates[dateIndex];
|
|
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,date);
|
|
if(null==price) continue;
|
|
if(dateIndex==dates.Count-1) stopLimitValue=position.InitialStopLimit;
|
|
double gainLossItem=0.00;
|
|
double gainLossPercentItem=0.00;
|
|
if(date.Equals(position.SellDate))
|
|
{
|
|
gainLossItem=(position.CurrentPrice*position.Shares)-(position.PurchasePrice*position.Shares);
|
|
gainLossPercentItem=((position.CurrentPrice-position.PurchasePrice)/position.PurchasePrice)*100.00;
|
|
}
|
|
else
|
|
{
|
|
gainLossItem=(price.Close*position.Shares)-(position.PurchasePrice*position.Shares);
|
|
gainLossPercentItem=((price.Close-position.PurchasePrice)/position.PurchasePrice)*100.00;
|
|
}
|
|
if(null!=StopLimits&&0!=StopLimits.Count)
|
|
{
|
|
StopLimit stopLimit=StopLimits.Where(x => x.Symbol.Equals(price.Symbol)&&x.AnalysisDate.Equals(price.Date)).FirstOrDefault();
|
|
if(null!=stopLimit)
|
|
{
|
|
stopLimitValue=stopLimit.NewStop;
|
|
priceTrendIndicatorSlope=stopLimit.PriceTrendIndicatorSlope;
|
|
}
|
|
}
|
|
sb=new StringBuilder();
|
|
sb.Append(price.Symbol).Append(",");
|
|
sb.Append(price.Date.ToShortDateString()).Append(",");
|
|
sb.Append(Utility.FormatCurrency(stopLimitValue)).Append(",");
|
|
sb.Append(Utility.FormatCurrency(price.High)).Append(",");
|
|
sb.Append(Utility.FormatCurrency(price.Low)).Append(",");
|
|
sb.Append(Utility.FormatCurrency(price.Close)).Append(",");
|
|
sb.Append(Utility.FormatCurrency(gainLossItem)).Append(",");
|
|
sb.Append(Utility.FormatNumber(gainLossPercentItem,2,false)).Append(",");
|
|
sb.Append(Utility.FormatNumber(priceTrendIndicatorSlope,4,false)).Append(",");
|
|
sb.Append(Utility.FormatCurrency(position.PurchasePrice));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,sb.ToString());
|
|
}
|
|
}
|
|
}
|
|
// ********************************************************************************************************************************************************************************
|
|
// ********************************************************************************************************************************************************************************
|
|
// ********************************************************************************************************************************************************************************
|
|
if(ActivePositions.Count > 0 && AllPositions.Count > 0)
|
|
{
|
|
double realtimeGainLoss=GetRealtimeGainLoss(PricingDA.GetLatestDate());
|
|
gainLoss=AllPositions.Sum(x => x.GainLoss);
|
|
if(!double.IsNaN(realtimeGainLoss)) gainLoss+=realtimeGainLoss;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Total Gain/Loss {0}",Utility.FormatCurrency(gainLoss)));
|
|
}
|
|
else if(ActivePositions.Count>0)
|
|
{
|
|
double realtimeGainLoss=GetRealtimeGainLoss(PricingDA.GetLatestDate());
|
|
if(!double.IsNaN(realtimeGainLoss)) gainLoss=realtimeGainLoss;
|
|
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 Gain/Loss {0}",Utility.FormatCurrency(gainLoss)));
|
|
}
|
|
netProfit=gainLoss/totalTrades;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Winners to losers {0}:{1} Hit Ratio:{2}",(int)winners,(int)losers,Utility.FormatPercent((winners/(winners+losers)))));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Average winner {0} Average Loser:{1}",Utility.FormatCurrency(averageWinner),Utility.FormatCurrency(averageLoser)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Total Trades:{0}",Utility.FormatNumber(totalTrades,0,true)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Total RWinners:{0}",Utility.FormatNumber(totalRWinners,0,true)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Total RLosers:{0}",Utility.FormatNumber(totalRLosers,0,true)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Average Profit {0}/Trade",Utility.FormatCurrency(netProfit)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Average RProfit {0}R per trade (expectancy)",Utility.FormatNumber((totalRWinners+totalRLosers)/totalTrades,2,false)));
|
|
GBPriceCache.GetInstance().Dispose();
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** E N T R Y T E S T *****************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
// This is for standalone testing.
|
|
public void EntryTest(String symbol,DateTime analysisDate)
|
|
{
|
|
try
|
|
{
|
|
CMTParams cmtParams=new CMTParams();
|
|
Parameters=cmtParams;
|
|
|
|
// DMA200 check : Candidates must remain above the 200 day moving average.
|
|
Prices prices200=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,MovingAverageGenerator.DayCount200+10);
|
|
Price currentPrice=GBPriceCache.GetInstance().GetPrice(symbol,analysisDate);
|
|
if(null==prices200||prices200.Count<MovingAverageGenerator.DayCount200)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA200 for candidate {0} due to insufficient pricng data",symbol));
|
|
|
|
return;
|
|
}
|
|
if(null==currentPrice)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA200 for candidate {0} due to lack of pricng data for {1}",symbol,Utility.DateTimeToStringMMHDDHYYYY(analysisDate)));
|
|
return;
|
|
}
|
|
prices200=new Prices(prices200.Take(MovingAverageGenerator.DayCount200).ToList());
|
|
DMAPrices dma200Prices=MovingAverageGenerator.GenerateMovingAverage(prices200,prices200.Count);
|
|
double dma200Close=dma200Prices[0].AVGPrice;
|
|
if(currentPrice.Close<dma200Close)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("DMA200 Violation for candidate {0}. The closing price is less than the 200 day moving average {1} < {2} on {3}",symbol,Utility.FormatCurrency(currentPrice.Close),Utility.FormatCurrency(dma200Close),analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
// DMA50 check : The 50 day moving average must remain above the 200 day moving average
|
|
Prices prices50=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,MovingAverageGenerator.DayCount50+10);
|
|
if(null==prices50||prices50.Count<MovingAverageGenerator.DayCount50)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA50 for candidate {0} due to insufficient pricng data",symbol));
|
|
return;
|
|
}
|
|
if(null==currentPrice)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA50 for candidate {0} due to lack of pricng data for {1}",symbol,Utility.DateTimeToStringMMHDDHYYYY(analysisDate)));
|
|
return;
|
|
}
|
|
prices50=new Prices(prices50.Take(MovingAverageGenerator.DayCount50).ToList());
|
|
DMAPrices dma50Prices=MovingAverageGenerator.GenerateMovingAverage(prices50,prices50.Count);
|
|
double dma50Close=dma50Prices[0].AVGPrice;
|
|
if(currentPrice.Close<dma50Close)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("DMA50 Violation for candidate {0}. The closing price is less than the 50 day moving average {1} < {2} on {3}",symbol,Utility.FormatCurrency(currentPrice.Close),Utility.FormatCurrency(dma50Close),analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
// Price trend check. Ensure that prices are trending higher UsePriceTrendSlope UsePriceTrendSlopeDayCount
|
|
if(Parameters.UsePriceSlopeIndicator)
|
|
{
|
|
int dayCount=Parameters.UsePriceSlopeIndicatorDays;
|
|
Prices pricesTrend=PricingDA.GetPrices(symbol,dayCount);
|
|
double[] pricesLow=Numerics.ToDouble(pricesTrend.GetPricesLow());
|
|
LeastSquaresResult leastSquaresResult=LeastSquaresHelper.CalculateLeastSquares(pricesLow);
|
|
if(leastSquaresResult.Slope<=0.00)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Price trend violation {0}. The {1} pricing slope is {2}",symbol,dayCount,Utility.FormatNumber(leastSquaresResult.Slope,6)));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// NarrowRange check
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,Parameters.EntryHorizon+5);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Checking for NarrowRange entry for symbol {0} on {1}",symbol,analysisDate.ToShortDateString()));
|
|
if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Narrow Range entry for candidate {0} due to lack of current price on {1}",symbol,analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
if(NarrowRangeIndicator.IsNarrowRangeEntry(analysisDate,prices,Parameters.EntryHorizon)) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on NarrowRange",symbol));
|
|
// Preparation for SwingEntry check
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Checking for Swing entry for symbol {0} on {1}",symbol,analysisDate.ToShortDateString()));
|
|
prices=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,Parameters.EntryHorizon<40?40:Parameters.EntryHorizon);
|
|
if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Swing entry for candidate {0} due to lack of current price on {1}",symbol,analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
DMAPrices dmaPrices=MovingAverageGenerator.GenerateMovingAverage(prices,Parameters.EntryHorizon);
|
|
BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices);
|
|
|
|
// SwingTrade check
|
|
SwingTrades swingTrades=SwingTradeHelper.FindSwingTrades(prices,dmaPrices.GetDMAPricesByDate(),bollingerBands.GetBollingerBandElementsByDate());
|
|
if(null!=swingTrades&&swingTrades.HasEntryOn(analysisDate)) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on Swing on {1}",symbol,analysisDate.ToShortDateString()));
|
|
// MACD check
|
|
if(!CMTMACDIndicator.IsMACDDowntrend(analysisDate,symbol,Parameters)) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on MACD{1}",symbol,Parameters.MACDSetup));
|
|
|
|
// PriceTrend check
|
|
prices=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,Parameters.PriceTrendDays+10);
|
|
if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Price Trend entry for candidate {0} due to lack of current price on {1}",symbol,analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
if(PriceTrendIndicator.IsUptrend(prices,Parameters.PriceTrendDays).IsUpTrend) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on PriceTrend on {1}",symbol,analysisDate.ToShortDateString()));
|
|
// VolumeTrend check
|
|
prices=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,Parameters.VolumeTrendDays+10);
|
|
if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Volume Trend entry for candidate {0} due to lack of current price on {1}",symbol,analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
if(VolumeTrendIndicator.IsUptrend(prices,Parameters.VolumeTrendDays)) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on VolumeTrend on {1}",symbol,analysisDate.ToShortDateString()));
|
|
// ChannelBreakout
|
|
prices=GBPriceCache.GetInstance().GetPrices(symbol,analysisDate,Parameters.ChannelBreakoutHorizon);
|
|
if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate ChannelBreakout entry for candidate {0} due to lack of current price on {1}",symbol,analysisDate.ToShortDateString()));
|
|
return;
|
|
}
|
|
if(ChannelBreakoutIndicator.IsChannelBreakOut(TradeDate,prices,Parameters.ChannelBreakoutHorizon)) MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on ChannelBreakout on {1}",symbol,analysisDate.ToShortDateString()));
|
|
}
|
|
finally
|
|
{
|
|
GBPriceCache.GetInstance().Dispose();
|
|
}
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** C L O S E **********************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double price,String sessionFile)
|
|
{
|
|
if(!CMTSessionManager.IsValidSessionFile(sessionFile))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Invalid session file '{0}'.",sessionFile));
|
|
return false;
|
|
}
|
|
PathSessionFileName=sessionFile;
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(PathSessionFileName);
|
|
Parameters=sessionParams.CMTParams;
|
|
TradeDate=sessionParams.TradeDate;
|
|
AnalysisDate=DateTime.Now;
|
|
sessionParams.CMTParams.AnalysisDate=DateTime.Now;
|
|
sessionParams.LastUpdated=DateTime.Now;
|
|
sessionParams.AnalysisDate=DateTime.Now;
|
|
Candidates=sessionParams.Candidates;
|
|
StopLimits=sessionParams.StopLimits;
|
|
PricingExceptions=sessionParams.PricingExceptions;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
CashBalance=sessionParams.CashBalance;
|
|
NonTradeableCash=sessionParams.NonTradeableCash;
|
|
if(!BackupSession()) return false;
|
|
Position 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;
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// *************************************************************************** E D I T ******************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public bool EditPosition(String symbol,DateTime purchaseDate,double purchasePrice,double initialStop,double trailingStop,String sessionFile)
|
|
{
|
|
if(!CMTSessionManager.IsValidSessionFile(sessionFile))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Invalid session file '{0}'.",sessionFile));
|
|
return false;
|
|
}
|
|
PathSessionFileName=sessionFile;
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(PathSessionFileName);
|
|
Parameters=sessionParams.CMTParams;
|
|
TradeDate=sessionParams.TradeDate;
|
|
AnalysisDate=DateTime.Now;
|
|
sessionParams.CMTParams.AnalysisDate=DateTime.Now;
|
|
sessionParams.LastUpdated=DateTime.Now;
|
|
sessionParams.AnalysisDate=DateTime.Now;
|
|
Candidates=sessionParams.Candidates;
|
|
StopLimits=sessionParams.StopLimits;
|
|
PricingExceptions=sessionParams.PricingExceptions;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
CashBalance=sessionParams.CashBalance;
|
|
NonTradeableCash=sessionParams.NonTradeableCash;
|
|
if(!BackupSession()) return false;
|
|
Position 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=(null==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;
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** D A I L Y *****************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public CMTTrendModelResult RunDaily(DateTime tradeDate,String paramPathSessionFileName,CMTParams cmtParams)
|
|
{
|
|
CMTTrendModelResult result=new CMTTrendModelResult();
|
|
|
|
try
|
|
{
|
|
DateTime now=DateTime.Now;
|
|
Parameters=cmtParams;
|
|
CashBalance=Parameters.InitialCash;
|
|
ActivePositions=new ActivePositions();
|
|
AllPositions=new Positions();
|
|
Candidates=new CMTCandidates();
|
|
PricingExceptions=new CMTPricingExceptions();
|
|
StopLimits=new StopLimits();
|
|
TradeDate=tradeDate;
|
|
PathSessionFileName=paramPathSessionFileName;
|
|
if(CMTSessionManager.IsValidSessionFile(paramPathSessionFileName))
|
|
{
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(paramPathSessionFileName);
|
|
result=CheckSequentialTradeDate(tradeDate,sessionParams.TradeDate);
|
|
if(!result.Success)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,result.Message);
|
|
return result;
|
|
}
|
|
Parameters=sessionParams.CMTParams;
|
|
cmtParams=sessionParams.CMTParams;
|
|
sessionParams.TradeDate=TradeDate;
|
|
sessionParams.CMTParams.TradeDate=tradeDate;
|
|
sessionParams.CMTParams.AnalysisDate=now;
|
|
sessionParams.LastUpdated=now;
|
|
sessionParams.AnalysisDate=now;
|
|
AnalysisDate=sessionParams.AnalysisDate;
|
|
Candidates=sessionParams.Candidates;
|
|
StopLimits=sessionParams.StopLimits;
|
|
StartDate=sessionParams.StartDate;
|
|
PricingExceptions=sessionParams.PricingExceptions;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
CashBalance=sessionParams.CashBalance;
|
|
NonTradeableCash=sessionParams.NonTradeableCash;
|
|
}
|
|
else Parameters=cmtParams;
|
|
DisplayRealtimeBlotter(TradeDate);
|
|
if(0==PricingDA.GetPriceCountOn(TradeDate))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No prices on {0}, skipping date.",TradeDate.ToShortDateString()));
|
|
return result;
|
|
}
|
|
if(0!=ActivePositions.Count&&TradeDate<ActivePositions.Select(x => x.PurchaseDate).Max())
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate ({0}) must be greater than or equal to the max position date ({1}) in the trade file.",TradeDate.ToShortDateString(),ActivePositions.Select(x => x.PurchaseDate).Max().ToShortDateString()));
|
|
return result;
|
|
}
|
|
ManageOpenPositions(TradeDate);
|
|
ManageCandidates(TradeDate);
|
|
// ************************************************************************************************************************************************************************
|
|
// **************************************************************************** N E W P O S I T I O N S *****************************************************************
|
|
// ************************************************************************************************************************************************************************
|
|
if(null!=PathSessionFileName) SaveSession(); // SAVE THE SESSION FILE POST POSITION AND CANDIDATE MANAGEMENT
|
|
if(Parameters.SuspendTrading)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Trading has been suspended for {0}. SuspendTrading flag={1} ",TradeDate.ToShortDateString(),Parameters.SuspendTrading));
|
|
result.Success=true;
|
|
return result;
|
|
}
|
|
if(ActivePositions.PositionsOn(TradeDate)>=Parameters.MaxDailyPositions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Number of trades for {0} is already at maximum {1}",TradeDate.ToShortDateString(),Parameters.MaxDailyPositions));
|
|
result.Success=true;
|
|
return result;
|
|
}
|
|
Positions positions=BuyCandidates(TradeDate,CashBalance,ActivePositions.GetSymbols());
|
|
if(null != positions && 0!=positions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"******************** B U Y ********************");
|
|
positions.Display();
|
|
if(CashBalance-positions.GetExposure()<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.GetExposure())));
|
|
result.Success=false;
|
|
return result;
|
|
}
|
|
ActivePositions.Add(positions);
|
|
CashBalance-=positions.GetExposure();
|
|
}
|
|
DisplayRealtimeBlotter(TradeDate);
|
|
result.Success=true;
|
|
result.CashBalance=CashBalance;
|
|
if(null!=PathSessionFileName) SaveSession();
|
|
return result;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
result.Success=false;
|
|
result.Message=exception.ToString();
|
|
return result;
|
|
}
|
|
finally
|
|
{
|
|
GBPriceCache.GetInstance().Dispose();
|
|
}
|
|
}
|
|
// ******************************************************************************************************************************************************
|
|
// ****************************************************************** B A C K T E S T *****************************************************************
|
|
// ******************************************************************************************************************************************************
|
|
public CMTTrendModelResult RunBacktestMode(DateTime startDate,DateTime endDate,bool sellAtEndOfSimulation,String paramPathSessionFileName,CMTParams cmtParams)
|
|
{
|
|
CMTTrendModelResult result=new CMTTrendModelResult();
|
|
|
|
try
|
|
{
|
|
Parameters=cmtParams;
|
|
CashBalance=Parameters.InitialCash;
|
|
ActivePositions=new ActivePositions();
|
|
AllPositions=new Positions();
|
|
Candidates=new CMTCandidates();
|
|
PricingExceptions=new CMTPricingExceptions();
|
|
StopLimits=new StopLimits();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
List<DateTime> runDates;
|
|
|
|
runDates=dateGenerator.GenerateHistoricalDates(startDate,endDate);
|
|
PathSessionFileName=paramPathSessionFileName;
|
|
if(CMTSessionManager.IsValidSessionFile(paramPathSessionFileName))
|
|
{
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(paramPathSessionFileName);
|
|
Parameters=sessionParams.CMTParams;
|
|
cmtParams=sessionParams.CMTParams;
|
|
sessionParams.TradeDate=startDate;
|
|
sessionParams.LastUpdated=DateTime.Now;
|
|
sessionParams.AnalysisDate=DateTime.Now;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
CashBalance=sessionParams.CashBalance;
|
|
}
|
|
else Parameters=cmtParams;
|
|
Parameters.DisplayConfiguration();
|
|
StartDate=startDate;
|
|
AnalysisDate=Today();
|
|
int currentYear=-1;
|
|
|
|
foreach(DateTime tradeDate in runDates)
|
|
{
|
|
TradeDate=tradeDate;
|
|
AnalysisDate=TradeDate;
|
|
if(-1==currentYear) currentYear=TradeDate.Year;
|
|
if(TradeDate.Year!=currentYear)
|
|
{
|
|
GBPriceCache.GetInstance().ClearCacheOnOrBefore(TradeDate,true);
|
|
currentYear=TradeDate.Year;
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("PROCESSING TRADE DATE {0} WITH AVAILABLE CASH OF {1}",tradeDate.ToShortDateString(),Utility.FormatCurrency(CashBalance)));
|
|
DisplayRealtimeBlotter(TradeDate);
|
|
if(0==PricingDA.GetPriceCountOn(TradeDate))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No prices on {0}, skipping date.",TradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
if(0!=ActivePositions.Count&&TradeDate<ActivePositions.Select(x => x.PurchaseDate).Max())
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate ({0}) must be greater than or equal to the max position date ({1}) in the trade file.",TradeDate.ToShortDateString(),ActivePositions.Select(x => x.PurchaseDate).Max().ToShortDateString()));
|
|
return result;
|
|
}
|
|
ManageOpenPositions(TradeDate);
|
|
ManageCandidates(TradeDate);
|
|
if(ActivePositions.PositionsOn(TradeDate)>=Parameters.MaxDailyPositions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Number of trades for {0} is already at maximum {1}",TradeDate.ToShortDateString(),Parameters.MaxDailyPositions));
|
|
result.Success=true;
|
|
continue;
|
|
}
|
|
Positions positions=BuyCandidates(TradeDate,CashBalance,ActivePositions.GetSymbols());
|
|
if(null!=positions&&0!=positions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"******************** B U Y ********************");
|
|
positions.Display();
|
|
if(CashBalance-positions.GetExposure()<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.GetExposure())));
|
|
result.Success=false;
|
|
return result;
|
|
}
|
|
ActivePositions.Add(positions);
|
|
CashBalance-=positions.GetExposure();
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"\n");
|
|
}
|
|
// ***********************************************************************************************************************************************************************
|
|
// ********************************************************* S E L L A L L A T E N D O F S I M U L A T I O N ***************************************************
|
|
if(sellAtEndOfSimulation)
|
|
{
|
|
SellPositions(ActivePositions,TradeDate,"Closed due to end of simulation.");
|
|
CashBalance+=ActivePositions.GetMarketValue();
|
|
AllPositions.Add(ActivePositions);
|
|
ActivePositions.Clear();
|
|
}
|
|
// ***********************************************************************************************************************************************************************
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"******************************************** C L O S E D P O S I T I O N S ***********************************************");
|
|
AllPositions.Display();
|
|
DisplayBalanceFromAllPositions(); // Aggregate the balance from the closed positions
|
|
result.Success=true;
|
|
result.CashBalance=CashBalance;
|
|
GBPriceCache.GetInstance().Dispose();
|
|
if(null!=PathSessionFileName) SaveSession();
|
|
return result;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
|
|
result.Success=false;
|
|
result.Message=exception.ToString();
|
|
return result;
|
|
}
|
|
finally
|
|
{
|
|
GBPriceCache.GetInstance().Dispose();
|
|
}
|
|
}
|
|
public void RunTrendTemplate(DateTime? analysisDate=null)
|
|
{
|
|
try
|
|
{
|
|
List<String> symbols=PricingDA.GetSymbols();
|
|
CMTParams cmtParams=new CMTParams();
|
|
List<CMTCandidate> violations=new List<CMTCandidate>();
|
|
List<CMTCandidate> candidates=new List<CMTCandidate>();
|
|
|
|
for(int index=0;index<symbols.Count;index++)
|
|
{
|
|
String symbol=symbols[index];
|
|
if(0==(index%500)) Console.WriteLine("GenerateCMTCandidates processing item {0} of {1}",index+1,symbols.Count);
|
|
CMTCandidate cmtCandidate=CMTCandidateGenerator.GenerateCandidate(symbol,analysisDate.Value,cmtParams);
|
|
if(cmtCandidate.Violation) violations.Add(cmtCandidate);
|
|
else candidates.Add(cmtCandidate);
|
|
}
|
|
List<String> violationTypes=violations.Select(x => x.Reason).Distinct().ToList();
|
|
violationTypes.Sort();
|
|
foreach(String violationType in violationTypes)
|
|
{
|
|
List<CMTCandidate> candidatesInViolation=violations.Where(x => x.Reason.Equals(violationType)).ToList();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Violation:{0} Count:{1} Percent:{2}",violationType,candidatesInViolation.Count,Utility.FormatPercent((double)candidatesInViolation.Count/(double)violations.Count)));
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Found {0} qualifying candidates",candidates.Count));
|
|
foreach(CMTCandidate candidate in candidates)
|
|
{
|
|
Console.WriteLine("_____________________________________________");
|
|
String companyName=PricingDA.GetNameForSymbol(candidate.Symbol);
|
|
Console.WriteLine(String.Format("Symbol:{0} Company Name:{1}",candidate.Symbol,companyName));
|
|
}
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
|
|
}
|
|
finally
|
|
{
|
|
GBPriceCache.GetInstance().Dispose();
|
|
}
|
|
}
|
|
// ***********************************************************************************************************************************************************************
|
|
// *********************************************************************** M A N A G E O P E N P O S I T I O N S *****************************************************
|
|
// ***********************************************************************************************************************************************************************
|
|
private void ManageOpenPositions(DateTime tradeDate)
|
|
{
|
|
if(0==ActivePositions.Count) return;
|
|
List<Position> closedPositions=new List<Position>();
|
|
foreach(Position position in ActivePositions)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,tradeDate);
|
|
if(null==price)
|
|
{
|
|
int exceptionCount=AddPricingException(position.Symbol);
|
|
if(exceptionCount>MaxPricingExceptions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[ManageOpenPositions] Selling {0} on {1} because price exceptions exceeds maximum of {2}",position.Symbol,tradeDate.ToShortDateString(),MaxPricingExceptions));
|
|
price=GBPriceCache.GetInstance().GetPriceOrLatestAvailable(position.Symbol,tradeDate);
|
|
position.SellDate=tradeDate;
|
|
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("[ManageOpenPositions] Cannot determine price for {0} on {1}",position.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
position.CurrentPrice=price.Close; // Intraday the Close will be the current price
|
|
RemovePricingException(position.Symbol);
|
|
if(Parameters.SidewaysDetection&&Utility.IsEpoch(position.LastStopAdjustment)&&price.Close>position.PurchasePrice)
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
int daysHeld=Math.Abs(dateGenerator.DaysBetweenActual(position.PurchaseDate,tradeDate));
|
|
if(daysHeld>Parameters.SidewaysAfterDays)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[ManageOpenPositions] Selling {0} on {1} due to sideways motion for {2} days",position.Symbol,tradeDate.ToShortDateString(),daysHeld));
|
|
position.SellDate=tradeDate;
|
|
position.CurrentPrice=position.TrailingStopLimit;
|
|
position.Comment="Closed due to sideways motion.";
|
|
CashBalance+=position.MarketValue;
|
|
AllPositions.Add(position);
|
|
closedPositions.Add(position);
|
|
continue;
|
|
}
|
|
}
|
|
if(price.Low<position.TrailingStopLimit&&!position.PurchaseDate.Equals(tradeDate)) // This will miss StopLimit violations that occur on the date of purchase. Needs to be watched manually because we don't have tick data
|
|
{
|
|
position.SellDate=tradeDate;
|
|
position.CurrentPrice=position.TrailingStopLimit;
|
|
position.Comment="Closed due to trailing stop.";
|
|
CashBalance+=position.MarketValue;
|
|
AllPositions.Add(position);
|
|
closedPositions.Add(position);
|
|
}
|
|
else if(HasDMABreak(position,tradeDate,price))
|
|
{
|
|
position.SellDate=tradeDate;
|
|
position.CurrentPrice=price.Close;
|
|
position.Comment="Closed due to DMA break";
|
|
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(tradeDate,price,position);
|
|
}
|
|
}
|
|
if(0!=closedPositions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
|
|
foreach(Position closedPosition in closedPositions)
|
|
{
|
|
closedPosition.Display();
|
|
ActivePositions.Remove(closedPosition);
|
|
}
|
|
}
|
|
}
|
|
// **********************************************************************************************************************************************************
|
|
// ***************************************************** M O V I N G A V E R A G E B R E A K C H E C K *************************************************
|
|
// **********************************************************************************************************************************************************
|
|
private bool HasDMABreak(Position position,DateTime tradeDate,Price currentPrice)
|
|
{
|
|
if(!Parameters.SellOnDMABreak) return false;
|
|
List<String> dmaBreakValues=Parameters.DMABreakValuesCollection;
|
|
foreach(String strDMABreakValue in dmaBreakValues)
|
|
{
|
|
int dmaBreakValue=int.Parse(strDMABreakValue);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Evaluating DMA({0}) for {1} on {2}",dmaBreakValue,position.Symbol,tradeDate.ToShortDateString()));
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(position.Symbol,tradeDate,dmaBreakValue+5);
|
|
if(null==prices||prices.Count<dmaBreakValue)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA({0}) for {1} on {2}",dmaBreakValue,position.Symbol,tradeDate.ToShortDateString()));
|
|
return true;
|
|
}
|
|
prices=new Prices(prices.Take(dmaBreakValue).ToList());
|
|
DMAPrices dmaPrices=MovingAverageGenerator.GenerateMovingAverage(prices,prices.Count);
|
|
if(currentPrice.Close<dmaPrices[0].AVGPrice)
|
|
{
|
|
bool isLoss=position.GetGainLoss(currentPrice)<0.00;
|
|
if(isLoss)
|
|
{
|
|
if(Parameters.DMABreakForceBreak)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Moving average violation detected for {0} on {1}. Price:{2} DMA({3}):{4} : Sell triggered.",position.Symbol,tradeDate.ToShortDateString(),Utility.FormatCurrency(currentPrice.Close),dmaBreakValue,Utility.FormatCurrency(dmaPrices[0].AVGPrice)));
|
|
return true;
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Moving average violation detected for {0} on {1}. Price:{2} DMA({3}):{4} : DMABreakForceBreak=False, holding security.",position.Symbol,tradeDate.ToShortDateString(),Utility.FormatCurrency(currentPrice.Close),dmaBreakValue,Utility.FormatCurrency(dmaPrices[0].AVGPrice)));
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Moving average violation detected for {0} on {1}. Price:{2} DMA({3}):{4} : Sell triggered.",position.Symbol,tradeDate.ToShortDateString(),Utility.FormatCurrency(currentPrice.Close),dmaBreakValue,Utility.FormatCurrency(dmaPrices[0].AVGPrice)));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// **********************************************************************************************************************************************************
|
|
// ***************************************************** 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)
|
|
{
|
|
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)
|
|
{
|
|
CMTPricingException pricingException=PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault();
|
|
if(null==pricingException) { pricingException=new CMTPricingException(symbol,1); PricingExceptions.Add(pricingException); PricingExceptions.Add(pricingException); }
|
|
else pricingException.ExceptionCount++;
|
|
return pricingException.ExceptionCount;
|
|
}
|
|
private void RemovePricingException(String symbol)
|
|
{
|
|
CMTPricingException pricingException=PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault();
|
|
if(null==pricingException) return;
|
|
PricingExceptions.Remove(pricingException);
|
|
}
|
|
private bool HasPricingException(String symbol)
|
|
{
|
|
CMTPricingException pricingException=PricingExceptions.Where(x => x.Symbol.Equals(symbol)).FirstOrDefault();
|
|
if(null==pricingException) return false;
|
|
return true;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// **************************************************************** S E L L P O S I T I O N S *****************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private void SellPositions(Positions positions,DateTime sellDate,String comment)
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
foreach(Position position in positions)
|
|
{
|
|
SellPosition(position,sellDate,comment);
|
|
}
|
|
}
|
|
private void SellPosition(Position position,DateTime sellDate,String comment)
|
|
{
|
|
position.SellDate=sellDate;
|
|
position.Comment=comment;
|
|
Price price=GetPrice(position.Symbol,sellDate);
|
|
if(null==price)
|
|
{
|
|
DateTime latestPricingDate=PricingDA.GetLatestDate(position.Symbol);
|
|
price=PricingDA.GetPrice(position.Symbol,latestPricingDate);
|
|
if(null==price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[SellPosition] **********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
|
|
position.CurrentPrice=position.PurchasePrice;
|
|
}
|
|
else
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Database does not contain a price for {0} on {1}. Latest pricing date for this security is {2}",position.Symbol,sellDate.ToShortDateString(),latestPricingDate.ToShortDateString()));
|
|
position.CurrentPrice=price.Close;
|
|
}
|
|
}
|
|
else position.CurrentPrice=price.Close;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// **************************************************************** B U Y C A N D I D A T E S *****************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private Positions BuyCandidates(DateTime tradeDate,double cash,List<String> symbolsHeld)
|
|
{
|
|
try
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
Positions positions=new Positions();
|
|
int positionCount=0;
|
|
|
|
if(ActivePositions.Count==MaxOpenPositions)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The maximum number of active positions has been reached. MaxOpenPositions={0}",MaxOpenPositions));
|
|
return positions;
|
|
}
|
|
if(null==Candidates||0==Candidates.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"There are no candidates in the candidate pool to search for entry points.");
|
|
return positions;
|
|
}
|
|
if(!IsTradeableMarket(tradeDate,Parameters))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The market is not tradeable on {0} due to one or more violations.",tradeDate));
|
|
return positions;
|
|
}
|
|
Candidates.Sort();
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format(""));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SEARCHING FOR ENTRY POINTS (TradeDate:{0} Available Cash:{1})",tradeDate.ToShortDateString(),Utility.FormatCurrency(cash)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0}",CMTCandidate.Header()));
|
|
for(int index=0;index<Candidates.Count;index++)
|
|
{
|
|
bool skipChecks=false;
|
|
CMTCandidate mmCandidate=Candidates[index];
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0}",mmCandidate.ToString()));
|
|
|
|
// Check NoTradeSymbol
|
|
if(Parameters.NoTradeSymbolsList.Any(x => x.Equals(mmCandidate.Symbol)))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Candidate {0} is in the NoTrade list",mmCandidate.Symbol));
|
|
RemoveCandidate(mmCandidate);
|
|
continue;
|
|
}
|
|
// Check if already held
|
|
if(ActivePositions.Any(x => x.Symbol.Equals(mmCandidate.Symbol)))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Candidate {0} is already held in active positions",mmCandidate.Symbol));
|
|
RemoveCandidate(mmCandidate);
|
|
continue;
|
|
}
|
|
// Check to make sure the candidate is still above the 200DMA. Candidates must remain above the 200 day moving average. (MMarc Minervini & Paul Tudor Jones)
|
|
Prices prices200=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,MovingAverageGenerator.DayCount200+10);
|
|
Price currentPrice=GBPriceCache.GetInstance().GetPrice(mmCandidate.Symbol,tradeDate);
|
|
if(null==prices200||prices200.Count<MovingAverageGenerator.DayCount200)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA200 for candidate {0} due to insufficient pricing data",mmCandidate.Symbol));
|
|
continue;
|
|
}
|
|
if(null==currentPrice)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA200 for candidate {0} due to lack of pricng data for {1}",mmCandidate.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
continue;
|
|
}
|
|
prices200=new Prices(prices200.Take(MovingAverageGenerator.DayCount200).ToList());
|
|
DMAPrices dma200Prices=MovingAverageGenerator.GenerateMovingAverage(prices200,prices200.Count);
|
|
double dma200Close=dma200Prices[0].AVGPrice;
|
|
if(currentPrice.Close<dma200Close)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("DMA200 Violation for candidate {0}. The closing price is less than the 200 day moving average {1} < {2} on {3}",mmCandidate.Symbol,Utility.FormatCurrency(currentPrice.Close),Utility.FormatCurrency(dma200Close),tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
// DMA50 check : The 50 day moving average must remain above the 200 day moving average
|
|
Prices prices50=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,MovingAverageGenerator.DayCount50+10);
|
|
if(null==prices50||prices50.Count<MovingAverageGenerator.DayCount50)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA50 for candidate {0} due to insufficient pricng data",mmCandidate.Symbol));
|
|
|
|
continue;
|
|
}
|
|
if(null==currentPrice)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate DMA50 for candidate {0} due to lack of pricng data for {1}",mmCandidate.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
continue;
|
|
}
|
|
prices50=new Prices(prices50.Take(MovingAverageGenerator.DayCount50).ToList());
|
|
DMAPrices dma50Prices=MovingAverageGenerator.GenerateMovingAverage(prices50,prices50.Count);
|
|
double dma50Close=dma50Prices[0].AVGPrice;
|
|
if(currentPrice.Close<dma50Close)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("DMA50 Violation for candidate {0}. The closing price is less than the 50 day moving average {1} < {2} on {3}",mmCandidate.Symbol,Utility.FormatCurrency(currentPrice.Close),Utility.FormatCurrency(dma50Close),tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
// Trend check ensure that prices are trending higher
|
|
if(Parameters.UsePriceSlopeIndicator)
|
|
{
|
|
int dayCount=Parameters.UsePriceSlopeIndicatorDays;
|
|
Prices pricesTrend=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,dayCount);
|
|
double[] pricesLow=Numerics.ToDouble(pricesTrend.GetPricesLow());
|
|
LeastSquaresResult leastSquaresResult=LeastSquaresHelper.CalculateLeastSquares(pricesLow);
|
|
if(leastSquaresResult.Slope<=0.00)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Price trend violation {0}. The {1} pricing slope is {2}",mmCandidate.Symbol,dayCount,Utility.FormatNumber(leastSquaresResult.Slope,6)));
|
|
continue;
|
|
}
|
|
}
|
|
// check for entries
|
|
if(Parameters.EntryTypesCollection.Any(x => x.Equals("OverExtended",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
bool? result=OverExtendedIndicator.IsOverextended(mmCandidate.Symbol,tradeDate,Parameters.UseOverExtendedIndicatorDays,Parameters.UseOverExtendedIndicatorViolationThreshhold,Parameters.UseOverExtendedIndicatorMarginPercent);
|
|
if(null==result || result.Value)continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on OverExtended",mmCandidate.Symbol));
|
|
}
|
|
if(Parameters.EntryTypesCollection.Any(x => x.Equals("MVP",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
if(MVPIndicator.IsTrue(mmCandidate.Symbol,tradeDate))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on MVP",mmCandidate.Symbol));
|
|
skipChecks=true; // skip the remaining checks. Immediate buy
|
|
}
|
|
}
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("NarrowRange",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,Parameters.EntryHorizon+5);
|
|
if(null==prices||0==prices.Count||!prices[0].Date.Date.Equals(tradeDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Narrow Range entry for candidate {0} due to lack of current price on {1}",mmCandidate.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
if(!NarrowRangeIndicator.IsNarrowRangeEntry(tradeDate,prices,Parameters.EntryHorizon)) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on NarrowRange",mmCandidate.Symbol));
|
|
}
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("Swing",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,Parameters.EntryHorizon<40?40:Parameters.EntryHorizon);
|
|
if(null==prices||!prices[0].Date.Date.Equals(tradeDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Swing entry for candidate {0} due to lack of current price on {1}",mmCandidate.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
DMAPrices dmaPrices=MovingAverageGenerator.GenerateMovingAverage(prices,Parameters.EntryHorizon);
|
|
BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices);
|
|
SwingTrades swingTrades=SwingTradeHelper.FindSwingTrades(prices,dmaPrices.GetDMAPricesByDate(),bollingerBands.GetBollingerBandElementsByDate());
|
|
if(null==swingTrades||!swingTrades.HasEntryOn(tradeDate)) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on Swing",mmCandidate.Symbol));
|
|
}
|
|
// MACD
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("MACD",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
if(CMTMACDIndicator.IsMACDDowntrend(tradeDate,mmCandidate.Symbol,Parameters)) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on MACD{1}",mmCandidate.Symbol,Parameters.MACDSetup));
|
|
}
|
|
// PRICETREND
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("PriceTrend",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,Parameters.PriceTrendDays+10);
|
|
if(null==prices||!prices[0].Date.Date.Equals(tradeDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Price Trend entry for candidate {0} due to lack of current price on {1}",mmCandidate.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
if(!PriceTrendIndicator.IsUptrend(prices,Parameters.PriceTrendDays).IsUpTrend) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on PriceTrend",mmCandidate.Symbol));
|
|
}
|
|
// VOLUMETREND
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("VolumeTrend",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,Parameters.VolumeTrendDays+10);
|
|
if(null==prices||!prices[0].Date.Date.Equals(tradeDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate Volume Trend entry for candidate {0} due to lack of current price on {1}",mmCandidate.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
if(!VolumeTrendIndicator.IsUptrend(prices,Parameters.VolumeTrendDays)) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on VolumeTrend",mmCandidate.Symbol));
|
|
}
|
|
// CHANNELBREAKOUT
|
|
if(!skipChecks && Parameters.EntryTypesCollection.Any(x => x.Equals("ChannelBreakout",StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(mmCandidate.Symbol,tradeDate,Parameters.ChannelBreakoutHorizon);
|
|
if(null==prices||!prices[0].Date.Date.Equals(tradeDate.Date))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot evaluate ChannelBreakout entry for candidate {0} due to lack of current price on {1}",mmCandidate.Symbol,tradeDate.ToShortDateString()));
|
|
continue;
|
|
}
|
|
if(!ChannelBreakoutIndicator.IsChannelBreakOut(tradeDate,prices,Parameters.ChannelBreakoutHorizon)) continue;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0} has entry on ChannelBreakout",mmCandidate.Symbol));
|
|
}
|
|
// end entry checks
|
|
Position prevPosition=AllPositions.Where(x => x.Symbol.Equals(mmCandidate.Symbol)).OrderByDescending(x => x.SellDate).FirstOrDefault();
|
|
if(null!=prevPosition)
|
|
{
|
|
int daysBetween=dateGenerator.DaysBetween(tradeDate,prevPosition.SellDate);
|
|
if(daysBetween<30)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Candidate was sold less than 30 days ago. Symbol: {0} Date Sold:{1}",mmCandidate.Symbol,prevPosition.SellDate));
|
|
continue;
|
|
}
|
|
}
|
|
Price price=GetPrice(mmCandidate.Symbol,tradeDate);
|
|
if(null==price)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[BuyCandidates] Cannot locate a price for {0} on {1}, skipping purchase.",mmCandidate.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
|
|
continue;
|
|
}
|
|
Position position=new Position();
|
|
position.Symbol=mmCandidate.Symbol;
|
|
position.PurchaseDate=tradeDate;
|
|
position.PurchasePrice=price.Close;
|
|
position.CurrentPrice=double.NaN;
|
|
position.Volume=mmCandidate.Volume;
|
|
position.Volatility=mmCandidate.Volatility;
|
|
if(ActivePositions.Count+positions.Count>=MaxOpenPositions) break;
|
|
RemoveCandidate(mmCandidate);
|
|
positions.Add(position);
|
|
positionCount++;
|
|
if(positionCount>=MaxDailyPositions) break;
|
|
}
|
|
positions=PerformPositionSizing(positions,cash,tradeDate);
|
|
if(null==positions||0==positions.Count)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**** NO ENTRY POINTS FOUND ON TRADE DATE:{0} ****",tradeDate.ToShortDateString()));
|
|
return positions;
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BOUGHT {0} POSITIONS, TRADE DATE:{1}, EXPOSURE:{2}",positions.Count,tradeDate.ToShortDateString(),Utility.FormatCurrency(positions.Sum(x => x.Exposure))));
|
|
return positions;
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BuyPositions: Exception:{0}",exception.ToString()));
|
|
return null;
|
|
}
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ***************************************************************** P O S I T I O N S I Z I N G **************************************************
|
|
// ***************************************************************************************************************************************************
|
|
|
|
private Positions PerformPositionSizing(Positions positions,double availableCash,DateTime tradeDate)
|
|
{
|
|
return PerformPositionSizingTotalRisk(positions, availableCash, tradeDate);
|
|
}
|
|
|
|
private Positions PerformPositionSizingTotalRisk(Positions positions,double availableCash,DateTime tradeDate)
|
|
{
|
|
Positions acceptedPositions=new Positions();
|
|
if(null==positions||0==positions.Count) return acceptedPositions;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("PERFORM POSITION SIZING TOTAL RISK : Positions:{0} Cash:{1}",positions.Count,Utility.FormatCurrency(availableCash)));
|
|
foreach(Position position in positions)
|
|
{
|
|
position.PositionRiskPercentDecimal=Parameters.PositionRiskPercentDecimal;
|
|
position.R=position.PositionRiskPercentDecimal*position.PurchasePrice;
|
|
position.C=Parameters.TotalRiskPercentDecimal*availableCash;
|
|
position.Shares=Math.Floor(position.P); // P=SHARES; P=C/R
|
|
position.InitialStopLimit=position.PurchasePrice-position.R;
|
|
position.TrailingStopLimit=position.InitialStopLimit;
|
|
if(position.Shares<=0)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Number of shares is zero for {0}",position.Symbol));
|
|
continue;
|
|
}
|
|
if(position.Exposure>availableCash)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Insufficient funds to purchase {0} shares of {1} at {2} per share with total exposure {3}. Available cash is {4}",
|
|
position.Shares,position.Symbol,Utility.FormatCurrency(position.PurchasePrice),Utility.FormatCurrency(position.Exposure),Utility.FormatCurrency(availableCash)));
|
|
continue;
|
|
}
|
|
availableCash-=position.Exposure;
|
|
acceptedPositions.Add(position);
|
|
}
|
|
return acceptedPositions;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ************************************************************** S T O P L I M I T S *************************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private void EvaluateStopPrice(DateTime tradeDate,Price currentPrice,Position position)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("EvaluateStopPrice: {0} on {1}",position.Symbol,tradeDate.ToShortDateString()));
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
Prices prices=GBPriceCache.GetInstance().GetPrices(position.Symbol,tradeDate,Parameters.PriceTrendDays); // only adjust stops if we are trending up
|
|
PriceTrendIndicatorResult priceTrendIndicatorResult=PriceTrendIndicator.IsUptrend(prices,Parameters.PriceTrendDays);
|
|
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,Parameters.PriceTrendDays));
|
|
return;
|
|
}
|
|
double trailingStop=position.InitialStopLimit+Math.Floor((currentPrice.Low-position.PurchasePrice)/position.R)*position.R; // where R = Risk Per Share in $
|
|
double trailingStopScaled=trailingStop;
|
|
double daysHeld=Math.Abs(dateGenerator.DaysBetweenActual(position.PurchaseDate,tradeDate));
|
|
if(Utility.IsEpoch(position.LastStopAdjustment)) // we've never adjusted the stop price
|
|
{
|
|
if(daysHeld>=Parameters.MinDaysBetweenInitialStopAdjustment)
|
|
{
|
|
if(Parameters.UseStopLimitScaling)
|
|
{
|
|
if(Parameters.StopLimitScalingType.Equals("AverageTrueRange")) trailingStopScaled=GetStopLimitWithScalingAverageTrueRange(tradeDate,currentPrice,position,trailingStop);
|
|
else
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Invalid StopLimitScalingType({0}). No scaling will be applied",Parameters.StopLimitScalingType));
|
|
trailingStopScaled=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;
|
|
}
|
|
if(Numerics.Round(trailingStop).Equals(Numerics.Round(position.TrailingStopLimit)))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be the same as the existing stop limit of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(position.TrailingStopLimit)));
|
|
return;
|
|
}
|
|
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)));
|
|
StopLimit newStopLimit=new StopLimit
|
|
{
|
|
Symbol=position.Symbol,
|
|
AnalysisDate=tradeDate,
|
|
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=tradeDate;
|
|
}
|
|
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,Parameters.MinDaysBetweenInitialStopAdjustment));
|
|
}
|
|
else // we have already made prior stop adjustments
|
|
{
|
|
int daysSinceLastStopAdjustment=Math.Abs(dateGenerator.DaysBetweenActual(position.LastStopAdjustment,tradeDate));
|
|
if(daysSinceLastStopAdjustment>=Parameters.MinDaysBetweenStopAdjustments)
|
|
{
|
|
if(Parameters.UseStopLimitScaling)
|
|
{
|
|
if(Parameters.StopLimitScalingType.Equals("AverageTrueRange")) trailingStopScaled=GetStopLimitWithScalingAverageTrueRange(tradeDate,currentPrice,position,trailingStop);
|
|
else
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Invalid StopLimitScalingType({0}). No scaling will be applied",Parameters.StopLimitScalingType));
|
|
trailingStopScaled=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;
|
|
}
|
|
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)));
|
|
StopLimit newStopLimit=new StopLimit
|
|
{
|
|
Symbol=position.Symbol,
|
|
AnalysisDate=tradeDate,
|
|
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=tradeDate;
|
|
}
|
|
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,Parameters.MinDaysBetweenStopAdjustments));
|
|
}
|
|
}
|
|
}
|
|
private double GetStopLimitWithScalingAverageTrueRange(DateTime tradeDate,Price currentPrice,Position position,double stopLimitNonScaled)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GetStopLimitWithScalingAverageTrueRange: Symbol:{0} RMultiple={1}",position.Symbol,position.RMultiple));
|
|
double volatility=double.NaN;
|
|
if(Parameters.UseProfitMaximization)
|
|
{
|
|
// double unadjustedStop=double.NaN;
|
|
// double adjustedStop=double.NaN;
|
|
|
|
CodeRunner codeRunner=new CodeRunner();
|
|
SymbolTable symbolTable=codeRunner.SymbolTable;
|
|
symbolTable.AddObjects(new Object[]{position}.ToList());
|
|
codeRunner.Execute(Parameters.UseProfitMaximizationExpression);
|
|
double multiplier=codeRunner.GetValue<double>("MULTIPLIER");
|
|
|
|
// volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,tradeDate,Parameters.StopLimitScalingVolatilityDays);
|
|
// unadjustedStop=currentPrice.Low-volatility;
|
|
|
|
volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,tradeDate,Parameters.StopLimitScalingVolatilityDays,multiplier);
|
|
// adjustedStop=currentPrice.Low-volatility;
|
|
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using profit maximization strategy for {0} which has RMultiple={1}. Multiplier:{2}",position.Symbol,position.RMultiple,Utility.FormatNumber(multiplier,3)));
|
|
}
|
|
else
|
|
{
|
|
volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,tradeDate,Parameters.StopLimitScalingVolatilityDays);
|
|
}
|
|
// double stopLimit=stopLimitNonScaled;
|
|
if(double.IsNaN(volatility))
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to calculate StopLimit for 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;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ************************************************************* M A N A G E S E T U P S ***********************************************************
|
|
// ***************************************************************************************************************************************************
|
|
private void ManageCandidates(DateTime tradeDate)
|
|
{
|
|
List<String> symbolsHeld=new List<String>();
|
|
ExpireCandidates(tradeDate);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate:{0}. There are {1} candidates in the candidate pool.",tradeDate.ToShortDateString(),Candidates.Count));
|
|
CMTGeneratorResult cmtGeneratorResult=CMTTrendGenerator.GenerateCMTCandidates(tradeDate,Parameters,symbolsHeld);
|
|
if(!cmtGeneratorResult.Success)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GenerateCMTCandidates failed with message {0}",cmtGeneratorResult.LastMessage));
|
|
return;
|
|
}
|
|
for(int index=0;index<cmtGeneratorResult.CMTCandidates.Count;index++)
|
|
{
|
|
CMTCandidate mmCandidate=cmtGeneratorResult.CMTCandidates[index];
|
|
AddCandidate(mmCandidate);
|
|
}
|
|
}
|
|
private void ExpireCandidates(DateTime tradeDate)
|
|
{
|
|
List<CMTCandidate> candidatesToRemove=new List<CMTCandidate>();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
if(null==Candidates||0==Candidates.Count) return;
|
|
foreach(CMTCandidate candidate in Candidates)
|
|
{
|
|
if(Math.Abs(dateGenerator.DaysBetweenActual(tradeDate,candidate.AnalysisDate))>Parameters.CandidateExpiryDays) candidatesToRemove.Add(candidate);
|
|
}
|
|
foreach(CMTCandidate candidate in candidatesToRemove) Candidates.Remove(candidate);
|
|
}
|
|
private void AddCandidate(CMTCandidate candidate)
|
|
{
|
|
if(null==Candidates) Candidates=new CMTCandidates();
|
|
if(Candidates.Any(x => x.Symbol.Equals(candidate.Symbol))) return;
|
|
Candidates.Add(candidate);
|
|
}
|
|
private void RemoveCandidate(CMTCandidate candidate)
|
|
{
|
|
if(null==Candidates) Candidates=new CMTCandidates();
|
|
if(!Candidates.Any(x => x.Symbol.Equals(candidate.Symbol))) return;
|
|
Candidates.Remove(Candidates.Where(x => x.Symbol.Equals(candidate.Symbol)).FirstOrDefault());
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ************************************************************************ M A R K E T C O N D I T I O N S ***************************************
|
|
// ***************************************************************************************************************************************************
|
|
private static bool IsTradeableMarket(DateTime tradeDate,CMTParams cmtParams)
|
|
{
|
|
Func<DateTime,CMTParams,bool>[] indicators = {IsTradeableMarketIndicator, IsTradeableVolatilityEnvironment};
|
|
return indicators.All(x => x.Invoke(tradeDate, cmtParams));
|
|
}
|
|
|
|
private static bool IsTradeableMarketIndicator(DateTime tradeDate,CMTParams cmtParams)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableMarketIndicator(setting):{0}",cmtParams.UseMarketIndicator?"Active":"Disabled"));
|
|
if(!cmtParams.UseMarketIndicator) return true;
|
|
bool result=true;
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
Prices benchmarkPrices=null;
|
|
double benchmarkDMA=double.NaN;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableMarketIndicator: Benchmark:{0} Horizon:{1} Moving Average Days:{2}",cmtParams.Benchmark,cmtParams.BenchmarkMovingAverageHorizon,cmtParams.BenchmarkMovingAverageDays));
|
|
List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(tradeDate,cmtParams.BenchmarkMovingAverageHorizon);
|
|
foreach(DateTime historicalDate in historicalDates)
|
|
{
|
|
benchmarkPrices=GBPriceCache.GetInstance().GetPrices(cmtParams.Benchmark,historicalDate,cmtParams.BenchmarkMovingAverageDays+20);
|
|
benchmarkPrices=new Prices(benchmarkPrices.Take(cmtParams.BenchmarkMovingAverageDays).ToList());
|
|
DMAPrices benchmarkDMAPrices=MovingAverageGenerator.GenerateMovingAverage(benchmarkPrices,benchmarkPrices.Count);
|
|
benchmarkDMA=benchmarkDMAPrices[0].AVGPrice;
|
|
if(benchmarkPrices[0].Close<benchmarkDMA)
|
|
{
|
|
result=false;
|
|
break;
|
|
}
|
|
}
|
|
if(!result)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableMarketIndicator:{0} Benchmark:{1} BenchmarkMovingAverageHorizon:{2} BenchmarkMovingAverageDays:{3} **",result,cmtParams.Benchmark,cmtParams.BenchmarkMovingAverageHorizon,cmtParams.BenchmarkMovingAverageDays));
|
|
}
|
|
return result;
|
|
}
|
|
// Determine volatility based on ^VIX bollinger L band break on the close within 60 days prior
|
|
private static bool IsTradeableVolatilityEnvironment(DateTime tradeDate,CMTParams cmtParams)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableVolatilityEnvironment(setting):{0}",cmtParams.UseMarketIndicatorVolatility?"Active":"Disabled"));
|
|
if(!cmtParams.UseMarketIndicatorVolatility) return true;
|
|
bool result=true;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableVolatilityEnvironment: Volatility Benchmark:{0} Volatility Horizon:{1}",cmtParams.UseMarketIndicatorVolatilityBenchmark,cmtParams.UseMarketIndicatorVolatilityHorizon));
|
|
DateTime bandBreakDate=BandBreakIndicator.IsLowerBandBreakClose(cmtParams.UseMarketIndicatorVolatilityBenchmark,tradeDate,cmtParams.UseMarketIndicatorVolatilityHorizon);
|
|
if(!(Utility.IsEpoch(bandBreakDate)))
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
int elapsedDays = dateGenerator.TradingDaysBetween(tradeDate,bandBreakDate);
|
|
int daysRemaining=cmtParams.UseMarketIndicatorVolatilityHorizon-elapsedDays;
|
|
result=false;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IsTradeableVolatilityEnvironment:{0}, VolatilityBenchmark:{1}, Violation/Band Break Date:{2}, Elapsed Days:{3}, Days Remaining:{4}, TradeDate:{5}",
|
|
result,cmtParams.UseMarketIndicatorVolatilityBenchmark,bandBreakDate.ToShortDateString(),elapsedDays,daysRemaining,tradeDate.ToShortDateString()));
|
|
}
|
|
return result;
|
|
}
|
|
// ***************************************************************************************************************************************************
|
|
// ************************************************************************ 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;
|
|
}
|
|
// *********************************************************************************************************************************************************************
|
|
// *********************************************************************************************************************************************************************
|
|
// *********************************************************************************************************************************************************************
|
|
public void DisplayRealtimeBlotter(DateTime tradeDate)
|
|
{
|
|
if(AllPositions.Count>0||ActivePositions.Count>0)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"***************************************************************************************************************************");
|
|
}
|
|
if(AllPositions.Count>0)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CLOSED POSITIONS ({0})...",AllPositions.Count));
|
|
Position.DisplayHeader();
|
|
foreach(Position position in AllPositions) position.Display();
|
|
}
|
|
if(ActivePositions.Count>0)
|
|
{
|
|
foreach(Position position in ActivePositions)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetPriceOrLatestAvailable(position.Symbol,tradeDate);
|
|
if(null==price) continue;
|
|
position.CurrentPrice=price.Close;
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("OPEN POSITIONS ({0})...",ActivePositions.Count));
|
|
Position.DisplayHeader();
|
|
foreach(Position position in ActivePositions) position.Display();
|
|
}
|
|
if(AllPositions.Count>0||ActivePositions.Count>0)
|
|
{
|
|
double totalGainLoss=GetRealtimeGainLoss(tradeDate);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE {0}",Utility.FormatCurrency(ActivePositions.GetExposure())));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL GAIN LOSS {0}",Utility.FormatCurrency(totalGainLoss)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CASH BALANCE {0}",Utility.FormatCurrency(CashBalance)));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"***************************************************************************************************************************");
|
|
}
|
|
}
|
|
public double GetRealtimeGainLoss(DateTime tradeDate)
|
|
{
|
|
int count=ActivePositions.Count;
|
|
double gainLoss=0.00;
|
|
|
|
foreach(Position position in AllPositions) gainLoss+=position.GainLoss;
|
|
foreach(Position position in ActivePositions)
|
|
{
|
|
Price price=GBPriceCache.GetInstance().GetPriceOrLatestAvailable(position.Symbol,tradeDate);
|
|
gainLoss+=(price.Close*position.Shares)-(position.PurchasePrice*position.Shares);
|
|
}
|
|
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 DisplayBalanceFromAllPositions()
|
|
{
|
|
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(AllPositions.GetExposure())),
|
|
Utility.AddQuotes(Utility.FormatCurrency(AllPositions.GetGainLoss())),
|
|
Utility.AddQuotes(Utility.FormatPercent(AllPositions.GetGainLossPercent())),
|
|
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
|
|
Utility.AddQuotes(Utility.FormatCurrency(CashBalance))));
|
|
}
|
|
// ****************************************************************************************************************************************
|
|
// ************************************************************* C O N T R O L T O D A Y ***********************************************
|
|
// ****************************************************************************************************************************************
|
|
public DateTime Today()
|
|
{
|
|
return DateTime.Now;
|
|
}
|
|
|
|
// Ensure that the given trade date is the next anticipated trade date given in the session file. (i.e.) TradeDate=SessionFile.TradeDate+1
|
|
private CMTTrendModelResult CheckSequentialTradeDate(DateTime tradeDate,DateTime sessionParamsTradeDate)
|
|
{
|
|
CMTTrendModelResult result=new CMTTrendModelResult();
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
DateTime nextBusinessDay=dateGenerator.FindNextBusinessDay(sessionParamsTradeDate);
|
|
if(!nextBusinessDay.Date.Equals(tradeDate.Date))
|
|
{
|
|
String strMessage=String.Format("****** The supplied trade date does not match the next business date in the trade file. Given:{0}, Expected:{1} ******",tradeDate.Date.ToShortDateString(),nextBusinessDay.ToShortDateString());
|
|
result.Message=strMessage;
|
|
result.Success=false;
|
|
return result;
|
|
}
|
|
result.Success=true;
|
|
return result;
|
|
}
|
|
// ****************************************************************************************************************************************
|
|
// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
|
|
// ****************************************************************************************************************************************
|
|
public CMTSessionParams RestoreSession()
|
|
{
|
|
try
|
|
{
|
|
if(!CMTSessionManager.SessionAvailable(PathSessionFileName)) return null;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName));
|
|
CMTSessionParams sessionParams=CMTSessionManager.RestoreSession(PathSessionFileName);
|
|
TradeDate=sessionParams.TradeDate;
|
|
if(TradeDate.Date<AnalysisDate.Date) TradeDate=AnalysisDate;
|
|
StartDate=sessionParams.StartDate;
|
|
Parameters=sessionParams.CMTParams;
|
|
PricingExceptions=sessionParams.PricingExceptions;
|
|
ActivePositions=sessionParams.ActivePositions;
|
|
AllPositions=sessionParams.AllPositions;
|
|
Candidates=sessionParams.Candidates;
|
|
StopLimits=sessionParams.StopLimits;
|
|
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));
|
|
CMTSessionParams sessionParams=new CMTSessionParams();
|
|
sessionParams.LastUpdated=Today();
|
|
sessionParams.TradeDate=TradeDate;
|
|
sessionParams.StartDate=StartDate;
|
|
sessionParams.AnalysisDate=AnalysisDate;
|
|
sessionParams.CMTParams=Parameters;
|
|
sessionParams.PricingExceptions=PricingExceptions;
|
|
sessionParams.StopLimits=StopLimits;
|
|
sessionParams.ActivePositions=ActivePositions;
|
|
sessionParams.AllPositions=AllPositions;
|
|
sessionParams.Candidates=Candidates;
|
|
sessionParams.CashBalance=CashBalance;
|
|
sessionParams.NonTradeableCash=NonTradeableCash;
|
|
CMTSessionManager.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));
|
|
CMTSessionParams sessionParams=new CMTSessionParams();
|
|
sessionParams.LastUpdated=Today();
|
|
sessionParams.TradeDate=TradeDate;
|
|
sessionParams.StartDate=StartDate;
|
|
sessionParams.AnalysisDate=AnalysisDate;
|
|
sessionParams.CMTParams=Parameters;
|
|
sessionParams.PricingExceptions=PricingExceptions;
|
|
sessionParams.StopLimits=StopLimits;
|
|
sessionParams.ActivePositions=ActivePositions;
|
|
sessionParams.AllPositions=AllPositions;
|
|
sessionParams.Candidates=Candidates;
|
|
sessionParams.CashBalance=CashBalance;
|
|
return CMTSessionManager.SaveSession(sessionParams,backupFileName);
|
|
}
|
|
}
|
|
}
|