Initial Commit

This commit is contained in:
2025-03-25 21:42:32 -04:00
parent c266eecfeb
commit 30c33d3cfd
247 changed files with 60107 additions and 0 deletions

View File

@@ -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);
}
/// <summary>
/// GetMovingAverageCrossovers
/// Prices will be in descending order with the most recent price at index[0] and the least recent price at index[count-1]
/// </summary>
/// <param name="prices"></param>
/// <param name="fastCrossOverDays">The fast crossover. Tom Basso uses 9</param>
/// <param name="slowCrossoverDays">The slow crosser. Tom Basso uses 41</param>
/// <param name="threshholdPercent">The threshhold percent. 5% would be .05</param>
/// <returns></returns>
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<MovingAverageCrossover>).Where(x => x.IsChangeInTrend==true).OrderByDescending(x => x.EventDate).ToList());
return movingAverageCrossovers;
}
/// <summary>
/// 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
/// </summary>
/// <param name="fastDMAPrices"></param>
/// <param name="slowDMAPrices"></param>
/// <param name="movingAverageCrossovers"></param>
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<DateTime> availableDates = new List<DateTime>(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<availableDates.Count;index++)
{
DateTime analysisDate = availableDates[index];
DMAPrice slowDMAPrice = slowDMAPricesByDate[analysisDate];
DMAPrice fastDMAPrice = fastDMAPricesByDate[analysisDate];
if(index == 0) // establish an initial orientation of the fast and slow moving averages. We need to create a new record to have a current trend
{
if(fastDMAPrice.AVGPrice > 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;
}
}
}
}
}

View File

@@ -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<MovingAverageCrossover>
{
public MovingAverageCrossovers()
{
}
public MovingAverageCrossovers(List<MovingAverageCrossover> 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();
}
}
}

View File

