From 47ccedf1fbb3dcf1fdca290a54954b8b612da1b5 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 6 Feb 2025 16:50:01 -0500 Subject: [PATCH] Add the MGSHMomentumHelper --- App.config | 2 +- MGSHMomentumHelper.cs | 127 ++++++++++++++++++++ MarketData.csproj | 1 + Program.cs | 271 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 MGSHMomentumHelper.cs diff --git a/App.config b/App.config index fa28871..9f87935 100644 --- a/App.config +++ b/App.config @@ -19,6 +19,6 @@ - + diff --git a/MGSHMomentumHelper.cs b/MGSHMomentumHelper.cs new file mode 100644 index 0000000..2227278 --- /dev/null +++ b/MGSHMomentumHelper.cs @@ -0,0 +1,127 @@ +using MarketData.Generator.MGSHMomentum; +using System; +using System.Collections.Generic; + +namespace MarketData +{ + public static class MGSHMomentumHelper + { + public static void HandleMGSHSession(String[] args) + { + CommandArgs commandArgs = new CommandArgs(args); + if(!commandArgs.Has("SESSIONFILE")) + { + MDTrace.WriteLine(LogLevel.DEBUG,"Missing SESSIONFILE"); + return; + } + MGSHMomentumBacktest momentumBacktest = new MGSHMomentumBacktest(); + momentumBacktest.DisplaySession(commandArgs.Coalesce("SESSIONFILE")); + } + + public static void HandleMGSHRunDaily(String[] args) + { + CommandArgs commandArgs = new CommandArgs(args); + if(!commandArgs.Has("SESSIONFILE,TRADEDATE")) + { + MDTrace.WriteLine(LogLevel.DEBUG,"SESSIONFILE and TRADEDATE are required parameters."); + return; + } + DateTime tradeDate = commandArgs.Get("TRADEDATE"); + String pathSessionFile = commandArgs.Get("PATHSESSIONFILE"); + MGSHMomentumBacktest momentumBacktest = new MGSHMomentumBacktest(); + MGSHBacktestResult backtestResult = momentumBacktest.UpdateDaily(tradeDate, tradeDate, pathSessionFile); + backtestResult.Display(); + } + + public static void HandleMGSHRunBacktest(String[] args) + { + CommandArgs commandArgs = new CommandArgs(args); + MGSHConfiguration mgParams=new MGSHConfiguration(); + 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); + } + + if(commandArgs.Has("USESTOPLIMITS")) + { + mgParams.UseStopLimits=commandArgs.Coalesce("USESTOPLIMITS",false); + } + + if(commandArgs.Has("STOPLIMITRISKPERCENTDECIMAL")) + { + mgParams.StopLimitRiskPercentDecimal=commandArgs.Coalesce("STOPLIMITRISKPERCENTDECIMAL",.12); + } + + if(commandArgs.Has("USEHEDGING")) + { + mgParams.UseHedging=commandArgs.Coalesce("USEHEDGING",false); + if(commandArgs.Has("INITIALHEDGECASH")) + { + mgParams.HedgeInitialCash=commandArgs.Get("INITIALHEDGECASH"); + } + } + + if(commandArgs.Has("KEEPSLOTPOSITIONS")) + { + mgParams.KeepSlotPositions=commandArgs.Get("KEEPSLOTPOSITIONS"); + } + +// ** 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); + } +// ** + MGSHQualityIndicator qualityIndicator=new MGSHQualityIndicator(MGSHQualityIndicator.QualityType.IDIndicator); + if(commandArgs.Has("QUALITYINDICATORTYPE")) qualityIndicator.Quality=MGSHQualityIndicator.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(); + MGSHMomentumBacktest backtestMomentum=new MGSHMomentumBacktest(); + results.Add(backtestMomentum.PerformBacktest(startDate,endDate,pathSessionFileName,mgParams)); + } + } +} diff --git a/MarketData.csproj b/MarketData.csproj index 7d288d8..c903eab 100644 --- a/MarketData.csproj +++ b/MarketData.csproj @@ -72,6 +72,7 @@ + diff --git a/Program.cs b/Program.cs index 5fbd5a9..eb6beb3 100644 --- a/Program.cs +++ b/Program.cs @@ -25,6 +25,7 @@ using System.Data; using MarketData.CNNProcessing; using MySql.Data.MySqlClient; using MarketData.Generator.MovingAverage; +using MarketData.Generator.MGSHMomentum; namespace MarketData { @@ -190,6 +191,10 @@ namespace MarketData MDTrace.WriteLine(LogLevel.DEBUG," MGCLOSEPOSITION /SESSIONFILE: /SYMBOL: /PURCHASEDATE: /SELLPRICE: /SELLDATE: sells the specified position in the session file with either the supplied sell date and sell price."); MDTrace.WriteLine(LogLevel.DEBUG," MGUPDATEPRICE /SYMBOL: /TRADEDATE: /PRICE: /SESSIONFILE:"); MDTrace.WriteLine(LogLevel.DEBUG," RUNBACKTEST /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{ENDDATE}: /SESSIONFILE:"); + MDTrace.WriteLine(LogLevel.DEBUG,"********** M G S H M O M E N T U M *********"); + MDTrace.WriteLine(LogLevel.DEBUG," MGSHSESSION /SESSIONFILE:"); + MDTrace.WriteLine(LogLevel.DEBUG," MGSHRUNBACKTEST /USEHEDGING: /HEDGEINITIALCASH: /USESTOPLIMITS: /KEEPSLOTPOSITIONS: /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{ENDDATE}: /SESSIONFILE: "); + MDTrace.WriteLine(LogLevel.DEBUG," MGSHRUNDAILY /SESSIONFILE: /TRADEDATE:"); MDTrace.WriteLine(LogLevel.DEBUG, "*********** C L E N O W M O M E N T U M (C M M O M E N T U M )************"); MDTrace.WriteLine(LogLevel.DEBUG, " RUNCMBACKTEST /STARTDATE: /MAXPOSITIONS: /INITIALCASH: /HOLDINGPERIOD: /{USEBINBASEDPOSITIONSIZING}: /{USEBINBASEDPOSITIONSIZINGNUMBINS}: /{TARGETBETA}: /{ENDDATE}: /SESSIONFILE: /{USECNN}: /{USECNNHOST}: /{USECNNDAYCOUNT}:"); MDTrace.WriteLine(LogLevel.DEBUG, " CMSESSION /SESSIONFILE:"); @@ -758,6 +763,212 @@ namespace MarketData outStream.Close(); outStream.Dispose(); } +// ***************************************************************************************************************************************************** + + public static void HedgeTest() + { + String hedgeShortSymbol="SH"; + double initialRiskPercentDecimal=.12; + double cash=10000; + double cashToInvest=3000; + double totalGainLoss=0.00; + bool hedgeOn=false; + int totalWinners=0; + int totalLosers=0; + int hedgeStopLimitScalingVolatilityDays=14; + int hedgeMinDaysBetweenStopAdjustments=1; + + MGSHPosition position = default; + + + MDTrace.WriteLine(LogLevel.DEBUG,"START"); + DateTime analysisStartDate=DateTime.Parse("01/31/2018"); +// DateTime analysisStartDate=DateTime.Parse("01/01/2011"); +// DateTime analysisStartDate=DateTime.Parse("06/06/2018"); + DateTime analysisEndDate=DateTime.Parse("12/31/2024"); + Prices prices = PricingDA.GetPrices(hedgeShortSymbol); + + prices=new Prices(prices.Where(x => x.Date > analysisStartDate).ToList()); + + BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices); + BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate(); + + PricesByDate pricesByDate = prices.GetPricesByDate(); + List dates = new List(pricesByDate.Keys); + + MGSHPositions AllPositions = new MGSHPositions(); + MarketData.Generator.Model.StopLimits StopLimits = new MarketData.Generator.Model.StopLimits(); + + HedgeManager hedgeManager = new HedgeManager(){Verbose=true}; + + for(int dateIndex=dates.Count-1;dateIndex>=0;dateIndex--) + { + DateTime analysisDate = dates[dateIndex]; + if(analysisDate > analysisEndDate)break; + if(!bollingerBandElementsByDate.ContainsKey(analysisDate))continue; + BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate]; + + if(!hedgeOn && hedgeManager.IsOpenHedgeIndicator(analysisDate)) + { + hedgeOn=true; + position = new MGSHPosition(); + position.Symbol=hedgeShortSymbol; + position.PurchasePrice = bollingerBandElement.Close; + position.PurchaseDate = analysisDate; + position.SellDate=Utility.Epoch; + position.InitialStopLimit=position.PurchasePrice/(1.00+initialRiskPercentDecimal); + position.TrailingStopLimit=position.InitialStopLimit; + position.LastStopAdjustment=Utility.Epoch; + position.R=initialRiskPercentDecimal; + double cashAllocation = Math.Min(cash, cashToInvest); + position.Shares=(int)(cashAllocation/bollingerBandElement.Close); + cash-=position.Shares*bollingerBandElement.Close; + +// This is for the simulation so that I can see the initial position as a stop limit in the UI + MarketData.Generator.Model.StopLimit stopLimit = new MarketData.Generator.Model.StopLimit() + { + Symbol=hedgeShortSymbol, + AnalysisDate=analysisDate, + PreviousStop=position.PurchasePrice, + NewStop=position.TrailingStopLimit, + CurrentPriceLow=0.00, + CurrentPriceClose=0.00, + PriceTrendIndicatorSlope=0.00 + }; + StopLimits.Add(stopLimit); + + MDTrace.WriteLine(LogLevel.DEBUG,$"Hedge BUY {analysisDate.ToShortDateString()} {Utility.FormatNumber(position.Shares,2)} shares @ {Utility.FormatCurrency(bollingerBandElement.Close)} Exposure:{Utility.FormatCurrency(position.Shares*bollingerBandElement.Close)}"); + } + else if(hedgeOn) // This will check for stop loss violation and close the position + { + Price price = GBPriceCache.GetInstance().GetPrice(hedgeShortSymbol, analysisDate); + if(null == price) + { + MDTrace.WriteLine(LogLevel.DEBUG,$"No price for {hedgeShortSymbol} on {analysisDate.ToShortDateString()}"); + continue; + } + if(price.Close0)totalWinners++; + else totalLosers++; + totalGainLoss+=position.GainLoss; + cash+=position.MarketValue; + MDTrace.WriteLine(LogLevel.DEBUG,$"Hedge SELL {analysisDate.ToShortDateString()} {Utility.FormatNumber(position.Shares,2)} shares @ {Utility.FormatCurrency(position.CurrentPrice)} Gain/Loss:{Utility.FormatCurrency(position.GainLoss)} Gain/Loss(%):{Utility.FormatPercent(position.GainLossPcnt,2)}"); + MDTrace.WriteLine(LogLevel.DEBUG,"**************************************************"); + AllPositions.Add(position); + + MarketData.Generator.Model.StopLimit stopLimit = new MarketData.Generator.Model.StopLimit() + { + Symbol=hedgeShortSymbol, + AnalysisDate=analysisDate, + PreviousStop=position.PurchasePrice, + NewStop=position.CurrentPrice, + CurrentPriceLow=0.00, + CurrentPriceClose=0.00, + PriceTrendIndicatorSlope=0.00 + }; + StopLimits.Add(stopLimit); + } + else if(hedgeManager.IsLowerBandBreakIndicator(analysisDate, position)) + { + MarketData.Generator.Model.StopLimit stopLimit = hedgeManager.EvaluateStopPriceHedge(analysisDate, position, hedgeStopLimitScalingVolatilityDays, hedgeMinDaysBetweenStopAdjustments); + if(null != stopLimit)StopLimits.Add(stopLimit); + } + } + } + MGSHSessionParams sessionParams = new MGSHSessionParams(); + sessionParams.LastUpdated=DateTime.Now; + sessionParams.TradeDate=DateTime.Now; + sessionParams.StartDate=DateTime.Now; + sessionParams.AnalysisDate=DateTime.Now; + sessionParams.Configuration=new MGSHConfiguration(); + sessionParams.ActivePositions=new MGSHActivePositions(); + sessionParams.AllPositions=AllPositions; + sessionParams.CashBalance=cash; + sessionParams.NonTradeableCash=0.00; + sessionParams.StopLimits=StopLimits; + sessionParams.HedgePositions= new MGSHPositions(); + sessionParams.Cycle = 0; + + MGSHSessionManager mgSession = new MGSHSessionManager(); + mgSession.SaveSession(sessionParams,"MGSH_PROTO_20250128_2.TXT"); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Gain/Loss:{Utility.FormatCurrency(totalGainLoss)}")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Cash:{Utility.FormatCurrency(cash)}")); + MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Open:{hedgeOn}")); + + return; + } +// **************************************************************************************************************************************************************** + public static void CrossoverExample() + { + // Crossover examples. + // Example 1 + //List symbols = PricingDA.GetSymbols(); + //List crossovers = new MovingAverageCrossovers(); + //DateTime analysisDate = DateTime.Parse("12/31/2024"); + //for (int index = 0; index < symbols.Count; index++) + //{ + // if (0 == (index % 500)) Console.WriteLine($"Working on {index}"); + // String symbol = symbols[index]; + // Prices prices = GBPriceCache.GetInstance().GetPrices(symbol, analysisDate, 252); + // MovingAverageCrossovers movingAverageCrossovers = ExponentialMovingAverageCrossover.GetMovingAverageCrossovers(prices, .05); // 5% + // movingAverageCrossovers = new MovingAverageCrossovers(movingAverageCrossovers.Where(x => x.EventDate.Equals(analysisDate)).ToList()); + // crossovers.AddRange(movingAverageCrossovers); + //} + //foreach (MovingAverageCrossover movingAverageCrossover in crossovers) + //{ + // Console.WriteLine(movingAverageCrossover.ToString()); + //} + + // Example 2 + //String symbol = "SH"; + //DateTime analysisDate = DateTime.Parse("12/31/2024"); + //Prices prices = GBPriceCache.GetInstance().GetPrices(symbol, analysisDate, 252); + //MovingAverageCrossovers movingAverageCrossovers = ExponentialMovingAverageCrossover.GetMovingAverageCrossovers(prices, .025); + //foreach (MovingAverageCrossover movingAverageCrossover in movingAverageCrossovers) + //{ + // Console.WriteLine(movingAverageCrossover.ToString()); + //} + + //DMAPrices fastDMAPrices = MovingAverageGenerator.GenerateExponentialMovingAverage(prices, 9); + //DMAPrices slowDMAPrices=MovingAverageGenerator.GenerateExponentialMovingAverage(prices, 41); + //DMAPricesByDate fastDMAPricesByDate=fastDMAPrices.GetDMAPricesByDate(); + //DMAPricesByDate slowDMAPricesByDate=slowDMAPrices.GetDMAPricesByDate(); + //List availableDates = new List(slowDMAPricesByDate.Keys); + //availableDates.Sort((a, b) => DateTime.Compare(a,b)); // earliest date should be in the lowest index, most recent date should be in the highest 2024[0], 2025[1], 2026[2] for example + //StreamWriter streamWriter = new StreamWriter(new FileStream("MovingAverage.csv",FileMode.Create)); + //streamWriter.WriteLine("Date,Fast,Slow"); + //foreach(DateTime date in availableDates) + //{ + // DMAPrice fastDMAPrice = fastDMAPricesByDate[date]; + // DMAPrice slowDMAPrice = slowDMAPricesByDate[date]; + // streamWriter.WriteLine($"{date.ToShortDateString()},{fastDMAPrice.AVGPrice},{slowDMAPrice.AVGPrice}"); + //} + //streamWriter.Flush(); + //streamWriter.Close(); + //streamWriter.Dispose(); + + // Example 3 + //DateTime beginDate = DateTime.Parse("12/31/2018"); + //DateTime stopDate = DateTime.Parse("12/31/2024"); + //String symbol="^VIX"; + //DateGenerator dateGenerator = new DateGenerator(); + //while(beginDate<=stopDate) + //{ + // Prices prices = GBPriceCache.GetInstance().GetPrices(symbol, beginDate, 252); + // MovingAverageCrossovers movingAverageCrossovers = ExponentialMovingAverageCrossover.GetMovingAverageCrossovers(prices, .025, 20, 50); // 1.75% + // if(null != movingAverageCrossovers && movingAverageCrossovers.Count>0 && movingAverageCrossovers[0].EventDate.Equals(beginDate)) + // { + // MDTrace.WriteLine(LogLevel.DEBUG,$"Found a crossover {movingAverageCrossovers[0].ToString()}"); + // } + // beginDate = dateGenerator.FindNextBusinessDay(beginDate); + //} + } + @@ -771,19 +982,40 @@ namespace MarketData Trace.Listeners.Add(new TextWriterTraceListener(strLogFile)); DateTime currentDate=DateTime.Now; - Prices prices = new Prices(); - prices.Add(new Price(){Date=DateTime.Parse("1/28/2025"),Open=10,High=10,Low=10,Close=10}); - prices.Add(new Price(){Date=DateTime.Parse("1/29/2025"),Open=15,High=15,Low=15,Close=15}); - prices.Add(new Price(){Date=DateTime.Parse("1/30/2025"),Open=5,High=5,Low=5,Close=5}); - prices.Add(new Price(){Date=DateTime.Parse("1/31/2025"),Open=20,High=20,Low=20,Close=20}); - prices.Add(new Price(){Date=DateTime.Parse("2/1/2025"),Open=10,High=10,Low=10,Close=10}); - prices.Add(new Price(){Date=DateTime.Parse("2/2/2025"),Open=25,High=25,Low=25,Close=25}); - prices.Add(new Price(){Date=DateTime.Parse("2/3/2025"),Open=30,High=30,Low=30,Close=30}); - DMAPrices dmaPrices = MovingAverageGenerator.GenerateMovingAverage(prices, 2); - DMAPrices emaPrices = MovingAverageGenerator.GenerateExponentialMovingAverage(prices, 2); +// CompanyProfile c1 = MarketDataHelper.GetCompanyProfile("AAME"); + CompanyProfiles companyProfiles = CompanyProfileDA.GetCompanyProfiles(); + List symbols = companyProfiles.Select(x => x.Symbol).ToList(); + foreach(String symbol in symbols) + { + CompanyProfile companyProfile = MarketDataHelper.GetCompanyProfile(symbol); + if (null != companyProfile) { + MDTrace.WriteLine(LogLevel.DEBUG, $"Symbol:{symbol} Sector:{companyProfile.Sector} Industry:{companyProfile.Industry} Description:{companyProfile.Description}"); + MDTrace.WriteLine(LogLevel.DEBUG, ""); + } + Thread.Sleep(2000); + } + + + + +// CompanyProfile companyProfile = MarketDataHelper.GetCompanyProfile("MOD"); + //PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades(); + //List symbols = portfolioTrades.Select(x => x.Symbol).Distinct().ToList(); + //for (int rindex=0; rindexmaxHolidayDate) @@ -1461,7 +1693,22 @@ namespace MarketData Program.HandleCMTSession(args); } // ************************************************************************************************************************************************************************************************************************* -// ************************************************************************************************** M G M O M E N T U M ************************************************************************************************* +// ************************************************************************************************** M G S H M O M E N T U M ************************************************************************************************* +// ************************************************************************************************************************************************************************************************************************* + else if(arg.Equals("MGSHSESSION")) + { + MGSHMomentumHelper.HandleMGSHSession(args); + } + else if(arg.Equals("MGSHRUNBACKTEST")) + { + MGSHMomentumHelper.HandleMGSHRunBacktest(args); + } + else if(arg.Equals("MGSHRUNDAILY")) + { + MGSHMomentumHelper.HandleMGSHRunDaily(args); + } +// ************************************************************************************************************************************************************************************************************************* +// ************************************************************************************************** M G M O M E N T U M ************************************************************************************************* // ************************************************************************************************************************************************************************************************************************* else if (arg.Equals("RUNMOMENTUM")) { @@ -2011,7 +2258,7 @@ namespace MarketData ThreadPool.QueueUserWorkItem(delegate { - UpdateAnalystPriceTarget(); // finance.yahoo.com + UpdateAnalystPriceTarget(); // MarketBeat GetETFHoldings(); // finance.yahoo.com resetEvents[STAGE_9].Set(); });