diff --git a/MarketData/MarketData/ModelHelper/MGMomentumHelper.cs b/MarketData/MarketData/ModelHelper/MGMomentumHelper.cs
new file mode 100755
index 0000000..8dd4243
--- /dev/null
+++ b/MarketData/MarketData/ModelHelper/MGMomentumHelper.cs
@@ -0,0 +1,129 @@
+using MarketData.Generator.Momentum;
+
+namespace MarketData.ModelHelper
+{
+ public static class MGMomentumHelper
+ {
+ ///
+ /// MGGAINLOSS /SESSIONFILE:{PATHSESSIONFILE} (i.e.) MGGAINLOSS /SESSIONFILE:C:\boneyard\marketdata\bin\Debug\saferun\MG20180131.txt");
+ ///
+ ///
+ public static void RunMGGainLoss(CommandArgs commandArgs)
+ {
+ if(!commandArgs.Has("SESSIONFILE")) { MDTrace.WriteLine(LogLevel.DEBUG,"Missing SESSIONFILE"); return; }
+ MomentumBacktest momentumBacktest = new MomentumBacktest();
+ MomentumBacktest.DisplayGainLoss(commandArgs.Coalesce("SESSIONFILE"));
+ }
+ ///
+ /// MGSESSION /SESSIONFILE:
+ ///
+ ///
+ public static void RunMGSession(CommandArgs commandArgs)
+ {
+ if(!commandArgs.Has("SESSIONFILE")) { MDTrace.WriteLine(LogLevel.DEBUG,"Missing SESSIONFILE"); return; }
+ MomentumBacktest momentumBacktest = new MomentumBacktest();
+ momentumBacktest.DisplaySession(commandArgs.Coalesce("SESSIONFILE"));
+ }
+
+ ///
+ /// RUNBACKTEST /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{ENDDATE}: /{SESSIONFILE}:
+ ///
+ ///
+ public static void RunBacktest(CommandArgs commandArgs)
+ {
+ MGConfiguration mgParams=new MGConfiguration();
+ if (!commandArgs.Has("STARTDATE,MAXPOSITIONS,INITIALCASH,HOLDINGPERIOD"))
+ {
+ if (!commandArgs.Has("STARTDATE")) MDTrace.WriteLine(LogLevel.DEBUG, "Missing STARTDATE");
+ if (!commandArgs.Has("MAXPOSITIONS")) MDTrace.WriteLine(LogLevel.DEBUG, "Missing MAXPOSITIONS");
+ if (!commandArgs.Has("INITIALCASH")) MDTrace.WriteLine(LogLevel.DEBUG, "Missing INITIALCASH");
+ if (!commandArgs.Has("HOLDINGPERIOD")) MDTrace.WriteLine(LogLevel.DEBUG, "Missing HOLDINGPERIOD");
+ return;
+ }
+ mgParams.MaxPositions=commandArgs.Coalesce("MAXPOSITIONS");
+ mgParams.InitialCash=commandArgs.Coalesce("INITIALCASH");
+ mgParams.HoldingPeriod=commandArgs.Coalesce("HOLDINGPERIOD");
+
+ if(commandArgs.Has("INCLUDETRADEMASTERFORSYMBOLSHELD"))
+ {
+ mgParams.IncludeTradeMasterForSymbolsHeld=commandArgs.Get("INCLUDETRADEMASTERFORSYMBOLSHELD");
+ }
+
+ if(commandArgs.Has("USESTOCHASTICS"))
+ {
+ mgParams.UseStochastics=commandArgs.Coalesce("USESTOCHASTICS",true);
+ }
+
+// ** M A C D
+ if(commandArgs.Has("USEMACD"))
+ {
+ mgParams.UseMACD=commandArgs.Coalesce("USEMACD",true);
+ }
+ if(commandArgs.Has("MACDREJECTSTRONGSELLSIGNALS"))
+ {
+ mgParams.MACDRejectStrongSellSignals=commandArgs.Coalesce("MACDREJECTSTRONGSELLSIGNALS",true);
+ }
+ if(commandArgs.Has("MACDREJECTWEAKSELLSIGNALS"))
+ {
+ mgParams.MACDRejectWeakSellSignals=commandArgs.Coalesce("MACDREJECTWEAKSELLSIGNALS",true);
+ }
+ if(commandArgs.Has("MACDSIGNALDAYS"))
+ {
+ mgParams.MACDSignalDays=commandArgs.Coalesce("MACDSIGNALDAYS",mgParams.MACDSignalDays);
+ }
+ if(commandArgs.Has("MACDSETUP"))
+ {
+ mgParams.MACDSetup=commandArgs.Coalesce("MACDSETUP",mgParams.MACDSetup);
+ }
+// **
+ QualityIndicator qualityIndicator=new QualityIndicator(QualityIndicator.QualityType.IDIndicator);
+ if(commandArgs.Has("QUALITYINDICATORTYPE")) qualityIndicator.Quality=QualityIndicator.ToQuality(commandArgs.Coalesce("QUALITYINDICATORTYPE","IDINDICATOR"));
+ mgParams.QualityIndicatorType=qualityIndicator.ToString();
+
+ mgParams.UseLowSlopeBetaCheck=true;
+ if(commandArgs.Has("USELOWSLOPEBETACHECK")) mgParams.UseLowSlopeBetaCheck=commandArgs.Coalesce("USELOWSLOPEBETACHECK",true);
+
+ DateTime startDate = commandArgs.Coalesce("STARTDATE");
+ DateTime endDate=commandArgs.Coalesce("ENDDATE",new DateTime());
+
+ String pathSessionFileName = commandArgs.Coalesce("SESSIONFILE", null);
+ if(null!=pathSessionFileName)pathSessionFileName=pathSessionFileName.Trim();
+
+ mgParams.DisplayHeader();
+ List results=new List();
+ MomentumBacktest backtestMomentum=new MomentumBacktest();
+ results.Add(backtestMomentum.PerformBacktest(startDate,endDate,pathSessionFileName,mgParams));
+ }
+ ///
+ /// RUNMOMENTUM /STARTDATE: /MAXPOSITIONS:
+ ///
+ ///
+ public static void RunMomentum(CommandArgs commandArgs)
+ {
+ if (!commandArgs.Has("STARTDATE,MAXPOSITIONS")) return;
+ DateTime analysisDate = commandArgs.Coalesce("STARTDATE");
+ int maxPositions = commandArgs.Coalesce("MAXPOSITIONS");
+ MGConfiguration config = new MGConfiguration();
+ MomentumCandidates momentumCandidates = MomentumGenerator.GenerateMomentum(analysisDate, config);
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0}", MomentumCandidate.Header()));
+ for (int index = 0; index < momentumCandidates.Count; index++)
+ {
+ MomentumCandidate momentumCandidate = momentumCandidates[index];
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0}", momentumCandidate.ToString()));
+ }
+ }
+
+ ///
+ /// MGLIQUIDATE /SESSIONFILE: /TRADEDATE:
+ ///
+ ///
+ public static void RunMGLiquidate(CommandArgs commandArgs)
+ {
+ DateTime? tradeDate = null;
+ if (!commandArgs.Has("SESSIONFILE")) return;
+ if (commandArgs.Has("TRADEDATE")) tradeDate = commandArgs.Coalesce("TRADEDATE");
+ MomentumBacktest momentumBacktest = new MomentumBacktest();
+ momentumBacktest.MGLiquididate(commandArgs.Coalesce("SESSIONFILE"), tradeDate);
+ }
+ }
+}
diff --git a/MarketData/MarketData/Models/runcmmomentumeom.sh b/MarketData/MarketData/Models/runcmmomentumeom.sh
new file mode 100755
index 0000000..2a106c8
--- /dev/null
+++ b/MarketData/MarketData/Models/runcmmomentumeom.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+echo "Starting CMMomentum end of month process."
+export DOTNET_ROOT=/opt/dotnet
+PATHMODEL="/home/pi/ARM64/MarketData/MarketData/Models/CM20191031.TXT"
+/home/pi/ARM64/MarketData/MarketData/bin/Debug/net8.0/mk RUNCMBACKTEST /STARTDATE:10-31-2019 /MAXPOSITIONS:3 /INITIALCASH:5000 /HOLDINGPERIOD:3 /TARGETBETA:1 /SESSIONFILE:$PATHMODEL
+
diff --git a/MarketData/MarketData/Models/runmomentumeom.sh b/MarketData/MarketData/Models/runmomentumeom.sh
new file mode 100755
index 0000000..dd8eea0
--- /dev/null
+++ b/MarketData/MarketData/Models/runmomentumeom.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo "Starting MGMomentum end of month process."
+export DOTNET_ROOT=/opt/dotnet
+PATHMODEL="/home/pi/ARM64/MarketData/MarketData/Models/MG20180131.TXT"
+/home/pi/ARM64/MarketData/MarketData/bin/Debug/net8.0/mk RUNBACKTEST /STARTDATE:01-31-2018 /MAXPOSITIONS:3 /INITIALCASH:10000 /HOLDINGPERIOD:3 /INTRADAYMODE:FALSE /SESSIONFILE:$PATHMODEL
diff --git a/MarketData/MarketData/Services/MainService.cs b/MarketData/MarketData/Services/MainService.cs
index 1c5024b..2a4de61 100755
--- a/MarketData/MarketData/Services/MainService.cs
+++ b/MarketData/MarketData/Services/MainService.cs
@@ -46,8 +46,11 @@ namespace MarketData.Services
MDTrace.WriteLine(LogLevel.DEBUG,"CMTSESSION /SESSIONFILE:{pathfilename} Runs Mark Minervini trend display session");
MDTrace.WriteLine(LogLevel.DEBUG, "RUNCMBACKTEST /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{USEBINBASEDPOSITIONSIZING}: /{USEBINBASEDPOSITIONSIZINGNUMBINS}: /{TARGETBETA}: /{ENDDATE}: /SESSIONFILE: /{USECNN}: /{USECNNHOST}: /{USECNNDAYCOUNT}:");
MDTrace.WriteLine(LogLevel.DEBUG, "CMSESSION /SESSIONFILE:");
- MDTrace.WriteLine(LogLevel.DEBUG, "CMCANDIDATELASTRESORT /TRADEDATE:");
+ MDTrace.WriteLine(LogLevel.DEBUG, "CMCANDIDATELASTRESORT /TRsADEDATE:");
MDTrace.WriteLine(LogLevel.DEBUG, @"CMGAINLOSS /SESSIONFILE:{PATHSESSIONFILE} (i.e.) CMGAINLOSS /SESSIONFILE:C:\boneyard\marketdata\bin\Debug\saferun\CM20191031.txt");
+ MDTrace.WriteLine(LogLevel.DEBUG,"MGSESSION /SESSIONFILE:");
+ MDTrace.WriteLine(LogLevel.DEBUG,@"MGGAINLOSS /SESSIONFILE:{PATHSESSIONFILE} (i.e.) MGGAINLOSS /SESSIONFILE:C:\boneyard\marketdata\bin\Debug\saferun\MG20180131.txt");
+ MDTrace.WriteLine(LogLevel.DEBUG,"RUNBACKTEST /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{ENDDATE}: /{SESSIONFILE}:");
MDTrace.WriteLine(LogLevel.DEBUG,"ECHO {param1} {param2} {param(n)");
}
@@ -86,6 +89,9 @@ namespace MarketData.Services
tasks.Add("CMSESSION",TaskCMMRunCMSession);
tasks.Add("CMCANDIDATELASTRESORT",TaskCMMRunCMCandidateLastResort);
tasks.Add("CMGAINLOSS",TaskCMMRunCMGainLoss);
+ tasks.Add("MGSESSION",TaskMGRunMGSession);
+ tasks.Add("MGGAINLOSS",TaskMGRunMGGainLoss);
+ tasks.Add("RUNBACKTEST",TaskMGRunMGBacktest);
tasks.Add("ECHO",TaskEcho);
GlobalConfig.Instance.Configuration = configuration; // This call sets up configuration stuff so it needs to be first.
@@ -352,8 +358,25 @@ namespace MarketData.Services
{
CMMomentumHelper.RunCMGainLoss(commandArgs);
await Task.FromResult(true);
- }
+ }
+ public async Task TaskMGRunMGSession(CommandArgs commandArgs)
+ {
+ MGMomentumHelper.RunMGSession(commandArgs);
+ await Task.FromResult(true);
+ }
+
+ public async Task TaskMGRunMGGainLoss(CommandArgs commandArgs)
+ {
+ MGMomentumHelper.RunMGGainLoss(commandArgs);
+ await Task.FromResult(true);
+ }
+
+ public async Task TaskMGRunMGBacktest(CommandArgs commandArgs)
+ {
+ MGMomentumHelper.RunBacktest(commandArgs);
+ await Task.FromResult(true);
+ }
// *********************************************************************************************************************************************************
// ******************************************************************* E N D T A S K S ********************************************************************
diff --git a/MarketData/MarketDataLib/Generator/Momentum/ActivePositions.cs b/MarketData/MarketDataLib/Generator/Momentum/ActivePositions.cs
new file mode 100755
index 0000000..c1e93ea
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/ActivePositions.cs
@@ -0,0 +1,175 @@
+using MarketData.Utils;
+
+namespace MarketData.Generator.Momentum
+{
+ public class ActivePositions : Dictionary
+ {
+ public ActivePositions()
+ {
+ }
+ public double GetExposure()
+ {
+ double exposure=0.00;
+ List keys = new List(Keys);
+ for(int index=0;index positions=this[keys[index]];
+ if(null==positions||0==positions.Count)continue;
+ exposure+=(from Position position in positions select position.Exposure).Sum();
+ }
+ return exposure;
+ }
+ public List GetSymbols()
+ {
+ return SymbolsHeld();
+ }
+ public Position FindPosition(String symbol,DateTime purchaseDate)
+ {
+ Position foundPosition=null;
+ List keys=new List(this.Keys);
+ for(int index=0;index keys=new List(this.Keys);
+ for(int index=0;index keys = new List(this.Keys);
+ for (int index = 0; index < keys.Count; index++)
+ {
+ Positions positions = this[keys[index]];
+ foreach (Position slotPosition in positions)
+ {
+ if (slotPosition == searchPosition)
+ {
+ positions.Remove(searchPosition);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public Positions GetPositions()
+ {
+ Positions positionsCollection=new Positions();
+ List keys=new List(this.Keys);
+ Dictionary symbols=new Dictionary();
+ for(int index=0;index SymbolsHeld()
+ {
+ List keys = new List(this.Keys);
+ Dictionary symbols = new Dictionary();
+ for (int index = 0; index < keys.Count; index++)
+ {
+ Positions positions = this[keys[index]];
+ foreach (Position position in positions)
+ {
+ if (!symbols.ContainsKey(position.Symbol)) symbols.Add(position.Symbol, position.Symbol);
+ }
+ }
+ return new List(symbols.Keys);
+ }
+ public double GetMarketValue()
+ {
+ int count=Count;
+ double marketValue=0.00;
+ List keys=new List(this.Keys);
+ for(int index=0;index positions=this[keys[index]];
+ if(null==positions||0==positions.Count)continue;
+ marketValue+=(from Position position in positions select position.MarketValue).Sum();
+ }
+ return marketValue;
+ }
+ public double GetGainLoss()
+ {
+ int count=Count;
+ double marketValue=0.00;
+ double exposure=0.00;
+
+ List keys = new List(this.Keys);
+ for(int index=0;index positions=this[keys[index]];
+ if(null==positions||0==positions.Count)continue;
+ exposure+=(from Position position in positions select position.Exposure).Sum();
+ marketValue+=(from Position position in positions select position.MarketValue).Sum();
+ }
+ return marketValue-exposure;
+ }
+ public double GetGainLossPercent()
+ {
+ double exposure=GetExposure();
+ double marketValue=GetMarketValue();
+ if(0.00==exposure)return exposure;
+ return (marketValue-exposure)/exposure;
+ }
+ public void Display()
+ {
+ List keys = new List(this.Keys);
+ for(int index=0;index ToNVPCollections()
+ {
+ List keys=new List(Keys);
+ List nvpCollectionsList=new List();
+ for(int index=0;index 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(MGSessionParams sessionParams)
+ {
+ Profiler profiler=new Profiler();
+ ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
+ DateGenerator dateGenerator=new DateGenerator();
+ try
+ {
+ if(null==sessionParams)return null;
+ MarketData.Generator.Momentum.Positions combinedPositions=sessionParams.GetCombinedPositions();
+
+ DateTime minDate=combinedPositions.Min(x => x.PurchaseDate);
+ DateTime maxDate=PricingDA.GetLatestDate();
+ double prevGainLoss=double.NaN;
+ LocalPriceCache.GetInstance().RemoveDate(maxDate);
+ List historicalDates=dateGenerator.GenerateHistoricalDates(minDate,maxDate);
+ foreach(DateTime currentDate in historicalDates)
+ {
+ MarketData.Generator.Momentum.Positions openPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
+ MarketData.Generator.Momentum.Positions closedPositions=new MarketData.Generator.Momentum.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.Momentum.Position openPosition in openPositions)
+ {
+ exposure+=openPosition.Shares*openPosition.PurchasePrice;
+ if(!LocalPriceCache.GetInstance().ContainsPrice(openPosition.Symbol,currentDate))
+ {
+ Prices prices=PricingDA.GetPricesForward(openPosition.Symbol,currentDate,PricingDA.ForwardLookingDays);
+ LocalPriceCache.GetInstance().Add(prices);
+ }
+ Price price=LocalPriceCache.GetInstance().GetPrice(openPosition.Symbol,currentDate);
+ if(null==price)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No price for {0} on {1}",openPosition.Symbol,currentDate.ToShortDateString()));
+ }
+ else
+ {
+ gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
+ marketValue+=(price.Close*openPosition.Shares);
+ }
+ }
+ foreach(MarketData.Generator.Momentum.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;
+ MGSessionParams 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));
+ Configuration.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,"************** A C T I V E P O S I T I O N S *************");
+ ActivePositions.Display();
+ MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P G A I N E R S *************");
+ AllPositions.DisplayTopFive();
+ MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P L O S S E R S *************");
+ AllPositions.DisplayBottomFive();
+ DisplayBalanceFromPositions();
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StartDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("AnalysisDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Next Slot:{0}",Cycle));
+ }
+// ******************************************************************************************************************************************************
+//******************************************************* L I Q U I D A T E A L L P O S I T I O N S ***********************************************
+// ******************************************************************************************************************************************************
+ public void MGLiquididate(String pathSessionFile,DateTime? tradeDate)
+ {
+ if (null == pathSessionFile) return;
+ MGSessionParams sessionParams=null;
+
+ PathSessionFileName = pathSessionFile;
+ if (null == (sessionParams = RestoreSession()))
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
+ return;
+ }
+ MDTrace.WriteLine(LogLevel.DEBUG, "************** L I Q U I D A T E P O S I T I O N S *************");
+ if (null == ActivePositions || 0 == ActivePositions.Count)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("No active positions in file {0}", pathSessionFile));
+ return;
+ }
+ if(null==tradeDate)tradeDate = PricingDA.GetLatestDate(ActivePositions.GetSymbols());
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Trade date:{0}", Utility.DateTimeToStringMMHDDHYYYY(tradeDate.Value)));
+ for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
+ {
+ Positions slotPositions = ActivePositions[slotIndex];
+ SellPositions(slotPositions, tradeDate.Value);
+ MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
+ slotPositions.Display();
+ AllPositions.Add(slotPositions);
+ CashBalance += slotPositions.MarketValue;
+ ActivePositions[slotIndex].Clear();
+ }
+ GBPriceCache.GetInstance().Dispose();
+ SaveSession();
+ }
+// ******************************************************************************************************************************************************
+// ****************************************************************** C L O S E **********************************************************************
+// ******************************************************************************************************************************************************
+ public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double sellPrice,String pathSessionFile)
+ {
+ if(null==pathSessionFile) return false;
+ MGSessionParams sessionParams=null;
+
+ PathSessionFileName=pathSessionFile;
+ if(null==(sessionParams=RestoreSession()))
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
+ return false;
+ }
+ if(!BackupSession()) return false;
+ Positions activePositions = ActivePositions.GetPositions();
+ 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 = sellPrice;
+ CashBalance += position.MarketValue;
+ SaveSession();
+ return true;
+ }
+ position.SellDate = sellDate;
+ position.CurrentPrice = sellPrice;
+ 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,String pathSessionFile)
+ {
+ if(null==pathSessionFile) return false;
+ PathSessionFileName=pathSessionFile;
+ MGSessionParams sessionParams=null;
+ if(null==(sessionParams=RestoreSession()))
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
+ return false;
+ }
+ if(!BackupSession()) return false;
+
+ Positions activePositions = ActivePositions.GetPositions();
+
+ 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.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)));
+ CashBalance+=(position.PurchasePrice-purchasePrice)*position.Shares;
+ 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;
+ }
+
+// ******************************************************************************************************************************************************
+// ****************************************************************** B A C K T E S T *****************************************************************
+// ******************************************************************************************************************************************************
+// Ideally, startDate should be November,February,May,August
+// paramStartDate is startDate {ORIGINAL START DATE}
+// paramAnalysisDate is endDate {TODAY}
+ public BacktestResult PerformBacktest(DateTime paramStartDate,DateTime paramAnalysisDate,String paramPathSessionFileName,MGConfiguration configuration)
+ {
+ BacktestResult backTestResult=new BacktestResult();
+ DateGenerator dateGenerator=new DateGenerator();
+ Configuration=configuration;
+ CashBalance=Configuration.InitialCash;
+ ActivePositions=new ActivePositions();
+ AllPositions=new Positions();
+ StartDate=paramStartDate;
+ TradeDate=paramStartDate;
+ AnalysisDate=paramAnalysisDate;
+ PathSessionFileName=paramPathSessionFileName;
+ MGSessionParams sessionParams=null;
+
+ Cycle=0;
+ if(AnalysisDate.Date>Today().Date)return backTestResult;
+ if(Utility.IsEpoch(AnalysisDate))AnalysisDate=Today();
+ TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate);
+ if(TradeDate>AnalysisDate)
+ {
+ int startMonth=StartDate.Month;
+ TimeSpan timeSpan=new TimeSpan();
+ if((new int[]{12,3,6,9}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(30,0,0,0);
+ else if((new int[]{1,4,7,10}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(60,0,0,0);
+ else if((new int[]{2,5,8,11}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(90,0,0,0);
+ StartDate=StartDate-timeSpan;
+ TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate);
+ }
+ if(null!=PathSessionFileName)sessionParams=RestoreSession();
+ if(null!=sessionParams)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}",paramPathSessionFileName,sessionParams.LastUpdated));
+ }
+ Configuration.DisplayConfiguration();
+ DisplayBalance();
+ while(true)
+ {
+ if(TradeDate>AnalysisDate)break;
+ int slotIndex=(int)(((double)Cycle)%((double)(HoldingPeriod)));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TRADE DATE {0} , ANALYSIS DATE {1}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate),Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
+ if(!ActivePositions.ContainsKey(slotIndex))
+ {
+ Positions positions = null;
+ positions=BuyPositions(TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld(TradeDate));
+ MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
+ if(CashBalance-positions.Exposure<0.00)
+ {
+ positions.Clear();
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
+ }
+ positions.Display();
+ ActivePositions.Add(slotIndex,positions);
+ CashBalance-=positions.Exposure;
+ DisplayBalance();
+ }
+ else
+ {
+ Positions slotPositions=ActivePositions[slotIndex];
+ SellPositions(slotPositions,TradeDate);
+ MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
+ slotPositions.Display();
+ AllPositions.Add(slotPositions);
+ CashBalance+=slotPositions.MarketValue;
+ ActivePositions[slotIndex].Clear();
+ DisplayBalance();
+ double cashAllocation=CashBalance;
+ cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / (double)HoldingPeriod);
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CASH ALLOCATION:{0}",Utility.FormatCurrency(cashAllocation)));
+ Positions positions = null;
+ positions=BuyPositions(TradeDate,AnalysisDate,cashAllocation,SymbolsHeld(TradeDate));
+ MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
+ positions.Display();
+ if(CashBalance-positions.Exposure<=0.00)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
+ break;
+ }
+ ActivePositions[slotIndex]=positions;
+ CashBalance-=positions.Exposure;
+ DisplayBalance();
+ }
+ Cycle++;
+ TradeDate=dateGenerator.GetNextMonthEnd(TradeDate);
+ if(TradeDate>AnalysisDate)break;
+ } // WHILE TRUE
+ MDTrace.WriteLine(LogLevel.DEBUG,"RUN COMPLETE.");
+ if(null!=PathSessionFileName)SaveSession();
+ for(int slotIndex=0;slotIndex SymbolsHeld(DateTime tradeDate)
+ {
+ List symbolsHeld=ActivePositions.SymbolsHeld();
+ if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld;
+ if(null == symbolsHeld)symbolsHeld=new List();
+ PortfolioTrades portfolioTrades=PortfolioDA.GetOpenTradesAsOf(tradeDate);
+ if(null == portfolioTrades || 0==portfolioTrades.Count)return symbolsHeld;
+ symbolsHeld.AddRange(portfolioTrades.Symbols.Distinct());
+ return symbolsHeld;
+ }
+// **********************************************************************************************************************************************************
+// **************************************************************** G E T E X P O S U R E / M A R K E T V A L U E*****************************************
+// **********************************************************************************************************************************************************
+ public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
+ {
+ int count=ActivePositions.Count;
+ double marketValue=0.00;
+ double exposure=0.00;
+ RealtimeGainLoss gainLoss=new RealtimeGainLoss();
+
+ for(int slotIndex=0;slotIndex positions=ActivePositions[slotIndex];
+ if(null==positions||0==positions.Count)continue;
+ foreach(Position position in positions)
+ {
+ Price price=GBPriceCache.GetInstance().GetRealtimePrice(position.Symbol);
+ if(null==price){MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot price {0} on {1}",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));continue;}
+ position.CurrentPrice=price.Close;
+ }
+ }
+ for(int slotIndex=0;slotIndex positions=ActivePositions[slotIndex];
+ if(null==positions||0==positions.Count)continue;
+ exposure+=(from Position position in positions select position.Exposure).Sum();
+ marketValue+=(from Position position in positions select position.MarketValue).Sum();
+ }
+ gainLoss.Exposure=exposure;
+ gainLoss.MarketValue=marketValue;
+ return gainLoss;
+ }
+// **************************************************************************************************************************************************
+// **************************************************************** S E L L P O S I T I O N S *****************************************************
+// ***************************************************************************************************************************************************
+ private void SellPositions(Positions positions,DateTime sellDate)
+ {
+ foreach(Position position in positions)
+ {
+ Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
+ position.SellDate=sellDate;
+ if(null==price)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
+ position.CurrentPrice=position.PurchasePrice;
+ }
+ else position.CurrentPrice=price.Close;
+ }
+ }
+
+ private void SellPosition(Position position,DateTime sellDate)
+ {
+ Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
+ position.SellDate=sellDate;
+ if(null==price)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
+ position.CurrentPrice=position.PurchasePrice;
+ }
+ else position.CurrentPrice=price.Close;
+ }
+// ***************************************************************************************************************************************************
+// ******************************************************** B U Y P O S I T I O N S *************************************************************
+// ***************************************************************************************************************************************************
+ private Positions BuyPositions(DateTime tradeDate, DateTime analysisDate, double cash, List symbolsHeld)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"**BUYPOSITIONS**");
+ Positions positions = new Positions();
+ int positionCount = 0;
+ if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash);
+ MomentumCandidates momentumCandidates = MomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration);
+ for (int index = 0; index < momentumCandidates.Count; index++)
+ {
+ MomentumCandidate momentumCandidate = momentumCandidates[index];
+ Price price = GBPriceCache.GetInstance().GetPrice(momentumCandidate.Symbol, tradeDate);
+ if (null == price)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", momentumCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
+ continue;
+ }
+ Position position = new Position();
+ position.Symbol = momentumCandidate.Symbol;
+ position.CumReturn252 = momentumCandidate.CumReturn252;
+ position.IDIndicator = momentumCandidate.IDIndicator;
+ position.Score=momentumCandidate.Score;
+ position.MaxDrawdown = momentumCandidate.MaxDrawdown;
+ position.MaxUpside = momentumCandidate.MaxUpside;
+ position.PE = momentumCandidate.PE;
+ position.Beta = momentumCandidate.Beta;
+ position.ZacksRank = momentumCandidate.ZacksRank;
+ position.Velocity = momentumCandidate.Velocity;
+ position.Volume = momentumCandidate.Volume;
+ position.Return1D = position.Return1D;
+ position.PurchaseDate = tradeDate;
+ position.PurchasePrice = price.Close;
+ position.CurrentPrice = price.Close;
+ position.Shares = (int)Math.Floor((cash / (double)MaxPositions) / position.PurchasePrice);
+ if (0 == position.Shares) continue; // if not able to purchase any shares, for example, if the share price exceeds our purchasing power then move on to the next candidate
+ positions.Add(position);
+ positionCount++;
+ if (positionCount >= MaxPositions) break;
+ }
+ if (0 == positions.Count && Configuration.UseFallbackCandidate) // if we don't get any signals then consider the fallback candidate options as per the configuration file
+ {
+ String fallbackCandidate = Configuration.FallbackCandidate;
+ if (null != Configuration.FallbackCandidateBestOf && !"".Equals(Configuration.FallbackCandidateBestOf))
+ {
+ QualityIndicator qualityIndicator=new QualityIndicator(Configuration.QualityIndicatorType);
+ fallbackCandidate=CandidateSelector.SelectBestCandidateSymbol(qualityIndicator,Utility.ToList(Configuration.FallbackCandidateBestOf),Configuration.FallbackCandidate,tradeDate);
+ }
+ Price price = GBPriceCache.GetInstance().GetPrice(fallbackCandidate, tradeDate);
+ if (null == price)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", fallbackCandidate, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
+ return positions;
+ }
+ Position position = new Position();
+ position.Symbol = fallbackCandidate;
+ position.PurchaseDate = tradeDate;
+ position.PurchasePrice = price.Close;
+ position.CurrentPrice = price.Close;
+ position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
+ if(0 == position.Shares)return positions;
+ positions.Add(position);
+ }
+ return positions;
+ }
+
+ private Positions BuyBenchmarkPositions(DateTime tradeDate, double cash)
+ {
+ Positions positions = new Positions();
+ Price benchmarkPrice = GBPriceCache.GetInstance().GetPrice(Configuration.BenchmarkModeSymbol, tradeDate);
+ if (null == benchmarkPrice)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", Configuration.BenchmarkModeSymbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
+ return positions;
+ }
+ Position position = new Position();
+ position.Symbol = Configuration.BenchmarkModeSymbol;
+ position.PurchaseDate = tradeDate;
+ position.PurchasePrice = benchmarkPrice.Close;
+ position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
+ if (0 == position.Shares) return positions;
+ positions.Add(position);
+ return positions;
+ }
+// *********************************************************************************************************************************************************************
+// ************************************************************ E N D B U Y P O S I T I O N S ***********************************************
+// *********************************************************************************************************************************************************************
+ private void DisplayBalance()
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
+ MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
+ 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))));
+ for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
+ {
+ Positions slotPositions = ActivePositions[slotIndex];
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SLOT:{0} EXPOSURE:{1}",slotIndex,Utility.FormatCurrency(slotPositions.Exposure)));
+ }
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE: {0}",Utility.FormatCurrency(ActivePositions.GetExposure())));
+ MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
+ }
+
+ private void DisplayBalanceFromPositions()
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
+ Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
+ Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetGainLoss())),
+ Utility.AddQuotes(Utility.FormatPercent(ActivePositions.GetGainLossPercent())),
+ Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
+ Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
+ }
+
+ private void DisplayBalance(RealtimeGainLoss gainLoss)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
+ Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
+ Utility.AddQuotes(Utility.FormatCurrency(gainLoss.GainLoss)),
+ Utility.AddQuotes(Utility.FormatPercent(gainLoss.GainLossPercent)),
+ Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
+ Utility.AddQuotes(Utility.FormatCurrency(gainLoss.MarketValue+CashBalance))));
+ }
+// ****************************************************************************************************************************************
+// ************************************************************* C O N T R O L T O D A Y ***********************************************
+// ****************************************************************************************************************************************
+ public DateTime Today()
+ {
+ return DateTime.Now;
+ }
+// ****************************************************************************************************************************************
+// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
+// ****************************************************************************************************************************************
+ public MGSessionParams RestoreSession()
+ {
+ try
+ {
+ MGSessionManager sessionManager=new MGSessionManager();
+ if(!MGSessionManager.SessionAvailable(PathSessionFileName))return null;
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName));
+ MGSessionParams sessionParams=MGSessionManager.RestoreSession(PathSessionFileName);
+ TradeDate=sessionParams.TradeDate;
+ if(TradeDate.Date candidates,String defaultCandidate,DateTime analysisDate)
+ {
+ String bestCandidate=defaultCandidate;
+ try
+ {
+ if(null==candidates||0==candidates.Count||Utility.IsEpoch(analysisDate)||null==defaultCandidate)return bestCandidate;
+ List qualityIndicatorCandidates=new List();
+ foreach(String candidate in candidates)
+ {
+ Prices prices=PricingDA.GetPrices(candidate,analysisDate,DAY_COUNT);
+ if(null==prices||0==prices.Count)continue;
+ QualityIndicatorCandidate qualityIndicatorCandidate=new QualityIndicatorCandidate();
+ qualityIndicatorCandidate.Symbol=candidate;
+ qualityIndicatorCandidate.IDIndicator=IDIndicator.Calculate(prices);
+ qualityIndicatorCandidate.Score=ScoreIndicator.Calculate(prices);
+ qualityIndicatorCandidates.Add(qualityIndicatorCandidate);
+ }
+ if(0==qualityIndicatorCandidates.Count)return bestCandidate;
+ if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
+ {
+ qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.IDIndicator ascending select qualityIndicatorCandidate).ToList();
+ }
+ else
+ {
+ qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.Score descending select qualityIndicatorCandidate).ToList();
+ }
+ return qualityIndicatorCandidates.Take(1).FirstOrDefault().Symbol;
+ }
+ catch(Exception exception)
+ {
+ MDTrace.WriteLine(String.Format("{0}",exception.ToString()));
+ return bestCandidate;
+ }
+ }
+ public static QualityIndicatorCandidate SelectBestCandidate(QualityIndicator qualityIndicator,List candidates,String defaultCandidate,DateTime analysisDate)
+ {
+ QualityIndicatorCandidate bestCandidate=new QualityIndicatorCandidate();
+ try
+ {
+ if(null==candidates||0==candidates.Count||Utility.IsEpoch(analysisDate)||null==defaultCandidate)return null;
+ List qualityIndicatorCandidates=new List();
+ foreach(String candidate in candidates)
+ {
+ Prices prices=PricingDA.GetPrices(candidate,analysisDate,DAY_COUNT);
+ Fundamental fundamental=FundamentalDA.GetFundamentalMaxDate(candidate,analysisDate);
+ if(null==prices||0==prices.Count)continue;
+ QualityIndicatorCandidate qualityIndicatorCandidate=new QualityIndicatorCandidate();
+ qualityIndicatorCandidate.Symbol=candidate;
+ qualityIndicatorCandidate.IDIndicator=IDIndicator.Calculate(prices);
+ qualityIndicatorCandidate.Score=ScoreIndicator.Calculate(prices);
+ qualityIndicatorCandidate.CumReturn252=prices.GetCumulativeReturn();
+ qualityIndicatorCandidate.DayCount=DAY_COUNT;
+ qualityIndicatorCandidate.Return1D=prices.GetReturn1D();
+ if(null!=fundamental)
+ {
+ qualityIndicatorCandidate.PE=fundamental.PE;
+ qualityIndicatorCandidate.Beta=fundamental.Beta;
+ }
+ qualityIndicatorCandidates.Add(qualityIndicatorCandidate);
+ }
+ if(0==qualityIndicatorCandidates.Count)return bestCandidate;
+
+ if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
+ {
+ qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.IDIndicator ascending select qualityIndicatorCandidate).ToList();
+ }
+ else
+ {
+ qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.Score descending select qualityIndicatorCandidate).ToList();
+ }
+ return qualityIndicatorCandidates.Take(1).FirstOrDefault();
+ }
+ catch(Exception exception)
+ {
+ MDTrace.WriteLine(String.Format("{0}",exception.ToString()));
+ return bestCandidate;
+ }
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/CandidateViolation.cs b/MarketData/MarketDataLib/Generator/Momentum/CandidateViolation.cs
new file mode 100755
index 0000000..8fb8095
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/CandidateViolation.cs
@@ -0,0 +1,19 @@
+
+namespace MarketData.Generator.Momentum
+{
+ public class CandidateViolation
+ {
+ public CandidateViolation(String symbol, String reasonCategory)
+ {
+ Symbol = symbol;
+ ReasonCategory = reasonCategory;
+ }
+
+ public String Symbol {get; set;}
+ public String ReasonCategory {get; set;}
+ }
+
+ public class CandidateViolations : List
+ {
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/Candidates.cs b/MarketData/MarketDataLib/Generator/Momentum/Candidates.cs
new file mode 100755
index 0000000..572c937
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/Candidates.cs
@@ -0,0 +1,46 @@
+using System.Text;
+using MarketData.Utils;
+
+namespace MarketData.Generator.Momentum
+{
+ public class MomentumCandidates : List
+ {
+ public MomentumCandidates()
+ {
+ }
+ public MomentumCandidates(List momentumCandidates)
+ {
+ foreach(MomentumCandidate momentumCandidate in momentumCandidates)Add(momentumCandidate);
+ }
+ }
+ public class MomentumCandidate
+ {
+ public String Symbol{get;set;}
+ public DateTime AnalysisDate{get;set;}
+ public double CumReturn252{get;set;}
+ public int DayCount{get;set;}
+ public double IDIndicator{get;set;} // This is the IDIndicator methodology used for quality. This is the default methodology
+ public double Score{get;set;} // This is the Score methodology used for quality. This one is taken from Andreas Clenow Momentum
+ public double MaxDrawdown{get;set;}
+ public double MaxUpside{get;set;}
+ public double PE{get;set;}
+ public double Beta{get;set;}
+ public double Velocity{get;set;}
+ public long Volume{get;set;}
+ public double Return1D{get;set;}
+ public String ZacksRank{get;set;}
+
+ public static String Header()
+ {
+ StringBuilder sb=new StringBuilder();
+ sb.Append("Symbol,AnalysisDate,Return,DayCount,IDIndicator,Score,MaxDrawdown,MaxUpside");
+ return sb.ToString();
+ }
+ public override String ToString()
+ {
+ StringBuilder sb=new StringBuilder();
+ sb.Append(Symbol).Append(",").Append(AnalysisDate).Append(",").Append(Utility.FormatPercent(CumReturn252)).Append(",").Append(DayCount).Append(",").Append(IDIndicator).Append(",").Append(Score).Append(","). Append(Utility.FormatPercent(MaxDrawdown)).Append(",").Append(Utility.FormatPercent(MaxUpside));
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/IDIndicator.cs b/MarketData/MarketDataLib/Generator/Momentum/IDIndicator.cs
new file mode 100755
index 0000000..dd23e36
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/IDIndicator.cs
@@ -0,0 +1,56 @@
+using MarketData.MarketDataModel;
+
+namespace MarketData.Generator.Momentum
+{
+ public class QualityIndicatorCandidate
+ {
+ public QualityIndicatorCandidate()
+ {
+ }
+ //public QualityIndicatorCandidate(String symbol,double idIndicator,double score)
+ //{
+ // Symbol=symbol;
+ // IDIndicator=idIndicator;
+ // Score=score;
+ //}
+ public String Symbol{get;set;}
+ public double CumReturn252{get;set;}
+ public double IDIndicator{get;set;}
+ public double Score{get;set;}
+ public int DayCount{get;set;}
+ public double PE{get;set;}
+ public double Beta{get;set;}
+ public double Return1D{get;set;}
+ }
+// *********************************************************************************************************
+ public class IDIndicator
+ {
+ private IDIndicator()
+ {
+ }
+//CountOf CountOf
+//Negative Positive Return Sign IDIndicator
+// 50 202 0.2 1 -60.31746032
+// 100 152 0.2 1 -20.63492063
+// 0 252 0.1 1 -100
+// 0 252 0.2 1 -100
+// The lower the IDIndicator the better
+// This calculator deviates from the original by using an exponential decay on the stream of returns such that weigh distant returns a bit less than current returns
+// and thereby attempt to give a more accurate picture of the quality of current returns.
+ public static double Calculate(Prices prices)
+ {
+ double idIndicator=0.00;
+ double cumulativeReturn=0.00;
+ float[] returns=prices.GetReturns();
+ ExponentialDecay exponentialDecay=new ExponentialDecay();
+ exponentialDecay.Prime(returns,2.00);
+ for(int index=0;index0.00 select value).Count();
+ double negativeCount=(from float value in returns where value<0.00 select value).Count();
+ for(int index=0;index0.00?1.00:-1.00;
+ idIndicator=sign*((negativeCount/returns.Length)*100.00-(positiveCount/returns.Length)*100.00);
+ return idIndicator;
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/MGConfiguration.cs b/MarketData/MarketDataLib/Generator/Momentum/MGConfiguration.cs
new file mode 100755
index 0000000..b67976e
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/MGConfiguration.cs
@@ -0,0 +1,234 @@
+using MarketData.Utils;
+
+namespace MarketData.Generator.Momentum
+{
+ public class MGConfiguration
+ {
+// Operational Settings
+ public bool Verbose{get;set;}
+// Basic settings
+ public int HoldingPeriod{get;set;}
+ public int MaxPositions{get;set;}
+ public String NoTradeSymbols{get;set;}
+ public String NoTradeFinancialSymbols{get;set;}
+ public double InitialCash{get;set;}
+
+// Fundamental screenings
+ public double MarketCapLowerLimit{get;set;}
+
+ public bool UsePEScreen{get;set;} // If set this filter will ignore any security that is either missing a PE or if the PE is present but less than 0
+
+ public bool UseMaxPEScreen{get;set;} // control Max PE range check
+ public double MaxPE{get;set;} // if UseMaxPECheck is set this setting will ignore any security who's PE is greater. If PE is missing candidate will be accepted
+ public bool StrictMaxPE{get;set;} // if TRUE then UseMaxPEScreen is STRICT otherwise UseMaxPEScreen is SOFT RULE where >MaxPE is AVOIDED but used to fill the quota. The quota is filled by ranking highPECandidates by PE and taking the lower PE's up to the quota. Soft rule yields best result
+
+// EBITDA screen
+ public bool UseEBITDAScreen{get;set;}
+ public bool UseRevenuePerShareScreen{get;set;}
+
+// If slope Beta Check is true then we compare the fundamental Beta to the threshhold value. If Beta>Threshhold AND the slope from the Benchmark LowPrice over BetaDays is <0 we reject
+ public String Benchmark{get;set;}
+ public bool UseLowSlopeBetaCheck{get;set;}
+ public int LowSlopeBetaDays{get;set;}
+ public double LowSlopeBetaThreshhold{get;set;}
+
+// MACD Settings : If MACD is being used then the process configures the MACD as per setup and eliminates candidates with a weak sell/strong sell signal in the signal days setting.
+ public bool UseMACD{get;set;}
+ public String MACDSetup="(12,26,9)"; // (8,17,9) is another alternative
+ public int MACDSignalDays{get;set;}
+ public bool MACDRejectWeakSellSignals{get;set;}
+ public bool MACDRejectStrongSellSignals{get;set;}
+
+// Stochastics Settings : If Stochastics is being used then the process eliminates candidates with a weak sell/strong sell in the signal days setting.
+ public bool UseStochastics{get;set;}
+ public int StochasticsSignalDays{get;set;}
+ public bool StochasticsRejectWeakSells{get;set;}
+ public bool StochasticsRejectStrongSells{get;set;}
+
+// FallbackCandidate : If this setting is true then the FallbackCandidate is purchased if there are no candidates that qualify in a given cycle
+ public bool UseFallbackCandidate{get;set;}
+ public String FallbackCandidate{get;set;}
+ public String FallbackCandidateBestOf{get;set;} // if this is set then the fallback candidate is selected as the best 252 day return
+
+// Benchmark mode : If this is set to true then purchases the benchmark symbol instead of the momentum candidates
+ public bool BenchmarkMode{get;set;}
+ public String BenchmarkModeSymbol{get;set;}
+
+// QualityIndicator
+ public String QualityIndicatorType{get;set;} // this can be either IDINDICATOR or SCOREINDICATOR. The SCOREINDICATOR was adopted from CMMomentum model
+
+// IncludeTradeMasterForSymbolsHeld :
+ public bool IncludeTradeMasterForSymbolsHeld{get;set;} // if this is set to true then use both ActivePositions within the model as well as the master trades table to determine held positions.
+
+ public MGConfiguration()
+ {
+ Verbose=true; // user verbose output
+ BenchmarkMode=false; // set this to true if you want to run the model using just the benchmark symbol to buy.
+ BenchmarkModeSymbol="SPY"; // SPY is the default symbol to buy when testing
+ HoldingPeriod=3; // 3 is the default
+ MaxPositions=3; // 3 is the default
+ NoTradeSymbols="GBTC,YOKU,PNY,RFMD,ASAZY"; // ASAZY came up as candidate during 3/30 run but not available on Robinhood
+ NoTradeFinancialSymbols="U.S. Private Equity,U.S. Financials,U.S. Financial Services,U.S. Banking and Investment Services,Trading-Miscellaneous,Trading--Miscellaneous,Trading--Leveraged Equity,Trading--Leveraged Debt,Trading--Leveraged Commodities,Trading--Inverse Equity,Trading--Inverse Commodities,Tactical Allocation,Specialty Finance,Japan Financials,Savings & Cooperative Banks,Option Writing,Insurance Brokers,Insurance - Specialty,Insurance - Reinsurance,Insurance - Property & Casualty,Insurance - Life,Insurance - Diversified,Global Private Equity,Global Financials,Financial Services,Financial Exchanges,Financial,China Financials,Banks - Regional - US,Banks - Regional - Latin America,Banks - Global,Asset Management,Credit Services";
+ Benchmark="SPY"; // SPY is the default
+// Candidate selection settings
+ MarketCapLowerLimit=1000000000; // 1B is the default
+// PEScreen is off by default
+ UsePEScreen=false; // false is the default. This setting yields the most optimal performance in backtests. Checks for existance of PE and if exists ensures >0
+// UseMaxPEScreen, MaxPE, and StrictPEExclusion
+ UseMaxPEScreen=true;
+ MaxPE=40;
+ StrictMaxPE=false;
+
+ UseEBITDAScreen=true; // true is the default
+ UseRevenuePerShareScreen=true; // true is the default
+ UseLowSlopeBetaCheck=true; // true is the default. this yields the most optimal performance in backtests
+ LowSlopeBetaDays=15; // 15 is the default. This yields the most optimal performance in backtests
+ LowSlopeBetaThreshhold=1.00; // (1.00) is the default This yields the most optimal performance in backtests
+ UseMACD=true; // true is the default
+ MACDSetup="(12,26,9)"; // (12,26,9)
+ MACDSignalDays=12; // 12 is the default
+ MACDRejectStrongSellSignals=false; // false is the default
+ MACDRejectWeakSellSignals=true; // true is the default
+ UseStochastics=true; // true is the default
+ StochasticsSignalDays=3; // 3 is the default
+ StochasticsRejectStrongSells=true; // true is the default
+ StochasticsRejectWeakSells=true; // true is the default
+
+// Fallback candidate settings
+ UseFallbackCandidate=true; // True is the default
+ FallbackCandidate="SHV"; // "SHV" ICE U.S. Treasury Short Bond Index, "AGG" Barclays U.S. Aggregate Bond Index - AGG can have a slighty better return but is more volatile
+ FallbackCandidateBestOf="SHV,AGG,ACWX"; // if set then the fallback candidate is selected as the best 252 day return in this comma seperated list (i.e.) "SHV,ACWX,AGG"
+// Set the QualityIndicator type to the IDIndicator
+ QualityIndicatorType=QualityIndicator.ToString(QualityIndicator.QualityType.IDIndicator);
+
+ IncludeTradeMasterForSymbolsHeld=false;
+ }
+ public void DisplayHeader()
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"Setting,Value");
+ }
+ public NVPCollection ToNVPCollection()
+ {
+ NVPCollection nvpCollection=new NVPCollection();
+ nvpCollection.Add(new NVP("Verbose",Verbose.ToString()));
+ nvpCollection.Add(new NVP("BenchmarkMode",BenchmarkMode.ToString()));
+ nvpCollection.Add(new NVP("BenchmarkModeSymbol",BenchmarkModeSymbol.ToString()));
+ nvpCollection.Add(new NVP("HoldingPeriod",HoldingPeriod.ToString()));
+ nvpCollection.Add(new NVP("MaxPositions",MaxPositions.ToString()));
+ nvpCollection.Add(new NVP("NoTradeSymbols",NoTradeSymbols.ToString()));
+ nvpCollection.Add(new NVP("NoTradeFinancialSymbols",NoTradeFinancialSymbols.ToString()));
+ nvpCollection.Add(new NVP("Benchmark",Benchmark.ToString()));
+ nvpCollection.Add(new NVP("MarketCapLowerLimit",MarketCapLowerLimit.ToString()));
+ nvpCollection.Add(new NVP("UsePEScreen",UsePEScreen.ToString()));
+ nvpCollection.Add(new NVP("UseEBITDAScreen",UseEBITDAScreen.ToString()));
+ nvpCollection.Add(new NVP("UseRevenuePerShareScreen",UseRevenuePerShareScreen.ToString()));
+ nvpCollection.Add(new NVP("UseLowSlopeBetaCheck",UseLowSlopeBetaCheck.ToString()));
+ nvpCollection.Add(new NVP("LowSlopeBetaDays",LowSlopeBetaDays.ToString()));
+ nvpCollection.Add(new NVP("LowSlopeBetaThreshhold",LowSlopeBetaThreshhold.ToString()));
+ nvpCollection.Add(new NVP("UseMACD",UseMACD.ToString()));
+ nvpCollection.Add(new NVP("MACDSetup",MACDSetup.ToString()));
+ nvpCollection.Add(new NVP("MACDSignalDays",MACDSignalDays.ToString()));
+ nvpCollection.Add(new NVP("MACDRejectStrongSellSignals",MACDRejectStrongSellSignals.ToString()));
+ nvpCollection.Add(new NVP("MACDRejectWeakSellSignals",MACDRejectWeakSellSignals.ToString()));
+ nvpCollection.Add(new NVP("UseStochastics",UseStochastics.ToString()));
+ nvpCollection.Add(new NVP("StochasticsSignalDays",StochasticsSignalDays.ToString()));
+ nvpCollection.Add(new NVP("StochasticsRejectStrongSells",StochasticsRejectStrongSells.ToString()));
+ nvpCollection.Add(new NVP("StochasticsRejectWeakSells",StochasticsRejectWeakSells.ToString()));
+ nvpCollection.Add(new NVP("UseFallbackCandidate",UseFallbackCandidate.ToString()));
+ nvpCollection.Add(new NVP("FallbackCandidate",FallbackCandidate.ToString()));
+ nvpCollection.Add(new NVP("FallbackCandidateBestOf",FallbackCandidateBestOf.ToString()));
+ nvpCollection.Add(new NVP("UseMaxPEScreen",UseMaxPEScreen.ToString()));
+ nvpCollection.Add(new NVP("MaxPE",MaxPE.ToString()));
+ nvpCollection.Add(new NVP("StrictMaxPE",StrictMaxPE.ToString()));
+ nvpCollection.Add(new NVP("QualityIndicatorType",QualityIndicatorType.ToString()));
+ nvpCollection.Add(new NVP("IncludeTradeMasterForSymbolsHeld",IncludeTradeMasterForSymbolsHeld.ToString()));
+ return nvpCollection;
+ }
+ public static MGConfiguration FromNVPCollection(NVPCollection nvpCollection)
+ {
+ MGConfiguration mgConfiguration=new MGConfiguration();
+ NVPDictionary nvpDictionary=nvpCollection.ToDictionary();
+ mgConfiguration.Verbose=nvpDictionary["Verbose"].Get();
+ mgConfiguration.BenchmarkMode=nvpDictionary["BenchmarkMode"].Get();
+ mgConfiguration.BenchmarkModeSymbol=nvpDictionary["BenchmarkModeSymbol"].Get();
+ mgConfiguration.HoldingPeriod=nvpDictionary["HoldingPeriod"].Get();
+ mgConfiguration.MaxPositions=nvpDictionary["MaxPositions"].Get();
+ mgConfiguration.NoTradeSymbols=nvpDictionary["NoTradeSymbols"].Get();
+ mgConfiguration.NoTradeFinancialSymbols=nvpDictionary["NoTradeFinancialSymbols"].Get();
+ mgConfiguration.Benchmark=nvpDictionary["Benchmark"].Get();
+ mgConfiguration.MarketCapLowerLimit=nvpDictionary["MarketCapLowerLimit"].Get();
+ mgConfiguration.UsePEScreen=nvpDictionary["UsePEScreen"].Get();
+ mgConfiguration.UseMaxPEScreen=nvpDictionary["UseMaxPEScreen"].Get();
+ mgConfiguration.MaxPE=nvpDictionary["MaxPE"].Get();
+ mgConfiguration.StrictMaxPE=nvpDictionary["StrictMaxPE"].Get();
+ mgConfiguration.UseEBITDAScreen=nvpDictionary["UseEBITDAScreen"].Get();
+ mgConfiguration.UseRevenuePerShareScreen=nvpDictionary["UseRevenuePerShareScreen"].Get();
+ mgConfiguration.UseLowSlopeBetaCheck=nvpDictionary["UseLowSlopeBetaCheck"].Get();
+ mgConfiguration.LowSlopeBetaDays=nvpDictionary["LowSlopeBetaDays"].Get();
+ mgConfiguration.LowSlopeBetaThreshhold=nvpDictionary["LowSlopeBetaThreshhold"].Get();
+ mgConfiguration.UseMACD=nvpDictionary["UseMACD"].Get();
+ mgConfiguration.MACDSetup=nvpDictionary["MACDSetup"].Get();
+ mgConfiguration.MACDSignalDays=nvpDictionary["MACDSignalDays"].Get();
+ mgConfiguration.MACDRejectStrongSellSignals=nvpDictionary["MACDRejectStrongSellSignals"].Get();
+ mgConfiguration.MACDRejectWeakSellSignals=nvpDictionary["MACDRejectWeakSellSignals"].Get();
+ mgConfiguration.UseStochastics=nvpDictionary["UseStochastics"].Get();
+ mgConfiguration.StochasticsSignalDays=nvpDictionary["StochasticsSignalDays"].Get();
+ mgConfiguration.StochasticsRejectStrongSells=nvpDictionary["StochasticsRejectStrongSells"].Get();
+ mgConfiguration.StochasticsRejectWeakSells=nvpDictionary["StochasticsRejectWeakSells"].Get();
+ mgConfiguration.UseFallbackCandidate=nvpDictionary["UseFallbackCandidate"].Get();
+ mgConfiguration.FallbackCandidate=nvpDictionary["FallbackCandidate"].Get();
+ mgConfiguration.FallbackCandidateBestOf=nvpDictionary["FallbackCandidateBestOf"].Get();
+
+ if(nvpDictionary.ContainsKey("QualityIndicatorType")) mgConfiguration.QualityIndicatorType=nvpDictionary["QualityIndicatorType"].Get();
+ else mgConfiguration.QualityIndicatorType=QualityIndicator.ToString(QualityIndicator.QualityType.IDIndicator);
+
+ if(nvpDictionary.ContainsKey("IncludeTradeMasterForSymbolsHeld")) mgConfiguration.IncludeTradeMasterForSymbolsHeld=nvpDictionary["IncludeTradeMasterForSymbolsHeld"].Get();
+ else mgConfiguration.IncludeTradeMasterForSymbolsHeld=false;
+ return mgConfiguration;
+ }
+ public void DisplayConfiguration()
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Verbose,{0}",Verbose));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Holding Period,{0}",HoldingPeriod));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MaxPositions,{0}",MaxPositions));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("NoTradeSymbols,{0}",NoTradeSymbols));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("NoTradeFinancialSymbols,{0}",NoTradeFinancialSymbols));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Benchmark,{0}",Benchmark));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MarketCapLowerLimit,{0}",MarketCapLowerLimit));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UsePEScreen,{0}",UsePEScreen));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseMaxPEScreen,{0}",UseMaxPEScreen));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MaxPE,{0}",MaxPE));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StrictMaxPE,{0}",StrictMaxPE));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseEBITDAScreen,{0}",UseEBITDAScreen));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseRevenuePerShareScreen,{0}",UseRevenuePerShareScreen));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseLowSlopeBetaCheck,{0}",UseLowSlopeBetaCheck));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("LowSlopeBetaDays,{0}",LowSlopeBetaDays));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("LowSlopeBetaThreshhold,{0}",LowSlopeBetaThreshhold));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseMACD,{0}",UseMACD));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDSetup,{0}",MACDSetup));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDSignalDays,{0}",MACDSignalDays));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDRejectStrongSellSignals,{0}",MACDRejectStrongSellSignals));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDRejectWeakSellSignals,{0}",MACDRejectWeakSellSignals));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseStochastics,{0}",UseStochastics));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsSignalDays,{0}",StochasticsSignalDays));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsRejectStrongSells,{0}",StochasticsRejectStrongSells));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsRejectWeakSells,{0}",StochasticsRejectWeakSells));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseFallbackCandidate,{0}",UseFallbackCandidate));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("FallbackCandidate,{0}",FallbackCandidate));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("FallbackCandidateBestOf,{0}",FallbackCandidateBestOf));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BenchmarkMode,{0}",BenchmarkMode));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BenchmarkSymbol,{0}",BenchmarkModeSymbol));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("QualityIndicatorType,{0}",QualityIndicatorType));
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IncludeTradeMasterForSymbolsHeld,{0}",IncludeTradeMasterForSymbolsHeld));
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/MGSessionManager.cs b/MarketData/MarketDataLib/Generator/Momentum/MGSessionManager.cs
new file mode 100755
index 0000000..1a01b6d
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/MGSessionManager.cs
@@ -0,0 +1,156 @@
+using MarketData.Utils;
+
+namespace MarketData.Generator.Momentum
+{
+// *****************************************************************************
+ public class MGSessionManager
+ {
+ public bool SaveSession(MGSessionParams sessionParams,String pathSessionFile)
+ {
+ try
+ {
+ if(null==pathSessionFile)return false;
+ pathSessionFile=GetSessionFileName(pathSessionFile);
+ FileStream outStream=new FileStream(pathSessionFile,FileMode.Create);
+ StreamWriter streamWriter=new StreamWriter(outStream);
+ streamWriter.WriteLine("SESSIONv1.00");
+ streamWriter.WriteLine((new NVP("LastUpdated",sessionParams.LastUpdated.ToString())).ToString());
+ streamWriter.WriteLine((new NVP("TradeDate",sessionParams.TradeDate.ToShortDateString())).ToString());
+ streamWriter.WriteLine((new NVP("StartDate",sessionParams.StartDate.ToShortDateString())).ToString());
+ streamWriter.WriteLine((new NVP("AnalysisDate",sessionParams.AnalysisDate.ToShortDateString())).ToString());
+ streamWriter.WriteLine((new NVP("Cycle",sessionParams.Cycle.ToString())).ToString());
+ streamWriter.WriteLine((new NVP("CashBalance",sessionParams.CashBalance.ToString())).ToString());
+ streamWriter.WriteLine((new NVP("NonTradeableCash",sessionParams.NonTradeableCash.ToString())).ToString());
+
+ NVPCollection configurationCollection=sessionParams.Configuration.ToNVPCollection();
+ streamWriter.WriteLine(configurationCollection.ToString());
+ List nvpCollectionsList=sessionParams.ActivePositions.ToNVPCollections();
+ int totalPositions=0;
+ foreach(NVPCollections nvpCollections in nvpCollectionsList)
+ {
+ List nvpCollectionsStringList=nvpCollections.ToList();
+ totalPositions+=nvpCollectionsStringList.Count;
+ }
+ streamWriter.WriteLine((new NVP("TotalActivePositions",totalPositions.ToString())).ToString());
+ foreach(NVPCollections nvpCollections in nvpCollectionsList)
+ {
+ List nvpCollectionsStringList=nvpCollections.ToList();
+ foreach(String str in nvpCollectionsStringList)
+ {
+ streamWriter.WriteLine(str);
+ }
+ }
+ NVPCollections allPositionsCollections=sessionParams.AllPositions.ToNVPCollections();
+ List nvpAllCollectionsStringList=allPositionsCollections.ToList();
+ streamWriter.WriteLine((new NVP("TotalPositions",nvpAllCollectionsStringList.Count.ToString())).ToString());
+ foreach(String str in nvpAllCollectionsStringList)streamWriter.WriteLine(str);
+ streamWriter.Flush();
+ outStream.Flush();
+ streamWriter.Close();
+ outStream.Close();
+ outStream.Dispose();
+ return true;
+ }
+ catch(Exception exception)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0}",exception.ToString()));
+ return false;
+ }
+ }
+ public static MGSessionParams RestoreSession(String pathSessionFile)
+ {
+ FileStream inStream =null;
+ StreamReader streamReader=null;
+ try
+ {
+ if(!SessionAvailable(pathSessionFile))return null;
+ MGSessionParams sessionParams=new MGSessionParams();
+ inStream =new FileStream(pathSessionFile,FileMode.Open);
+ streamReader=new StreamReader(inStream);
+ String versionInfo=streamReader.ReadLine();
+ double version=double.Parse(Utility.BetweenString(versionInfo,"v",null));
+ if(1.00!=version)return null;
+ NVP lastUpdated=new NVP(streamReader.ReadLine());
+ NVP tradeDate=new NVP(streamReader.ReadLine());
+ NVP startDate=new NVP(streamReader.ReadLine());
+ NVP analysisDate=new NVP(streamReader.ReadLine());
+ NVP cycle=new NVP(streamReader.ReadLine());
+ NVP cashBalance=new NVP(streamReader.ReadLine());
+ NVP nonTradeableCash=new NVP(streamReader.ReadLine());
+ sessionParams.LastUpdated=lastUpdated.Get();
+ sessionParams.TradeDate=tradeDate.Get();
+ sessionParams.StartDate=startDate.Get();
+ sessionParams.AnalysisDate=analysisDate.Get();
+ sessionParams.Cycle=cycle.Get();
+ sessionParams.CashBalance=cashBalance.Get();
+ sessionParams.NonTradeableCash=nonTradeableCash.Get();
+ NVPCollection configurationCollection=new NVPCollection(streamReader.ReadLine());
+ sessionParams.Configuration=MGConfiguration.FromNVPCollection(configurationCollection);
+ int totalActivePositions=(new NVP(streamReader.ReadLine())).Get();
+ sessionParams.ActivePositions=new ActivePositions();
+ for(int positionIndex=0;positionIndex();
+ NVPCollections nvpCollections=new NVPCollections();
+ for(int positionIndex=0;positionIndexGenerate momentum selections -
+ public class MomentumGenerator
+ {
+ public enum MomentumGeneratorConstants{DayCount=252}; // Trading days in one year
+
+ private MomentumGenerator()
+ {
+ }
+// These two interfaces are used by the UI so that it can capture the fallback candidates
+ public static MomentumCandidates GenerateMomentum(DateTime tradeDate,MGConfiguration config)
+ {
+ List symbolsHeld=new List();
+ return new MomentumCandidates(GenerateMomentum(tradeDate,symbolsHeld,config).Take(config.MaxPositions).ToList());
+ }
+ public static MomentumCandidates GenerateMomentumWithFallback(DateTime tradeDate,MGConfiguration config)
+ {
+ List symbolsHeld=new List();
+ MomentumCandidates momentumCandidates=GenerateMomentum(tradeDate,symbolsHeld,config);
+ QualityIndicator qualityIndicator=new QualityIndicator(config.QualityIndicatorType);
+ if((null==momentumCandidates||0==momentumCandidates.Count)&&config.UseFallbackCandidate)
+ {
+ QualityIndicatorCandidate bestCandidate=null;
+ if(null!=config.FallbackCandidateBestOf && !"".Equals(config.FallbackCandidateBestOf))
+ {
+ bestCandidate=CandidateSelector.SelectBestCandidate(qualityIndicator,Utility.ToList(config.FallbackCandidateBestOf),config.FallbackCandidate,tradeDate);
+ if(null!=bestCandidate)
+ {
+ ZacksRank zacksRank=ZacksRankDA.GetZacksRankOnOrBefore(bestCandidate.Symbol,tradeDate);
+ MomentumCandidate momentumCandidate=new MomentumCandidate();
+ momentumCandidate.Symbol=bestCandidate.Symbol;
+ momentumCandidate.AnalysisDate=tradeDate;
+ momentumCandidate.CumReturn252=bestCandidate.CumReturn252;
+ momentumCandidate.IDIndicator=bestCandidate.IDIndicator;
+ momentumCandidate.Score=bestCandidate.Score;
+ momentumCandidate.DayCount=bestCandidate.DayCount;
+ momentumCandidate.PE=bestCandidate.PE;
+ momentumCandidate.Beta=bestCandidate.Beta;
+ momentumCandidate.Return1D=bestCandidate.Return1D;
+ if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
+ momentumCandidates=new MomentumCandidates();
+ momentumCandidates.Add(momentumCandidate);
+ }
+ }
+ }
+ return momentumCandidates;
+ }
+// This interface is called by the Backtest
+ public static MomentumCandidates GenerateMomentum(DateTime tradeDate,List symbolsHeld,MGConfiguration config)
+ {
+ DateGenerator dateGenerator=new DateGenerator();
+ List symbols=PricingDA.GetSymbols();
+ MomentumCandidates momentumCandidates=new MomentumCandidates();
+ MomentumCandidates highPECandidates=new MomentumCandidates();
+ DateTime startDateOfReturns=dateGenerator.GetPrevMonthEnd(tradeDate,2);
+ List noTradeSymbols=Utility.ToList(config.NoTradeSymbols);
+ List noTradeFinancialSymbols=Utility.ToList(config.NoTradeFinancialSymbols);
+ CandidateViolations candidateViolations = new CandidateViolations();
+
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Generate momentum.. examining candidates"));
+// Go through the universe of stocks
+ for(int index=0;indexx.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate already held."));
+ continue;
+ }
+
+// Check if the symbol is in the no trade list (i.e.) Bitcoin etc.,
+ if(noTradeSymbols.Any(x=>x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate in NoTradeSymbol."));
+ continue;
+ }
+
+// Check MarketCap, EBITDA, PE, and Revenue Per Share
+ Fundamental fundamental=FundamentalDA.GetFundamentalMaxDate(symbol,tradeDate);
+ if(null==fundamental)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate no fundamental."));
+ continue;
+ }
+
+ if(!(fundamental.MarketCap>=config.MarketCapLowerLimit))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate MarketCapLimit."));
+ continue;
+ }
+
+ if(config.UseEBITDAScreen && (double.IsNaN(fundamental.EBITDA)||fundamental.EBITDA<=0))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate EBITDA violation."));
+ continue;
+ }
+
+ if(config.UseRevenuePerShareScreen && (double.IsNaN(fundamental.RevenuePerShare)||fundamental.RevenuePerShare<0.00))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate RevenuePerShare violation."));
+ continue;
+ }
+
+// Initial PE screening. This screen checks for existance of PE and if it is availabe it must be >0.00 . There is another PE based on limits further below
+ if(config.UsePEScreen && (double.IsNaN(fundamental.PE)||fundamental.PE<=0.00))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate PE violation."));
+ continue;
+ }
+
+// Exclude any company in the "Financial" sector
+ CompanyProfile companyProfile=CompanyProfileDA.GetCompanyProfile(symbol);
+ if(null!=companyProfile&&null!=companyProfile.Sector&&noTradeFinancialSymbols.Any(x=>x.Equals(companyProfile.Sector)))
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate Financial Sector violation."));
+ continue;
+ }
+
+// Fetch single day price
+ Price price=GBPriceCache.GetInstance().GetPrice(symbol,tradeDate);
+ if(null==price)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price on trade date."));
+ continue;
+ }
+// Filter penny stocks - don't trade anything less than $1.00
+ if(price.Close<1.00||price.Open<1.00)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate penny stock violation."));
+ continue;
+ }
+
+// Retrieve prices
+ Prices prices=null;
+ prices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGeneratorConstants.DayCount);
+ if(null==prices||prices.Count!=(int)MomentumGeneratorConstants.DayCount)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price history."));
+ continue;
+ }
+
+// calculate the one day return
+ double return1D=prices.GetReturn1D();
+
+// Liquidity check - if any day has volume < 10,000 then we reject it
+ if(((from Price xPrice in prices where xPrice.Volume<10000 select xPrice).Count())>1)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Liquidity violation."));
+ continue;
+ }
+
+// Calculate velocity as a percentage range of the open price within the 252+20 day range of prices - This is used for display purposes
+ double velocity;
+ Prices velocityPrices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGenerator.MomentumGeneratorConstants.DayCount+20);
+ double priceHigh=(from Price selectPrice in velocityPrices select selectPrice.Open).Max();
+ double priceLow=(from Price selectPrice in velocityPrices select selectPrice.Open).Min();
+ if(0.00==priceHigh-priceLow)velocity=0.00;
+ else velocity=((price.Open-priceLow)*(100/(priceHigh-priceLow)))/100.00;
+
+// Price slopes - These are used for display purposes
+ double[] pricesArray=null;
+ LeastSquaresResult leastSquaresResult;
+
+// Get the benchmark pricing low pricing data and check the slope of previous lows; only if Beta of candidate is >= LowSlopeBetaThreshhold
+// The idea behind this check is that a high beta stock will track to the benchmark. So if the benchmark lows are forming a downward pattern then we
+// assume that this is a somewhat bearish condition. The config has the setting at a 15 day check and the threshold beta set to 1.00
+ if(config.UseLowSlopeBetaCheck && fundamental.Beta>=config.LowSlopeBetaThreshhold)
+ {
+ Prices benchmarkPrices=GBPriceCache.GetInstance().GetPrices(config.Benchmark,tradeDate,config.LowSlopeBetaDays);
+ pricesArray=Numerics.ToDouble(benchmarkPrices.GetPricesLow());
+ leastSquaresResult=Numerics.LeastSquares(pricesArray);
+ double slopeBmk=leastSquaresResult.Slope;
+ if(slopeBmk<0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Beta threshhold violation."));
+ continue;
+ }
+ }
+
+// *** MACDSignal detection
+ if(config.UseMACD)
+ {
+ MACDSetup macdSetup=new MACDSetup(config.MACDSetup);
+ MACDSignals macdSignals=MACDGenerator.GenerateMACD(prices,macdSetup);
+ Signals signalsMACD = SignalGenerator.GenerateSignals(macdSignals);
+ signalsMACD=new Signals(signalsMACD.Take(config.MACDSignalDays).ToList());
+ int weakSellSignals=(from Signal signal in signalsMACD where signal.IsWeakSell() select signal).Count();
+ int strongSellSignals=(from Signal signal in signalsMACD where signal.IsStrongSell() select signal).Count();
+ if(config.MACDRejectWeakSellSignals && weakSellSignals>0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Weak Sell violation."));
+ continue;
+ }
+ if(config.MACDRejectStrongSellSignals && strongSellSignals>0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Strong Sell violation."));
+ continue;
+ }
+ }
+
+// *** Stochastics oscillator
+ if(config.UseStochastics)
+ {
+ Stochastics stochastics=StochasticsGenerator.GenerateStochastics(prices);
+ Signals signalsStochastics=new Signals(SignalGenerator.GenerateSignals(stochastics).OrderByDescending(x => x.SignalDate).ToList());
+ signalsStochastics=new Signals(signalsStochastics.Take(config.StochasticsSignalDays).ToList());
+ int weakSellCount=(from Signal signal in signalsStochastics where signal.IsWeakSell() select signal).Count();
+ int strongSellCount=(from Signal signal in signalsStochastics where signal.IsStrongSell() select signal).Count();
+ if(config.StochasticsRejectStrongSells&&strongSellCount>0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Strong Sell violation."));
+ continue;
+ }
+ if(config.StochasticsRejectWeakSells&&weakSellCount>0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Weak Sell violation."));
+ continue;
+ }
+ }
+
+// Analyst Ratings - "Downgrades" that are more than a year old (252 days) are not considered. Mean reversion.... bad companies improve, good companies decline.
+ DateTime minRatingDate=dateGenerator.GenerateHistoricalDate(startDateOfReturns,(int)MomentumGeneratorConstants.DayCount);
+ AnalystRatings analystRatings=AnalystRatingsDA.GetAnalystRatingsMaxDateNoZacks(symbol,tradeDate);
+ analystRatings.RemoveAll(x => x.Date.50 select value).Count()>0)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate pricing contains outliers in the returns."));
+ continue;
+ }
+// Cumulative return
+ double cumulativeReturn=prices.GetCumulativeReturn();
+ if(cumulativeReturn<.10)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"Candidate cumulative returns below threshhold."));
+ continue;
+ }
+
+// Zacks Rank. This is for informational purposes for now but may further it's use in the future.
+ ZacksRank zacksRank=ZacksRankDA.GetZacksRankOnOrBefore(symbol,tradeDate);
+
+// Apply the PEScreening last because there an option to permit the inclusion of the high PE candidates if we have no other available candidates.
+// The idea is to try to avoid high PE stocks as they are more likey to introduce drawdowns as backtests have shown.
+ if(config.UseMaxPEScreen && !double.IsNaN(fundamental.PE) && fundamental.PE>config.MaxPE)
+ {
+ candidateViolations.Add(new CandidateViolation(symbol,"PE violation."));
+ MomentumCandidate highPECandidate=new MomentumCandidate();
+ highPECandidate.AnalysisDate=tradeDate;
+ highPECandidate.Symbol=symbol;
+ highPECandidate.CumReturn252=prices.GetCumulativeReturn();
+ highPECandidate.DayCount=(int)MomentumGeneratorConstants.DayCount;
+ highPECandidate.IDIndicator=IDIndicator.Calculate(prices);
+ highPECandidate.Score=ScoreIndicator.Calculate(prices);
+ highPECandidate.MaxDrawdown=prices.MaxDrawdown();
+ highPECandidate.MaxUpside=prices.MaxUpside();
+ highPECandidate.PE=fundamental.PE;
+ highPECandidate.Beta=fundamental.Beta;
+ highPECandidate.Velocity=velocity;
+ highPECandidate.Volume=price.Volume;
+ highPECandidate.Return1D=return1D;
+ if(null!=zacksRank)highPECandidate.ZacksRank=zacksRank.Rank;
+ highPECandidates.Add(highPECandidate);
+ continue;
+ }
+// *********************************************************************** C A N D I D A T E A C C E P T A N C E *******************************************************
+// At this point whatever remains is taken so initialize the candidate and add to list
+ MomentumCandidate momentumCandidate=new MomentumCandidate();
+ momentumCandidate.AnalysisDate=tradeDate;
+ momentumCandidate.Symbol=symbol;
+ momentumCandidate.CumReturn252=prices.GetCumulativeReturn();
+ momentumCandidate.DayCount=(int)MomentumGeneratorConstants.DayCount;
+ momentumCandidate.IDIndicator=IDIndicator.Calculate(prices);
+ momentumCandidate.Score=ScoreIndicator.Calculate(prices);
+ momentumCandidate.MaxDrawdown=prices.MaxDrawdown();
+ momentumCandidate.MaxUpside=prices.MaxUpside();
+ momentumCandidate.PE=fundamental.PE;
+ momentumCandidate.Beta=fundamental.Beta;
+ momentumCandidate.Velocity=velocity;
+ momentumCandidate.Volume=price.Volume;
+ momentumCandidate.Return1D=return1D;
+ if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
+ momentumCandidates.Add(momentumCandidate);
+
+ } // for all symbols
+
+ if(0!=candidateViolations.Count)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,"**************** C A N D I D A T E S U M M A R Y ************************");
+ IEnumerable> groups = candidateViolations.GroupBy(x => x.ReasonCategory).OrderByDescending(group => group.Count()).Select(group => Tuple.Create(group.Key, group.Count()));
+ foreach(Tuple group in groups)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Group: {0} Count:{1}",group.Item1, group.Item2));
+ }
+ }
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Considered : {momentumCandidates.Count+candidateViolations.Count}"));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Disqualified : {candidateViolations.Count}"));
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Eligible : {momentumCandidates.Count}"));
+ MDTrace.WriteLine(LogLevel.DEBUG,"******************************************************************************************************");
+
+// ********************************************************* E N D C A N D I D A T E S E L E C T I O N C R I T E R I A ****************************************
+// If we wind up with less than the number of required candidates then check the StrictMaxPE
+// flag and, if allowed, add the highPECandidate (that we've accumulated but skipped) to the momentumCandidates ordering them by the Lowest PE
+ if(!config.StrictMaxPE && momentumCandidates.Count0)
+ {
+ int takeCandidates=config.MaxPositions-momentumCandidates.Count;
+ highPECandidates=new MomentumCandidates(highPECandidates.OrderBy(x=>x.PE).Take(takeCandidates).ToList());
+ momentumCandidates.AddRange(highPECandidates);
+ if(config.Verbose)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("High PE Candidates,{0}",Utility.FromList((from MomentumCandidate momentumCandidate in highPECandidates select momentumCandidate.Symbol).ToList())));
+ }
+
+ QualityIndicator qualityIndicator=new QualityIndicator(config.QualityIndicatorType);
+ if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
+ {
+ momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.IDIndicator ascending, momentumCandidate.CumReturn252 descending, momentumCandidate.Return1D descending, momentumCandidate.Volume descending select momentumCandidate).ToList());
+ }
+ else
+ {
+ momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.Score descending,momentumCandidate.CumReturn252 descending,momentumCandidate.Return1D descending,momentumCandidate.Volume descending select momentumCandidate).ToList());
+ }
+ MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MomentumGenertor.GenerateMomentum:{0} candidates",momentumCandidates.Count()));
+ return momentumCandidates;
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/PositionNotification.cs b/MarketData/MarketDataLib/Generator/Momentum/PositionNotification.cs
new file mode 100755
index 0000000..5f945cb
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/PositionNotification.cs
@@ -0,0 +1,19 @@
+
+namespace MarketData.Generator.Momentum
+{
+ public class SMSNotifications : Dictionary
+ {
+ }
+ public class SMSNotification
+ {
+ public SMSNotification()
+ {
+ }
+ public DateTime NotificationTime{get;set;}
+ public int ElapsedTimeMinutes(DateTime currentTime)
+ {
+ TimeSpan elapsedTime=currentTime-NotificationTime;
+ return elapsedTime.Minutes;
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/Positions.cs b/MarketData/MarketDataLib/Generator/Momentum/Positions.cs
new file mode 100755
index 0000000..9bd77d8
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/Positions.cs
@@ -0,0 +1,303 @@
+using MarketData.Utils;
+using MarketData.Generator.Interface;
+
+namespace MarketData.Generator.Momentum
+{
+ public class Position : IPurePosition
+ {
+ public Position()
+ {
+ CurrentPrice=double.NaN;
+ }
+
+ public Position(Position position)
+ {
+ Symbol = position.Symbol;
+ PurchaseDate = position.PurchaseDate;
+ SellDate = position.SellDate;
+ Shares = position.Shares;
+ PurchasePrice = position.PurchasePrice;
+ CurrentPrice = position.CurrentPrice;
+ Volume = position.Volume;
+ Return1D = position.Return1D;
+ ZacksRank = position.ZacksRank;
+ CumReturn252 = position.CumReturn252;
+ IDIndicator = position.IDIndicator;
+ Score=position.Score;
+ MaxDrawdown = position.MaxDrawdown;
+ MaxUpside = position.MaxUpside;
+ Velocity = position.Velocity;
+ PE = position.PE;
+ Beta = position.Beta;
+ SharpeRatio = position.SharpeRatio;
+ }
+
+ public static Position Clone(Position position)
+ {
+ Position clonedPosition = new Position();
+ clonedPosition.Symbol = position.Symbol;
+ clonedPosition.PurchaseDate = position.PurchaseDate;
+ clonedPosition.SellDate = position.SellDate;
+ clonedPosition.Shares = position.Shares;
+ clonedPosition.PurchasePrice = position.PurchasePrice;
+ clonedPosition.CurrentPrice = position.CurrentPrice;
+ clonedPosition.Volume = position.Volume;
+ clonedPosition.Return1D = position.Return1D;
+ clonedPosition.ZacksRank = position.ZacksRank;
+ clonedPosition.CumReturn252 = position.CumReturn252;
+ clonedPosition.IDIndicator = position.IDIndicator;
+ clonedPosition.Score = position.Score;
+ clonedPosition.MaxDrawdown = position.MaxDrawdown;
+ clonedPosition.MaxUpside = position.MaxUpside;
+ clonedPosition.Velocity = position.Velocity;
+ clonedPosition.PE = position.PE;
+ clonedPosition.Beta = position.Beta;
+ clonedPosition.SharpeRatio = position.SharpeRatio;
+ return clonedPosition;
+ }
+
+ public String Symbol{get;set;}
+
+ public DateTime PurchaseDate{get;set;}
+
+ public DateTime SellDate{get;set;}
+
+ public double Shares{get;set;}
+
+ public double PurchasePrice{get;set;}
+
+ public double CurrentPrice{get;set;}
+
+ public double Exposure{get{return Shares*PurchasePrice;}}
+
+ public double MarketValue{get{return Shares*CurrentPrice;}}
+
+ public long Volume{get;set;}
+
+ public double Return1D{get;set;}
+
+ public double GainLoss{get{return MarketValue-Exposure;}}
+
+ public double GainLossPcnt{get{return (MarketValue-Exposure)/Exposure;}}
+
+ public String ZacksRank{get;set;}
+
+ public double CumReturn252{get;set;}
+
+ public double IDIndicator{get;set;}
+
+ public double Score{get;set;}
+
+ public double MaxDrawdown{get;set;}
+
+ public double MaxUpside{get;set;}
+
+ public double Velocity{get;set;}
+
+ public double PE{get;set;}
+
+ public double Beta{get;set;}
+
+ public double SharpeRatio { get; set; }
+
+ public virtual NVPCollection ToNVPCollection()
+ {
+ NVPCollection nvpCollection=new NVPCollection();
+ nvpCollection.Add(new NVP("Symbol",Symbol.ToString()));
+ nvpCollection.Add(new NVP("PurchaseDate",PurchaseDate.ToString()));
+ nvpCollection.Add(new NVP("SellDate",SellDate.ToString()));
+ nvpCollection.Add(new NVP("Shares",Shares.ToString()));
+ nvpCollection.Add(new NVP("PurchasePrice",PurchasePrice.ToString()));
+ nvpCollection.Add(new NVP("CurrentPrice",CurrentPrice.ToString()));
+ nvpCollection.Add(new NVP("Volume",Volume.ToString()));
+ nvpCollection.Add(new NVP("Return1D",Return1D.ToString()));
+ nvpCollection.Add(new NVP("ZacksRank",ZacksRank));
+ nvpCollection.Add(new NVP("CumReturn252",CumReturn252.ToString()));
+ nvpCollection.Add(new NVP("IDIndicator",IDIndicator.ToString()));
+ nvpCollection.Add(new NVP("Score",Score.ToString()));
+ nvpCollection.Add(new NVP("MaxDrawdown",MaxDrawdown.ToString()));
+ nvpCollection.Add(new NVP("MaxUpside",MaxUpside.ToString()));
+ nvpCollection.Add(new NVP("Velocity",Velocity.ToString()));
+ nvpCollection.Add(new NVP("PE",PE.ToString()));
+ nvpCollection.Add(new NVP("Beta",Beta.ToString()));
+ nvpCollection.Add(new NVP("SharpeRatio", SharpeRatio.ToString()));
+ return nvpCollection;
+ }
+
+ public static Position FromNVPCollection(NVPCollection nvpCollection)
+ {
+ Position position=new Position();
+ NVPDictionary nvpDictionary=nvpCollection.ToDictionary();
+ position.Symbol=nvpDictionary["Symbol"].Get();
+ position.PurchaseDate=nvpDictionary["PurchaseDate"].Get();
+ position.SellDate=nvpDictionary["SellDate"].Get();
+ position.Shares=nvpDictionary["Shares"].Get();
+ position.PurchasePrice=nvpDictionary["PurchasePrice"].Get();
+ position.CurrentPrice=nvpDictionary["CurrentPrice"].Get();
+ position.Volume=nvpDictionary["Volume"].Get();
+ position.Return1D=nvpDictionary["Return1D"].Get();
+ if(nvpDictionary.ContainsKey("ZacksRank"))position.ZacksRank=nvpDictionary["ZacksRank"].Get();
+ position.CumReturn252=nvpDictionary["CumReturn252"].Get();
+ position.IDIndicator=nvpDictionary["IDIndicator"].Get();
+ if(nvpDictionary.ContainsKey("MaxDrawdown"))position.MaxDrawdown=nvpDictionary["MaxDrawdown"].Get();
+ if(nvpDictionary.ContainsKey("MaxUpside"))position.MaxUpside=nvpDictionary["MaxUpside"].Get();
+ position.Velocity=nvpDictionary["Velocity"].Get();
+ position.PE=nvpDictionary["PE"].Get();
+ position.Beta=nvpDictionary["Beta"].Get();
+ if (nvpDictionary.ContainsKey("SharpeRatio")) position.SharpeRatio = nvpDictionary["SharpeRatio"].Get();
+ else position.SharpeRatio = double.NaN;
+ if(nvpDictionary.ContainsKey("Score")) position.Score=nvpDictionary["Score"].Get();
+ else position.Score=double.NaN;
+ return position;
+ }
+
+ public static void DisplayHeader()
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, "Symbol,Purchase Date,Shares,Purchase Price,Exposure,Volume,Return1D,Sell Date,Sell Price,Market Value,Gain Loss,Gain Loss(%),CumReturn252,IDIndicator,Score,MaxDrawdown,MaxUpside,Velocity,PE,Beta,SharpeRatio");
+ }
+
+ public void Display()
+ {
+ if (Utility.IsEpoch(SellDate) && double.IsNaN(CurrentPrice))
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},N/A,N/A,N/A,N/A,N/A,{7},{8},{9},{10},{11},{12},{13},{14},{15}",
+ Symbol,
+ Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
+ Shares,
+ Utility.AddQuotes(Utility.FormatCurrency(PurchasePrice)),
+ Utility.AddQuotes(Utility.FormatCurrency(Exposure)),
+ Volume,
+ Utility.AddQuotes(Utility.FormatPercent(Return1D)),
+ Utility.AddQuotes(Utility.FormatPercent(CumReturn252)),
+ Utility.AddQuotes(Utility.FormatNumber(IDIndicator)),
+ Utility.AddQuotes(Utility.FormatNumber(Score)),
+ Utility.AddQuotes(Utility.FormatPercent(MaxDrawdown)),
+ Utility.AddQuotes(Utility.FormatPercent(MaxUpside)),
+ Utility.AddQuotes(Utility.FormatPercent(Velocity)),
+ Utility.AddQuotes(Utility.FormatNumber(PE)),
+ Utility.AddQuotes(Utility.FormatNumber(Beta)),
+ Utility.AddQuotes(Utility.FormatNumber(SharpeRatio))
+ ));
+ }
+ else
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20}",
+ Symbol,
+ Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
+ Shares,
+ Utility.AddQuotes(Utility.FormatCurrency(PurchasePrice)),
+ Utility.AddQuotes(Utility.FormatCurrency(Exposure)),
+ Volume,
+ Utility.AddQuotes(Utility.FormatPercent(Return1D)),
+ Utility.DateTimeToStringMMHDDHYYYY(SellDate),
+ Utility.AddQuotes(Utility.FormatCurrency(CurrentPrice)),
+ Utility.AddQuotes(Utility.FormatCurrency(MarketValue)),
+ Utility.AddQuotes(Utility.FormatCurrency(GainLoss)),
+ Utility.FormatPercent(GainLossPcnt),
+ Utility.AddQuotes(Utility.FormatPercent(CumReturn252)),
+ Utility.AddQuotes(Utility.FormatNumber(IDIndicator)),
+ Utility.AddQuotes(Utility.FormatNumber(Score)),
+ Utility.AddQuotes(Utility.FormatPercent(MaxDrawdown)),
+ Utility.AddQuotes(Utility.FormatPercent(MaxUpside)),
+ Utility.AddQuotes(Utility.FormatPercent(Velocity)),
+ Utility.AddQuotes(Utility.FormatNumber(PE)),
+ Utility.AddQuotes(Utility.FormatNumber(Beta)),
+ Utility.AddQuotes(Utility.FormatNumber(SharpeRatio))
+ ));
+ }
+ }
+ }
+// ****************************************************************************************************************************************************************
+ public class Positions : List
+ {
+ public Positions()
+ {
+ }
+ public Positions(Positions positions)
+ {
+ foreach(Position position in positions)Add(position);
+ }
+ public Positions(List positions)
+ {
+ foreach(Position position in positions)Add(position);
+ }
+ public Positions(Position position)
+ {
+ Add(position);
+ }
+ public void Add(Positions positions)
+ {
+ foreach(Position position in positions)Add(position);
+ }
+ public double Exposure
+ {
+ get
+ {
+ return (from Position position in this select position.PurchasePrice*position.Shares).Sum();
+ }
+ }
+ public double MarketValue
+ {
+ get
+ {
+ return (from Position position in this select position.CurrentPrice*position.Shares).Sum();
+ }
+ }
+ public NVPCollections ToNVPCollections()
+ {
+ NVPCollections nvpCollections=new NVPCollections();
+ foreach(Position position in this)
+ {
+ nvpCollections.Add(position.ToNVPCollection());
+ }
+ return nvpCollections;
+ }
+ public static Positions FromNVPCollections(NVPCollections nvpCollections)
+ {
+ Positions positions=new Positions();
+ foreach(NVPCollection nvpCollection in nvpCollections)
+ {
+ positions.Add(Position.FromNVPCollection(nvpCollection));
+ }
+ return positions;
+ }
+ public void DisplayTopFive()
+ {
+ Positions positions = new Positions(this.OrderByDescending(x => x.GainLossPcnt).Take(5).ToList());
+ Position.DisplayHeader();
+ for (int index = 0; index < positions.Count; index++)
+ {
+ Position position = positions[index];
+ position.Display();
+ }
+ }
+ public void DisplayBottomFive()
+ {
+ Positions positions = new Positions(this.OrderBy(x => x.GainLossPcnt).Take(5).ToList());
+ Position.DisplayHeader();
+ for (int index = 0; index < positions.Count; index++)
+ {
+ Position position = positions[index];
+ position.Display();
+ }
+ }
+ public void Display()
+ {
+ Position.DisplayHeader();
+ for(int index=0;indexUtility.IsEpoch(x.SellDate)||double.IsNaN(x.CurrentPrice)))
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("EXPOSURE:{0}", Utility.FormatCurrency(this.Exposure)));
+ }
+ else
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, String.Format("GAIN/LOSS {0}", Utility.FormatCurrency(this.Sum(x => x.GainLoss))));
+ }
+ MDTrace.WriteLine(LogLevel.DEBUG,"****************************************************************************************************************************");
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/QualityIndicator.cs b/MarketData/MarketDataLib/Generator/Momentum/QualityIndicator.cs
new file mode 100755
index 0000000..a0bec0d
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/QualityIndicator.cs
@@ -0,0 +1,48 @@
+
+namespace MarketData.Generator.Momentum
+{
+ public class QualityIndicator
+ {
+ public enum QualityType{IDIndicator=0,ScoreIndicator=1};
+ private QualityType qualityType;
+ public QualityIndicator()
+ {
+ qualityType=QualityType.IDIndicator;
+ }
+ public QualityIndicator(QualityType qualityType)
+ {
+ this.qualityType=qualityType;
+ }
+ public QualityIndicator(String strQualityType)
+ {
+ if(null==strQualityType)
+ {
+ qualityType=QualityType.IDIndicator;
+ return;
+ }
+ strQualityType=strQualityType.ToUpper();
+ if("SCOREINDICATOR".Equals(strQualityType))qualityType=QualityType.ScoreIndicator;
+ else qualityType=QualityType.IDIndicator;
+ }
+ public override String ToString()
+ {
+ if(qualityType.Equals(QualityType.ScoreIndicator))return "SCOREINDICATOR";
+ return "IDINDICATOR";
+ }
+ public static String ToString(QualityIndicator.QualityType qualityType)
+ {
+ if(qualityType.Equals(QualityType.ScoreIndicator))return "SCOREINDICATOR";
+ return "IDINDICATOR";
+ }
+ public static QualityType ToQuality(String strQualityType)
+ {
+ if(strQualityType.Equals("SCOREINDICATOR")) return QualityIndicator.QualityType.ScoreIndicator;
+ return QualityIndicator.QualityType.IDIndicator;
+ }
+ public QualityType Quality
+ {
+ get{return qualityType;}
+ set{qualityType=value;}
+ }
+ }
+}
diff --git a/MarketData/MarketDataLib/Generator/Momentum/ScoreIndicator.cs b/MarketData/MarketDataLib/Generator/Momentum/ScoreIndicator.cs
new file mode 100755
index 0000000..16aea6a
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/ScoreIndicator.cs
@@ -0,0 +1,32 @@
+using MarketData.MarketDataModel;
+using MarketData.Numerical;
+
+namespace MarketData.Generator.Momentum
+{
+ public class ScoreIndicator
+ {
+ private ScoreIndicator()
+ {
+ }
+// This uses the same scoring mechanism as is used in Clenow Momentum which is based upon the log of returns.
+// The higher the score the better the rank. The logPrices array gets filled with the most distant price in the lower indices. Hence the backward walk through the prices.
+ public static double Calculate(Prices prices)
+ {
+ double score=double.NaN;
+ if(null==prices || 0==prices.Count)return double.NaN;
+ double[] logPrices=new double[prices.Count];
+ for(int index=prices.Count-1;index>=0;index--)
+ {
+ Price price=prices[index];
+ logPrices[(prices.Count-index)-1]=Math.Log(price.Close);
+ }
+ LeastSquaresResultWithR2 leastSquaresResult=LeastSquaresHelper.CalculateLeastSquaresWithR2(logPrices);
+ double slope=leastSquaresResult.Slope;
+ double annualizedReturn=Math.Pow(Math.Exp(slope),252);
+ if(slope<0.00)annualizedReturn*=-1.00;
+ score=leastSquaresResult.RSquared*annualizedReturn;
+ return score;
+ }
+ }
+}
+
diff --git a/MarketData/MarketDataLib/Generator/Momentum/SlotPosition.cs b/MarketData/MarketDataLib/Generator/Momentum/SlotPosition.cs
new file mode 100755
index 0000000..7461778
--- /dev/null
+++ b/MarketData/MarketDataLib/Generator/Momentum/SlotPosition.cs
@@ -0,0 +1,69 @@
+using MarketData.Utils;
+
+namespace MarketData.Generator.Momentum
+{
+ public class SlotPositions : List
+ {
+ public SlotPositions()
+ {
+ }
+ public SlotPositions(int slot,Positions positions)
+ {
+ for(int index=0;index();
+ return slotPosition;
+ }
+ }
+}