From 1e130e3a15ea1944b0fa4da4453127e410207771 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 31 Jan 2025 18:13:22 -0500 Subject: [PATCH] Added ExponentialMovingAverageCrossover and moved all of the moving average stuff into it's own folder. --- MarketDataLib/CNNProcessing/DataProcessor.cs | 1 + .../Generator/BollingerBandGenerator.cs | 1 + .../CMMomentum/CMCandidateGenerator.cs | 1 + .../CMMomentum/CMMomentumGenerator.cs | 1 + .../CMTrend/CMTCandidateGenerator.cs | 1 + .../Generator/CMTrend/CMTTrendModel..cs | 1 + .../ModelGenerators/VolatilityGenerator.cs | 3 +- .../ExponentialMovingAverageCrossover.cs | 212 ++++++++++++++++++ .../MovingAverage/MovingAverageCrossover.cs | 47 ++++ .../MovingAverageGenerator.cs | 4 +- .../Generator/StochasticsGenerator.cs | 1 + .../Helper/MovingAverageHelperSheet.cs | 1 + MarketDataLib/MarketDataLib.csproj | 4 +- MarketDataLib/MarketDataModel/YieldCurve.cs | 1 + Program.cs | 18 +- 15 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 MarketDataLib/Generator/MovingAverage/ExponentialMovingAverageCrossover.cs create mode 100644 MarketDataLib/Generator/MovingAverage/MovingAverageCrossover.cs rename MarketDataLib/Generator/{ => MovingAverage}/MovingAverageGenerator.cs (99%) diff --git a/MarketDataLib/CNNProcessing/DataProcessor.cs b/MarketDataLib/CNNProcessing/DataProcessor.cs index 76b49e1..5ed95a1 100644 --- a/MarketDataLib/CNNProcessing/DataProcessor.cs +++ b/MarketDataLib/CNNProcessing/DataProcessor.cs @@ -11,6 +11,7 @@ using MarketData.MarketDataModel; using MarketData.Generator; using MarketData.Utils; using System.Drawing.Drawing2D; +using MarketData.Generator.MovingAverage; namespace MarketData.CNNProcessing { diff --git a/MarketDataLib/Generator/BollingerBandGenerator.cs b/MarketDataLib/Generator/BollingerBandGenerator.cs index 1c40a4f..d591a67 100644 --- a/MarketDataLib/Generator/BollingerBandGenerator.cs +++ b/MarketDataLib/Generator/BollingerBandGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; // Filename: BollingerBandGenerator.cs // Author:Sean Kessler diff --git a/MarketDataLib/Generator/CMMomentum/CMCandidateGenerator.cs b/MarketDataLib/Generator/CMMomentum/CMCandidateGenerator.cs index 57bf8d7..60309d4 100644 --- a/MarketDataLib/Generator/CMMomentum/CMCandidateGenerator.cs +++ b/MarketDataLib/Generator/CMMomentum/CMCandidateGenerator.cs @@ -1,5 +1,6 @@ using MarketData.DataAccess; using MarketData.Generator.Indicators; +using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; using MarketData.Numerical; using MarketData.Utils; diff --git a/MarketDataLib/Generator/CMMomentum/CMMomentumGenerator.cs b/MarketDataLib/Generator/CMMomentum/CMMomentumGenerator.cs index 41058ba..48e7e73 100644 --- a/MarketDataLib/Generator/CMMomentum/CMMomentumGenerator.cs +++ b/MarketDataLib/Generator/CMMomentum/CMMomentumGenerator.cs @@ -1,5 +1,6 @@ using MarketData.CNNProcessing; using MarketData.DataAccess; +using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; using MarketData.Utils; using System; diff --git a/MarketDataLib/Generator/CMTrend/CMTCandidateGenerator.cs b/MarketDataLib/Generator/CMTrend/CMTCandidateGenerator.cs index 823be3f..a740cec 100644 --- a/MarketDataLib/Generator/CMTrend/CMTCandidateGenerator.cs +++ b/MarketDataLib/Generator/CMTrend/CMTCandidateGenerator.cs @@ -1,6 +1,7 @@ using MarketData.Cache; using MarketData.DataAccess; using MarketData.Generator.Indicators; +using MarketData.Generator.MovingAverage; using MarketData.Helper; using MarketData.MarketDataModel; using MarketData.Numerical; diff --git a/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs b/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs index 5cab01c..8587d04 100644 --- a/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs +++ b/MarketDataLib/Generator/CMTrend/CMTTrendModel..cs @@ -18,6 +18,7 @@ using StopLimit=MarketData.Generator.Model.StopLimit; using StopLimits=MarketData.Generator.Model.StopLimits; using MarketData.Generator.ModelGenerators; using Axiom.Interpreter; +using MarketData.Generator.MovingAverage; namespace MarketData.Generator.CMTrend { diff --git a/MarketDataLib/Generator/ModelGenerators/VolatilityGenerator.cs b/MarketDataLib/Generator/ModelGenerators/VolatilityGenerator.cs index 56c1e48..e19aa8b 100644 --- a/MarketDataLib/Generator/ModelGenerators/VolatilityGenerator.cs +++ b/MarketDataLib/Generator/ModelGenerators/VolatilityGenerator.cs @@ -1,4 +1,5 @@ -using MarketData.MarketDataModel; +using MarketData.Generator.MovingAverage; +using MarketData.MarketDataModel; using MarketData.Utils; using System; using System.Collections.Generic; diff --git a/MarketDataLib/Generator/MovingAverage/ExponentialMovingAverageCrossover.cs b/MarketDataLib/Generator/MovingAverage/ExponentialMovingAverageCrossover.cs new file mode 100644 index 0000000..4585179 --- /dev/null +++ b/MarketDataLib/Generator/MovingAverage/ExponentialMovingAverageCrossover.cs @@ -0,0 +1,212 @@ +using MarketData.Cache; +using MarketData.MarketDataModel; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MarketData.Generator.MovingAverage +{ + public static class ExponentialMovingAverageCrossover + { + public static MovingAverageCrossovers GetMovingAverageCrossovers(String symbol, DateTime analysisDate,double threshholdPercentDecimal=.05, int fastCrossOverDays=9, int slowCrossoverDays=41) + { + Prices prices = GBPriceCache.GetInstance().GetPrices(symbol, analysisDate, slowCrossoverDays * 6); + if(prices == null || 0==prices.Count)return new MovingAverageCrossovers(); + + return GetMovingAverageCrossovers(prices, threshholdPercentDecimal, fastCrossOverDays, slowCrossoverDays); + } + + /// + /// GetMovingAverageCrossovers + /// Prices will be in descending order with the most recent price at index[0] and the least recent price at index[count-1] + /// + /// + /// The fast crossover. Tom Basso uses 9 + /// The slow crosser. Tom Basso uses 41 + /// The threshhold percent. 5% would be .05 + /// + public static MovingAverageCrossovers GetMovingAverageCrossovers(Prices prices, double threshholdPercentDecimal=.05, int fastCrossOverDays=9, int slowCrossoverDays=41) + { + MovingAverageCrossovers movingAverageCrossovers = new MovingAverageCrossovers(); + + if(prices == null || 0==prices.Count || prices.Count<=slowCrossoverDays)return movingAverageCrossovers; + if(fastCrossOverDays >= slowCrossoverDays)return movingAverageCrossovers; + + DMAPrices fastDMAPrices = MovingAverageGenerator.GenerateExponentialMovingAverage(prices, fastCrossOverDays); + DMAPrices slowDMAPrices = MovingAverageGenerator.GenerateExponentialMovingAverage(prices, slowCrossoverDays); + + if(null == fastDMAPrices || 0 == fastDMAPrices.Count)return movingAverageCrossovers; + if(null == slowDMAPrices || 0 == slowDMAPrices.Count)return movingAverageCrossovers; + + FindCrossovers(fastDMAPrices, slowDMAPrices, movingAverageCrossovers, threshholdPercentDecimal); + +// put the most recent item in the lowest index + movingAverageCrossovers = new MovingAverageCrossovers((movingAverageCrossovers as List).Where(x => x.IsChangeInTrend==true).OrderByDescending(x => x.EventDate).ToList()); + return movingAverageCrossovers; + } + + /// + /// If the sorter term (faster) crossed the longer term (slower) moving average then a direction change is noted. + /// If the change carried more than 5% in that direction then consider it a successful trend + /// + /// + /// + /// + private static void FindCrossovers(DMAPrices fastDMAPrices, DMAPrices slowDMAPrices,MovingAverageCrossovers movingAverageCrossovers, double threshholdPercentDecimal) + { + double trendChangePercentThreshhold = threshholdPercentDecimal*100.00; // if a change in direction carries more than 5% then consider is a successful trend + movingAverageCrossovers.Clear(); + DateTime startDate = slowDMAPrices[slowDMAPrices.Count-1].Date; + MovingAverageCrossover movingAverageCrossover = default; + + 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 + + int currentDirection=0; // 1:fast DMA is above the slow, -1:fast DMA is below the slow, 0:slow DMA == fastDMA + + DMAPrice prevSlowDMAPrice = default; + DMAPrice prevFastDMAPrice = default; + + for(int index=0;index slowDMAPrice.AVGPrice) + { + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.UpStart; + movingAverageCrossover.IsChangeInTrend = false; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + movingAverageCrossover.ChangePercent=0.00; + movingAverageCrossovers.Add(movingAverageCrossover); + currentDirection = 1; + } + else if(fastDMAPrice.AVGPrice < slowDMAPrice.AVGPrice) + { + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.DownStart; + movingAverageCrossover.IsChangeInTrend = false; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + movingAverageCrossover.ChangePercent=0.00; + movingAverageCrossovers.Add(movingAverageCrossover); + currentDirection = -1; + } + else + { + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.NeutralStart; + movingAverageCrossover.IsChangeInTrend = false; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + movingAverageCrossover.ChangePercent=0.00; + movingAverageCrossovers.Add(movingAverageCrossover); + currentDirection = 0; + } + prevSlowDMAPrice = slowDMAPrice; + prevFastDMAPrice = fastDMAPrice; + } + else + { + if(fastDMAPrice.AVGPrice > slowDMAPrice.AVGPrice) + { + if(1 == currentDirection) // The fast is above the slow as it was previously + { + if(movingAverageCrossovers.Count>0) // check the last change record for a threshhold break + { + MovingAverageCrossover movingAverageCrossOver = movingAverageCrossovers[movingAverageCrossovers.Count-1]; + double changePercent = ((fastDMAPrice.AVGPrice - movingAverageCrossOver.FastDMAPrice.AVGPrice)/movingAverageCrossOver.FastDMAPrice.AVGPrice)*100.00; + if(changePercent > 0.00 && changePercent > trendChangePercentThreshhold) // if the threshhold is exceeded then create a new record and mark it as a trend change + { + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.UpCross; + movingAverageCrossover.IsChangeInTrend = true; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + movingAverageCrossover.ChangePercent=changePercent; + movingAverageCrossovers.Add(movingAverageCrossover); + } + } + } + else if(-1 == currentDirection || 0 == currentDirection) // The fast is above the slow and it was below the slow previously + { + currentDirection = 1; // switch directions + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.UpCross; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + double changePercent = Math.Abs(((fastDMAPrice.AVGPrice - slowDMAPrice.AVGPrice)/slowDMAPrice.AVGPrice)*100.00); + movingAverageCrossover.ChangePercent=changePercent; + if(changePercent>trendChangePercentThreshhold)movingAverageCrossover.IsChangeInTrend = true; + else movingAverageCrossover.IsChangeInTrend = false; + movingAverageCrossovers.Add(movingAverageCrossover); + } + } + else if(fastDMAPrice.AVGPrice < slowDMAPrice.AVGPrice) + { + if(-1 == currentDirection) // The fast is below the slow as it was previously + { + if(movingAverageCrossovers.Count>0) // check the last change record for a threshhold break + { + movingAverageCrossover = movingAverageCrossovers[movingAverageCrossovers.Count-1]; + double changePercent = Math.Abs(((fastDMAPrice.AVGPrice - movingAverageCrossover.FastDMAPrice.AVGPrice)/movingAverageCrossover.FastDMAPrice.AVGPrice)*100.00); + if(changePercent<0.00 && Math.Abs(changePercent)>trendChangePercentThreshhold) // if the threshhold is exceeded then create a new record and mark it as a trend change + { + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.UpCross; + movingAverageCrossover.IsChangeInTrend = true; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + movingAverageCrossover.ChangePercent = changePercent; + movingAverageCrossovers.Add(movingAverageCrossover); + } + } + } + else if(1 == currentDirection || 0 == currentDirection) // The fast is below the slow but it was above the slow previously so we've crossed + { + currentDirection = -1; // switch directions + movingAverageCrossover = new MovingAverageCrossover(); + movingAverageCrossover.EventDate = analysisDate; + movingAverageCrossover.Symbol = slowDMAPrice.Symbol; + movingAverageCrossover.CrossOverDirection = MovingAverageCrossover.CrossoverDirectionEnum.DownCross; + movingAverageCrossover.SlowDMAPrice = slowDMAPrice; + movingAverageCrossover.FastDMAPrice = fastDMAPrice; + double changePercent = Math.Abs(((fastDMAPrice.AVGPrice - slowDMAPrice.AVGPrice)/slowDMAPrice.AVGPrice)*100.00); + movingAverageCrossover.ChangePercent=changePercent; + if(changePercent>trendChangePercentThreshhold)movingAverageCrossover.IsChangeInTrend = true; + else movingAverageCrossover.IsChangeInTrend = false; + movingAverageCrossovers.Add(movingAverageCrossover); + } + } + else // The fast and the slow are equal. There's nothing to do here + { + + } + prevSlowDMAPrice = slowDMAPrice; + prevFastDMAPrice = fastDMAPrice; + } + } + } + } +} diff --git a/MarketDataLib/Generator/MovingAverage/MovingAverageCrossover.cs b/MarketDataLib/Generator/MovingAverage/MovingAverageCrossover.cs new file mode 100644 index 0000000..802bb78 --- /dev/null +++ b/MarketDataLib/Generator/MovingAverage/MovingAverageCrossover.cs @@ -0,0 +1,47 @@ +using MarketData.MarketDataModel; +using MarketData.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MarketData.Generator.MovingAverage +{ + public class MovingAverageCrossovers : List + { + public MovingAverageCrossovers() + { + } + + public MovingAverageCrossovers(List movingAverageCrossovers) + { + foreach(MovingAverageCrossover movingAverageCrossover in movingAverageCrossovers) + { + Add(movingAverageCrossover); + } + } + } + + public class MovingAverageCrossover + { + public enum CrossoverDirectionEnum{UpCross, DownCross, UpStart, DownStart, NeutralStart}; + public String Symbol { get; set; } + public DateTime EventDate { get; set; } + public CrossoverDirectionEnum CrossOverDirection {get; set;} + public bool IsChangeInTrend { get; set; } + public double ChangePercent { get; set; } + public DMAPrice SlowDMAPrice { get; set; } // the slow DMA price associated with this crossover + public DMAPrice FastDMAPrice { get; set; } // the fast DMA price associated with this crossover + public double CurrentPrice {get{return SlowDMAPrice.CurrentPrice;}} // it doesn't matter if sourced from slow or fast since they both refer to the same underlying price + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Symbol:").Append(Symbol); + sb.Append(" Date:").Append(EventDate.ToShortDateString()); + sb.Append(" Direction:").Append(CrossOverDirection.ToString()); + sb.Append(" IsChangeInTrend:").Append(IsChangeInTrend.ToString()); + sb.Append(" ChangePercent:").Append(Utility.FormatNumber(ChangePercent,2)); + sb.Append(" ClosePrice:").Append(Utility.FormatCurrency(CurrentPrice)); + return sb.ToString(); + } + } +} diff --git a/MarketDataLib/Generator/MovingAverageGenerator.cs b/MarketDataLib/Generator/MovingAverage/MovingAverageGenerator.cs similarity index 99% rename from MarketDataLib/Generator/MovingAverageGenerator.cs rename to MarketDataLib/Generator/MovingAverage/MovingAverageGenerator.cs index d0f6a7f..cc2a13f 100644 --- a/MarketDataLib/Generator/MovingAverageGenerator.cs +++ b/MarketDataLib/Generator/MovingAverage/MovingAverageGenerator.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using MarketData.MarketDataModel; using MarketData.DataAccess; using MarketData.Numerical; using MarketData.Utils; -namespace MarketData.Generator +namespace MarketData.Generator.MovingAverage { public class MovingAverageGenerator { diff --git a/MarketDataLib/Generator/StochasticsGenerator.cs b/MarketDataLib/Generator/StochasticsGenerator.cs index 48d813e..9e16071 100644 --- a/MarketDataLib/Generator/StochasticsGenerator.cs +++ b/MarketDataLib/Generator/StochasticsGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; // Filename: StochasticsGenerator.cs diff --git a/MarketDataLib/Helper/MovingAverageHelperSheet.cs b/MarketDataLib/Helper/MovingAverageHelperSheet.cs index fa476a6..16ceb45 100644 --- a/MarketDataLib/Helper/MovingAverageHelperSheet.cs +++ b/MarketDataLib/Helper/MovingAverageHelperSheet.cs @@ -6,6 +6,7 @@ using MarketData.DataAccess; using MarketData.MarketDataModel; using MarketData.Generator; using MarketData.Utils; +using MarketData.Generator.MovingAverage; namespace MarketData.Helper { diff --git a/MarketDataLib/MarketDataLib.csproj b/MarketDataLib/MarketDataLib.csproj index c779179..747cd1e 100644 --- a/MarketDataLib/MarketDataLib.csproj +++ b/MarketDataLib/MarketDataLib.csproj @@ -162,6 +162,9 @@ + + + @@ -300,7 +303,6 @@ - diff --git a/MarketDataLib/MarketDataModel/YieldCurve.cs b/MarketDataLib/MarketDataModel/YieldCurve.cs index d7edfa3..493bb05 100644 --- a/MarketDataLib/MarketDataModel/YieldCurve.cs +++ b/MarketDataLib/MarketDataModel/YieldCurve.cs @@ -4,6 +4,7 @@ using System.Text; using System.Linq; using MarketData.Generator; using MarketData.Utils; +using MarketData.Generator.MovingAverage; namespace MarketData.MarketDataModel { diff --git a/Program.cs b/Program.cs index 31ea698..5fbd5a9 100644 --- a/Program.cs +++ b/Program.cs @@ -23,6 +23,8 @@ using MarketData.Generator.CMTrend; using Axiom.Interpreter; using System.Data; using MarketData.CNNProcessing; +using MySql.Data.MySqlClient; +using MarketData.Generator.MovingAverage; namespace MarketData { @@ -769,13 +771,19 @@ namespace MarketData Trace.Listeners.Add(new TextWriterTraceListener(strLogFile)); DateTime currentDate=DateTime.Now; -// Price price = MarketDataHelper.GetLatestPriceYahoo("^GSPC"); + 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); -// GetObservations(90,30); -// CompressObservations("Observations.txt"); -// Console.WriteLine(CNNClient.Model.inception.ToString()); -// CNNClient.Model model = (CNNClient.Model)Enum.Parse(typeof(CNNClient.Model),"inception",true); DateTime maxHolidayDate =HolidayDA.GetMaxHolidayDate(); if(currentDate>maxHolidayDate)