@@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Numerical;
using MarketData.Utils;
namespace MarketData.Generator.MovingAverage
{
public class MovingAverageGenerator
{
public static readonly int DayCount200=200;
public static readonly int DayCount100=100;
public static readonly int DayCount55=55;
public static readonly int DayCount50=50;
public static readonly int DayCount21=21;
public static readonly int DayCount5=5;
private MovingAverageGenerator()
{
}
public static MovingAverages GenerateMovingAverages(String symbol,int dayCount=180)
{
try
{
Dictionary<DateTime, DMAPrice> ma200ByDate = new Dictionary<DateTime, DMAPrice>();
Dictionary<DateTime, DMAPrice> ma100ByDate = new Dictionary<DateTime, DMAPrice>();
Dictionary<DateTime, DMAPrice> ma55ByDate = new Dictionary<DateTime, DMAPrice>();
Dictionary<DateTime, DMAPrice> ma21ByDate = new Dictionary<DateTime, DMAPrice>();
Dictionary<DateTime, DMAPrice> ma5ByDate = new Dictionary<DateTime, DMAPrice>();
if (null == symbol) return null;
String companyName = PricingDA.GetNameForSymbol(symbol);
DateGenerator dateGenerator = new DateGenerator();
DateTime startDate = dateGenerator.GetPrevBusinessDay(DateTime.Now);
Prices prices = PricingDA.GetPrices(symbol, startDate, dayCount);
if (null == prices || 0 == prices.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG,"No prices for symbol '" + symbol + "'");
return null;
}
Price latestPrice = prices[0];
DMAPrices ma200= GenerateMovingAverage(prices, DayCount200);
DMAPrices ma100 = GenerateMovingAverage(prices, DayCount100);
DMAPrices ma55 = GenerateMovingAverage(prices, DayCount55);
DMAPrices ma21 = GenerateMovingAverage(prices, DayCount21);
DMAPrices ma5 = GenerateMovingAverage(prices, DayCount5);
for (int index = 0; index < ma200.Count; index++) ma200ByDate.Add(ma200[index].Date,ma200[index]);
for (int index = 0; index < ma100.Count; index++) ma100ByDate.Add(ma100[index].Date, ma100[index]);
for (int index = 0; index < ma55.Count; index++) ma55ByDate.Add(ma55[index].Date, ma55[index]);
for (int index = 0; index < ma21.Count; index++) ma21ByDate.Add(ma21[index].Date, ma21[index]);
for (int index = 0; index < ma5.Count; index++) ma5ByDate.Add(ma5[index].Date, ma5[index]);
MovingAverages movingAverages = new MovingAverages();
movingAverages.ThruDate = prices[0].Date;
movingAverages.FromDate = prices[prices.Count - 1].Date;
for (int index = 0; index < prices.Count; index++)
{
Price price = prices[index];
DMAPrice ma200Price = null;
DMAPrice ma100Price = null;
DMAPrice ma55Price = null;
DMAPrice ma21Price = null;
DMAPrice ma5Price = null;
if (ma55ByDate.ContainsKey(price.Date)) ma55Price=ma55ByDate[price.Date];
if (ma21ByDate.ContainsKey(price.Date)) ma21Price = ma21ByDate[price.Date];
if (ma5ByDate.ContainsKey(price.Date)) ma5Price = ma5ByDate[price.Date];
if(ma200ByDate.ContainsKey(price.Date))ma200Price = ma200ByDate[price.Date];
if(ma100ByDate.ContainsKey(price.Date)) ma100Price = ma100ByDate[price.Date];
MovingAverageElement movingAverageElement = new MovingAverageElement();
movingAverageElement.Symbol = price.Symbol;
movingAverageElement.Date = price.Date;
movingAverageElement.Close = price.Close;
movingAverageElement.High = price.High;
movingAverageElement.Low = price.Low;
movingAverageElement.MA200 = null==ma200Price?double.NaN:ma200Price.AVGPrice;
movingAverageElement.MA100 = null == ma100Price ? double.NaN : ma100Price.AVGPrice;
movingAverageElement.MA55 = null==ma55Price?double.NaN:ma55Price.AVGPrice;
movingAverageElement.MA21 = null==ma21Price?double.NaN:ma21Price.AVGPrice;
movingAverageElement.MA5 = null==ma5Price?double.NaN:ma5Price.AVGPrice;
movingAverages.Add(movingAverageElement);
}
return movingAverages;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception);
return null;
}
}
public static DMAValues GenerateMovingAverage(DMAValues values,int dayCount)
{
try
{
DMAValues dmaValues = new DMAValues();
for (int index = 0; index < values.Count; index++)
{
DMAValue value = values[index];
DMAValue dmaValue = new DMAValue();
dmaValue.Date = value.Date;
float[] pricesArray = values.GetValues(index, dayCount);
if (null == pricesArray) break;
dmaValue.MAValue = Numerics.Mean(ref pricesArray);
dmaValue.Value = value.Value;
if (double.IsNaN(dmaValue.MAValue)) continue;
dmaValues.Add(dmaValue);
}
return dmaValues;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception);
return null;
}
}
/// <summary>
/// The EMA coverage shoudld be the same as the simple moving average. If you have 10 elements in the SMA then you should have 10 elements in the EMA
/// The EMA smooths the line by applying a smoothing (Beta) where Beta=1/(dayCount+1). Foe example if you are wanting to calculate the
/// 20 day exponential moving average over a series then Beta=2/(20+1)=.095
/// The formula: EMA=prevEMA.AVGPrice+beta*(smaPrice.CurrentPrice - prevEMA.AVGPrice)
/// Tom Basso uses a 9 day(Fast) and 41 day(Slow) exponential moving average crossover to determine change in trend direction.
/// </summary>
/// <param name="prices"></param>
/// <param name="dayCount"></param>
/// <returns></returns>
public static DMAPrices GenerateExponentialMovingAverage(Prices prices,int dayCount)
{
try
{
if(null==prices||prices.Count<dayCount+1)return null; // (i.e.) a 20 day ExponentialMovingAverage requires 21 days of data
double beta=2.00/((double)dayCount+1.00);
DMAPrices emaPrices=new DMAPrices();
DMAPrices smaPrices=GenerateMovingAverage(prices,dayCount);
if(null==smaPrices||smaPrices.Count<dayCount)return null;
for(int index=0;index<smaPrices.Count;index++)
{
DMAPrice smaPrice=smaPrices[index];
DMAPrice emaPrice=new DMAPrice();
if(0==index)
{
emaPrice.Symbol=smaPrice.Symbol;
emaPrice.Date=smaPrice.Date;
emaPrice.AVGPrice=smaPrice.AVGPrice;
emaPrice.CurrentPrice=smaPrice.CurrentPrice;
emaPrice.MaxPrice=smaPrice.MaxPrice;
emaPrice.MinPrice=smaPrice.MinPrice;
emaPrices.Add(emaPrice);
}
else
{
DMAPrice prevEMA=emaPrices[emaPrices.Count-1];
emaPrice.Symbol=smaPrice.Symbol;
emaPrice.Date=smaPrice.Date;
emaPrice.CurrentPrice=smaPrice.CurrentPrice;
emaPrice.MaxPrice=smaPrice.MaxPrice;
emaPrice.MinPrice=smaPrice.MinPrice;
emaPrice.AVGPrice = prevEMA.AVGPrice+beta*(smaPrice.CurrentPrice - prevEMA.AVGPrice);
emaPrices.Add(emaPrice);
}
}
return emaPrices;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception);
return null;
}
}
/// <summary>Generates a dayCount moving average given prices.</summary>
public static DMAPrices GenerateMovingAverage(Prices prices, int dayCount)
{
try
{
DMAPrices dmaPrices = new DMAPrices();
for (int index = 0; index < prices.Count; index++)
{
Price price = prices[index];
DMAPrice dmaPrice = new DMAPrice();
dmaPrice.Symbol = price.Symbol;
dmaPrice.Date = price.Date;
float[] pricesArray = prices.GetPrices(index, dayCount);
if (null == pricesArray) break;
dmaPrice.AVGPrice = Numerics.Mean(ref pricesArray);
if (double.IsNaN(dmaPrice.AVGPrice)) continue;
dmaPrice.CurrentPrice = price.Close;
dmaPrices.Add(dmaPrice);
}
return dmaPrices;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception);
return null;
}
}
public static DMAPrices GenerateMovingMinsOnLow(Prices prices, int dayCount)
{
try
{
DMAPrices dmaPrices = new DMAPrices();
for (int index = 0; index < prices.Count; index++)
{
Price price = prices[index];
DMAPrice dmaPrice = new DMAPrice();
dmaPrice.Symbol = price.Symbol;
dmaPrice.Date = price.Date;
float[] pricesArray = prices.GetPricesLow(index, dayCount);
if (null == pricesArray) break;
dmaPrice.MinPrice = Numerics.Min(ref pricesArray);
if (double.IsNaN(dmaPrice.MinPrice)) continue;
dmaPrice.CurrentPrice = price.Low;
dmaPrices.Add(dmaPrice);
}
return dmaPrices;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
public static DMAPrices GenerateMovingMaxOnHigh(Prices prices, int dayCount)
{
try
{
DMAPrices dmaPrices = new DMAPrices();
for (int index = 0; index < prices.Count; index++)
{
Price price = prices[index];
DMAPrice dmaPrice = new DMAPrice();
dmaPrice.Symbol = price.Symbol;
dmaPrice.Date = price.Date;
float[] pricesArray = prices.GetPricesHigh(index, dayCount);
if (null == pricesArray) break;
dmaPrice.MaxPrice = Numerics.Max(ref pricesArray);
if (double.IsNaN(dmaPrice.MaxPrice)) continue;
dmaPrice.CurrentPrice = price.High;
dmaPrices.Add(dmaPrice);
}
return dmaPrices;
}
catch (Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
}
}