diff --git a/MarketDataLib/Numerics/BetaGenerator.cs b/MarketDataLib/Numerics/BetaGenerator.cs index b382021..ce01c35 100644 --- a/MarketDataLib/Numerics/BetaGenerator.cs +++ b/MarketDataLib/Numerics/BetaGenerator.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using MarketData.Cache; using MarketData.DataAccess; using MarketData.MarketDataModel; using MarketData.Utils; -// This Beta calculator is modelled after Yahoo Finance Beta calculator. It calculates Beta using 36 monthly prices start at beginning of previous month and going back 36 months -// While the calculator does not match exactly to Yahoo Finance I will use this as an alternative in the event that Yahoo Finance Beta is no longer available. +// This Beta calculator is modelled after Yahoo Finance Beta calculator. It calculates Beta using 36 (default) monthly prices starting at beginning of previous month +// and going back 36 months. While the calculator does not match exactly to Yahoo Finance I will use this as an alternative in the event that Yahoo Finance Beta is +// no longer available. namespace MarketData.Numerical { public class BetaPrices : List @@ -16,7 +15,8 @@ namespace MarketData.Numerical public BetaPrices() { } -// assuming that the list is in descending date order + +// Assumes that the list is in descending date order with the Latest (Most Recent) date in the lowest index and Earliest (Most Historical) date in the highest index public double[] ReturnsBenchmark() { double[] returns=new double[Count-1]; @@ -27,6 +27,8 @@ namespace MarketData.Numerical } return returns; } + +// Assumes that the list is in descending date order with the Latest (Most Recent) date in the lowest index and Earliest (Most Historical) date in the highest index public double[] ReturnsSymbol() { double[] returns=new double[Count-1]; @@ -38,6 +40,7 @@ namespace MarketData.Numerical return returns; } } + public class BetaPrice { public BetaPrice(String symbol,String benchmark,Price symbolPrice,Price benchmarkPrice,DateTime pricingDate) @@ -60,11 +63,13 @@ namespace MarketData.Numerical private BetaGenerator() { } - public static double Beta(String symbol,int months = 36) + public static double Beta(String symbol,int months = 36, bool verbose=true) { - return Beta(symbol, PricingDA.GetLatestDate(symbol), months); + return Beta(symbol, PricingDA.GetLatestDate(symbol), months, verbose); } - public static double Beta(String symbol,DateTime asof,int months=36) + + + public static double Beta(String symbol,DateTime asof,int months=36,bool verbose=true) { try { @@ -77,28 +82,38 @@ namespace MarketData.Numerical Dictionary symbolPricesByDate = new Dictionary(); Dictionary benchmarkPricesByDate = new Dictionary(); List historicalDates = new List(); + while (historicalDates.Count < (months + 1)) { historicalDates.Add(startDate); startDate = dateGenerator.GetPrevMonthStart(startDate); } - Prices symbolPrices = PricingDA.GetPrices(symbol, asof, historicalDates[historicalDates.Count - 1]); - Prices benchmarkPrices = PricingDA.GetPrices(benchmark, asof, historicalDates[historicalDates.Count - 1]); + DateTime earliestDate = historicalDates[historicalDates.Count - 1]; + + Prices symbolPrices = GBPriceCache.GetInstance().GetPrices(symbol, asof, earliestDate); + Prices benchmarkPrices = GBPriceCache.GetInstance().GetPrices(benchmark, asof, earliestDate); + foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price); foreach (Price price in benchmarkPrices) benchmarkPricesByDate.Add(price.Date, price); + startDate = dateGenerator.GetPrevMonthStart(asof); + while (betaPrices.Count < (months + 1)) { BetaPrice betaPrice = GetPrice(symbol, benchmark, startDate, symbolPricesByDate, benchmarkPricesByDate); if (null == betaPrice) { - MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Beta({0},{1}).. Cannot calculate Beta. Missing price on {2}",symbol,Utility.DateTimeToStringMMHDDHYYYY(asof),Utility.DateTimeToStringMMHDDHYYYY(startDate))); + if(verbose) + { + MDTrace.WriteLine(LogLevel.DEBUG,String.Format("INFO:Beta({0},{1}).. Cannot calculate Beta. Missing price on {2}",symbol,Utility.DateTimeToStringMMHDDHYYYY(asof),Utility.DateTimeToStringMMHDDHYYYY(startDate))); + } return double.NaN; } betaPrices.Add(betaPrice); startDate = dateGenerator.GetPrevMonthStart(startDate); if (startDate < minPricingDate) break; } + double[] returnsSymbol = betaPrices.ReturnsSymbol(); double[] returnsBenchmark = betaPrices.ReturnsBenchmark(); double beta = Numerics.Beta(ref returnsSymbol, ref returnsBenchmark); @@ -110,23 +125,33 @@ namespace MarketData.Numerical return double.NaN; } } + + + /// + /// Fetch the first price of the month for both the asset and the benchmark, allowing both to walk forward in the month by up to 10 days until we get a good price + /// + /// + /// + /// + /// + /// + /// private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary symbolPricesByDate, Dictionary benchmarkPricesByDate) { try { int maxAdvanceDays = 10; - Price symbolPrice = null; - Price benchmarkPrice = null; - for (int advanceDays = 0; advanceDays < maxAdvanceDays; advanceDays++) - { - if (!symbolPricesByDate.ContainsKey(requestedDate)) { requestedDate = requestedDate.AddDays(1); continue; } - symbolPrice = symbolPricesByDate[requestedDate]; - if (!benchmarkPricesByDate.ContainsKey(requestedDate)) { requestedDate = requestedDate.AddDays(1); continue; } - benchmarkPrice = benchmarkPricesByDate[requestedDate]; - } + Price symbolPrice = default; + Price benchmarkPrice = default; + + symbolPrice = GetPriceWithinDays(requestedDate, maxAdvanceDays, symbolPricesByDate); + benchmarkPrice = GetPriceWithinDays(requestedDate, maxAdvanceDays, benchmarkPricesByDate); + if (null == symbolPrice || null == benchmarkPrice) return null; + symbolPrice.Date = requestedDate.Date; benchmarkPrice.Date = requestedDate.Date; + return new BetaPrice(symbol, benchmark, symbolPrice, benchmarkPrice, requestedDate); } catch (Exception exception) @@ -135,6 +160,130 @@ namespace MarketData.Numerical return null; } } + + private static Price GetPriceWithinDays(DateTime requestedDate, int maxDays, Dictionary pricesByDate) + { + DateGenerator dateGenerator = new DateGenerator(); + + List futureDates = dateGenerator.GenerateFutureDates(requestedDate, maxDays); + foreach(DateTime futureDate in futureDates) + { + if(pricesByDate.ContainsKey(futureDate)) + { + return pricesByDate[futureDate]; + } + } + return default; + } + +// assuming that the list is in descending date order + + + + /// + /// Fetch the first price of the month for both the asset and the benchmark, allowing both to walk forward in the month by up to 10 days until we get a good price + /// + /// + /// + /// + /// + /// + /// + //private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary symbolPricesByDate, Dictionary benchmarkPricesByDate) + //{ + // try + // { + // int maxAdvanceDays = 10; + // Price symbolPrice = default; + // Price benchmarkPrice = default; + + // for (int advanceDays = 0; advanceDays < maxAdvanceDays; advanceDays++) + // { + // if (!symbolPricesByDate.ContainsKey(requestedDate)) + // { + // requestedDate = requestedDate.AddDays(1); + // continue; + // } + // symbolPrice = symbolPricesByDate[requestedDate]; + + // if (!benchmarkPricesByDate.ContainsKey(requestedDate)) + // { + // requestedDate = requestedDate.AddDays(1); + // continue; + // } + // benchmarkPrice = benchmarkPricesByDate[requestedDate]; + // if(null != symbolPrice && null != benchmarkPrice)break; + // } + + // if (null == symbolPrice || null == benchmarkPrice) return null; + + // symbolPrice.Date = requestedDate.Date; + // benchmarkPrice.Date = requestedDate.Date; + // return new BetaPrice(symbol, benchmark, symbolPrice, benchmarkPrice, requestedDate); + // } + // catch (Exception exception) + // { + // MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString())); + // return null; + // } + //} + + + //public static double Beta(String symbol,DateTime asof,int months=36) + //{ + // try + // { + // String benchmark = "SPY"; + // DateGenerator dateGenerator = new DateGenerator(); + + // BetaPrices betaPrices = new BetaPrices(); + // DateTime startDate = dateGenerator.GetPrevMonthStart(asof); + // DateTime minPricingDate = PricingDA.GetEarliestDate(symbol); + // Dictionary symbolPricesByDate = new Dictionary(); + // Dictionary benchmarkPricesByDate = new Dictionary(); + // List historicalDates = new List(); + + // while (historicalDates.Count < (months + 1)) + // { + // historicalDates.Add(startDate); + // startDate = dateGenerator.GetPrevMonthStart(startDate); + // } + // DateTime earliestDate = historicalDates[historicalDates.Count - 1]; + + // Prices symbolPrices = PricingDA.GetPrices(symbol, asof, earliestDate); + // Prices benchmarkPrices = PricingDA.GetPrices(benchmark, asof, earliestDate); + + // foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price); + // foreach (Price price in benchmarkPrices) benchmarkPricesByDate.Add(price.Date, price); + + // startDate = dateGenerator.GetPrevMonthStart(asof); + + // while (betaPrices.Count < (months + 1)) + // { + // BetaPrice betaPrice = GetPrice(symbol, benchmark, startDate, symbolPricesByDate, benchmarkPricesByDate); + // if (null == betaPrice) + // { + // MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Beta({0},{1}).. Cannot calculate Beta. Missing price on {2}",symbol,Utility.DateTimeToStringMMHDDHYYYY(asof),Utility.DateTimeToStringMMHDDHYYYY(startDate))); + // return double.NaN; + // } + // betaPrices.Add(betaPrice); + // startDate = dateGenerator.GetPrevMonthStart(startDate); + // if (startDate < minPricingDate) break; + // } + + // double[] returnsSymbol = betaPrices.ReturnsSymbol(); + // double[] returnsBenchmark = betaPrices.ReturnsBenchmark(); + // double beta = Numerics.Beta(ref returnsSymbol, ref returnsBenchmark); + // return beta; + // } + // catch (Exception exception) + // { + // MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString())); + // return double.NaN; + // } + //} + + } }