Improve the BetaGenerator

This commit is contained in:
2025-02-14 18:58:11 -05:00
parent 310cd83f07
commit 9516c4027b

View File

@@ -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<BetaPrice>
@@ -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<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
Dictionary<DateTime, Price> benchmarkPricesByDate = new Dictionary<DateTime, Price>();
List<DateTime> historicalDates = new List<DateTime>();
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;
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="symbol"></param>
/// <param name="benchmark"></param>
/// <param name="requestedDate"></param>
/// <param name="symbolPricesByDate"></param>
/// <param name="benchmarkPricesByDate"></param>
/// <returns></returns>
private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate, Dictionary<DateTime, Price> 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<DateTime,Price> pricesByDate)
{
DateGenerator dateGenerator = new DateGenerator();
List<DateTime> 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
/// <summary>
/// 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
/// </summary>
/// <param name="symbol"></param>
/// <param name="benchmark"></param>
/// <param name="requestedDate"></param>
/// <param name="symbolPricesByDate"></param>
/// <param name="benchmarkPricesByDate"></param>
/// <returns></returns>
//private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate, Dictionary<DateTime, Price> 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<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
// Dictionary<DateTime, Price> benchmarkPricesByDate = new Dictionary<DateTime, Price>();
// List<DateTime> historicalDates = new List<DateTime>();
// 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;
// }
//}
}
}