Update GBPriceCache.

This commit is contained in:
2026-03-11 20:58:51 -04:00
parent aa13085fd3
commit 10e1c87ec1

View File

@@ -5,7 +5,6 @@ using System.Threading;
using MarketData.MarketDataModel; using MarketData.MarketDataModel;
using MarketData.Utils; using MarketData.Utils;
using MarketData.Helper; using MarketData.Helper;
using MarketData.Numerical;
using MarketData.DataAccess; using MarketData.DataAccess;
namespace MarketData.Cache namespace MarketData.Cache
@@ -43,6 +42,7 @@ namespace MarketData.Cache
public class GBPriceCache : IDisposable public class GBPriceCache : IDisposable
{ {
private static readonly int EVICTION_DAYCOUNT=252; // upon eviction trigger remove all data older than maxdate - evictionPolicyThreshholdDays
private Thread cacheMonitorThread = null; private Thread cacheMonitorThread = null;
private volatile bool threadRun = true; private volatile bool threadRun = true;
private Object thisLock = new Object(); private Object thisLock = new Object();
@@ -61,6 +61,11 @@ namespace MarketData.Cache
cacheMonitorThread.Start(); cacheMonitorThread.Start();
} }
/// <summary>
/// GetInstance
/// </summary>
/// <returns></returns>
/// <exception cref="ObjectDisposedException"></exception>
public static GBPriceCache GetInstance() public static GBPriceCache GetInstance()
{ {
lock (typeof(GBPriceCache)) lock (typeof(GBPriceCache))
@@ -81,6 +86,11 @@ namespace MarketData.Cache
} }
} }
/// <summary>
/// Shuts down the cache and stops the monitor thread. This is a full application-level
/// shutdown. Callers should always access the cache via GetInstance() and not hold
/// long-lived references to the instance.
/// </summary>
public void Dispose() public void Dispose()
{ {
lock (thisLock) lock (thisLock)
@@ -97,40 +107,15 @@ namespace MarketData.Cache
} }
} }
public void ClearCacheOnOrBefore(DateTime onOrBeforeDate, bool collect = false)
{
lock (thisLock)
{
Dictionary<String, PricesByDate> newPriceCache = new Dictionary<String, PricesByDate>();
foreach (KeyValuePair<String, PricesByDate> entry in snapshot.PriceCache)
{
String symbol = entry.Key;
PricesByDate filteredPrices = new PricesByDate();
PricesByDate existingPrices = entry.Value;
foreach (KeyValuePair<DateTime, Price> kvp in existingPrices)
{
if (kvp.Key >= onOrBeforeDate)
{
filteredPrices.Add(kvp.Key, kvp.Value);
}
}
if (filteredPrices.Count > 0)
{
newPriceCache.Add(symbol, filteredPrices);
}
}
UpdateSnapshot(newPriceCache, snapshot.RealTimePriceCache, snapshot.NullCache);
if (collect) GC.Collect();
}
}
public Price GetPriceOrLatestAvailable(String symbol, DateTime date) public Price GetPriceOrLatestAvailable(String symbol, DateTime date)
{ {
Price price = GetPrice(symbol, date); Price price = GetPrice(symbol, date);
if (null != price) return price; if (null != price) return price;
DateTime latestPricingDate = PricingDataAccess.GetLatestDateOnOrBefore(symbol, date); DateTime latestPricingDate = PricingDataAccess.GetLatestDateOnOrBefore(symbol, date);
price = GetPrice(symbol, latestPricingDate); price = GetPrice(symbol, latestPricingDate);
if (null != price) return price; if (null != price) return price;
fetchSemaphore.Wait(); fetchSemaphore.Wait();
try try
{ {
@@ -140,7 +125,8 @@ namespace MarketData.Cache
{ {
fetchSemaphore.Release(); fetchSemaphore.Release();
} }
if (null !=price) AddPrice(price);
if (null != price) AddPrice(price);
return price; return price;
} }
@@ -152,27 +138,33 @@ namespace MarketData.Cache
} }
Price price = MarketDataHelper.GetLatestPrice(symbol); Price price = MarketDataHelper.GetLatestPrice(symbol);
if (null != price) if (null != price)
{ {
Dictionary<String, Price> newRealtime = new Dictionary<String, Price>(snapshot.RealTimePriceCache); Dictionary<String, Price> newRealtime = new Dictionary<String, Price>(snapshot.RealTimePriceCache);
newRealtime.Add(symbol, price); newRealtime.Add(symbol, price);
UpdateSnapshot(snapshot.PriceCache, newRealtime, snapshot.NullCache); UpdateSnapshot(snapshot.PriceCache, newRealtime, snapshot.NullCache);
} }
return price; return price;
} }
public Price GetPrice(String symbol, DateTime date) public Price GetPrice(String symbol, DateTime date)
{ {
date = date.Date; date = date.Date;
if (!ContainsPrice(symbol, date)) if (!ContainsPrice(symbol, date))
{ {
String key = symbol + Utility.DateTimeToStringMMHDDHYYYY(date); String key = symbol + Utility.DateTimeToStringMMHDDHYYYY(date);
if (snapshot.NullCache.ContainsKey(key)) if (snapshot.NullCache.ContainsKey(key))
{ {
return null; return null;
} }
fetchSemaphore.Wait(); fetchSemaphore.Wait();
Price price; Price price;
try try
{ {
price = PricingDataAccess.GetPrice(symbol, date); price = PricingDataAccess.GetPrice(symbol, date);
@@ -181,7 +173,8 @@ namespace MarketData.Cache
{ {
fetchSemaphore.Release(); fetchSemaphore.Release();
} }
if (null ==price)
if (null == price)
{ {
Dictionary<String, bool> newNullCache = new Dictionary<String, bool>(snapshot.NullCache); Dictionary<String, bool> newNullCache = new Dictionary<String, bool>(snapshot.NullCache);
newNullCache.Add(key, true); newNullCache.Add(key, true);
@@ -191,23 +184,29 @@ namespace MarketData.Cache
AddPrice(price); AddPrice(price);
} }
if (!snapshot.PriceCache.ContainsKey(symbol)) return null; if (!snapshot.PriceCache.ContainsKey(symbol)) return null;
PricesByDate pricesByDate = snapshot.PriceCache[symbol]; PricesByDate pricesByDate = snapshot.PriceCache[symbol];
if (!pricesByDate.ContainsKey(date)) return null; if (!pricesByDate.ContainsKey(date)) return null;
return pricesByDate[date]; return pricesByDate[date];
} }
public Prices GetPrices(String symbol, DateTime earlierDate, DateTime laterDate) public Prices GetPrices(String symbol, DateTime earlierDate, DateTime laterDate)
{ {
DateGenerator localDateGenerator = new DateGenerator(); DateGenerator localDateGenerator = new DateGenerator();
if (laterDate < earlierDate) if (laterDate < earlierDate)
{ {
DateTime tempDate = earlierDate; DateTime tempDate = earlierDate;
earlierDate = laterDate; earlierDate = laterDate;
laterDate = tempDate; laterDate = tempDate;
} }
List<DateTime> datesList = localDateGenerator.GenerateHistoricalDates(earlierDate, laterDate); List<DateTime> datesList = localDateGenerator.GenerateHistoricalDates(earlierDate, laterDate);
datesList = datesList.Where(x => x >= earlierDate).ToList(); datesList = datesList.Where(x => x >= earlierDate).ToList();
return GetPrices(symbol, laterDate, datesList.Count); return GetPrices(symbol, laterDate, datesList.Count);
} }
@@ -215,17 +214,20 @@ namespace MarketData.Cache
{ {
List<DateTime> historicalDates = dateGenerator.GenerateHistoricalDates(startDate, dayCount + 60); List<DateTime> historicalDates = dateGenerator.GenerateHistoricalDates(startDate, dayCount + 60);
List<DateTime> missingDates = new List<DateTime>(); List<DateTime> missingDates = new List<DateTime>();
foreach (DateTime historicalDate in historicalDates) foreach (DateTime historicalDate in historicalDates)
{ {
if (!ContainsPrice(symbol, historicalDate)) if (!ContainsPrice(symbol, historicalDate))
{ {
String key = symbol + Utility.DateTimeToStringMMHDDHYYYY(historicalDate); String key = symbol + Utility.DateTimeToStringMMHDDHYYYY(historicalDate);
if (!snapshot.NullCache.ContainsKey(key)) if (!snapshot.NullCache.ContainsKey(key))
{ {
missingDates.Add(historicalDate); missingDates.Add(historicalDate);
} }
} }
} }
if (missingDates.Count > 0) if (missingDates.Count > 0)
{ {
DateTime minDate = missingDates.Min(); DateTime minDate = missingDates.Min();
@@ -233,6 +235,7 @@ namespace MarketData.Cache
fetchSemaphore.Wait(); fetchSemaphore.Wait();
Prices loadedPrices; Prices loadedPrices;
try try
{ {
loadedPrices = PricingDataAccess.GetPrices(symbol, maxDate, minDate); loadedPrices = PricingDataAccess.GetPrices(symbol, maxDate, minDate);
@@ -249,11 +252,14 @@ namespace MarketData.Cache
} }
Prices prices = new Prices(); Prices prices = new Prices();
foreach (DateTime historicalDate in historicalDates) foreach (DateTime historicalDate in historicalDates)
{ {
if (!snapshot.PriceCache.ContainsKey(symbol)) continue; if (!snapshot.PriceCache.ContainsKey(symbol)) continue;
PricesByDate pricesByDate = snapshot.PriceCache[symbol]; PricesByDate pricesByDate = snapshot.PriceCache[symbol];
if (!pricesByDate.ContainsKey(historicalDate)) continue; if (!pricesByDate.ContainsKey(historicalDate)) continue;
prices.Add(pricesByDate[historicalDate]); prices.Add(pricesByDate[historicalDate]);
} }
@@ -268,17 +274,21 @@ namespace MarketData.Cache
lock (thisLock) lock (thisLock)
{ {
PricesByDate pricesByDate; PricesByDate pricesByDate;
if (!snapshot.PriceCache.ContainsKey(price.Symbol)) if (!snapshot.PriceCache.ContainsKey(price.Symbol))
{ {
pricesByDate = new PricesByDate(); pricesByDate = new PricesByDate();
pricesByDate.Add(price.Date, price); pricesByDate.Add(price.Date, price);
Dictionary<String, PricesByDate> newCache = new Dictionary<String, PricesByDate>(snapshot.PriceCache); Dictionary<String, PricesByDate> newCache = new Dictionary<String, PricesByDate>(snapshot.PriceCache);
newCache.Add(price.Symbol, pricesByDate); newCache.Add(price.Symbol, pricesByDate);
UpdateSnapshot(newCache, snapshot.RealTimePriceCache, snapshot.NullCache); UpdateSnapshot(newCache, snapshot.RealTimePriceCache, snapshot.NullCache);
} }
else else
{ {
pricesByDate = snapshot.PriceCache[price.Symbol]; pricesByDate = snapshot.PriceCache[price.Symbol];
if (!pricesByDate.ContainsKey(price.Date)) if (!pricesByDate.ContainsKey(price.Date))
{ {
pricesByDate.Add(price.Date, price); pricesByDate.Add(price.Date, price);
@@ -290,10 +300,47 @@ namespace MarketData.Cache
public bool ContainsPrice(String symbol, DateTime date) public bool ContainsPrice(String symbol, DateTime date)
{ {
if (!snapshot.PriceCache.ContainsKey(symbol)) return false; if (!snapshot.PriceCache.ContainsKey(symbol)) return false;
PricesByDate pricesByDate = snapshot.PriceCache[symbol]; PricesByDate pricesByDate = snapshot.PriceCache[symbol];
return pricesByDate.ContainsKey(date); return pricesByDate.ContainsKey(date);
} }
public void ClearCacheOnOrBefore(DateTime onOrBeforeDate, bool collect = false)
{
lock (thisLock)
{
UpdateSnapshot(BuildEvictedPriceCache(onOrBeforeDate), snapshot.RealTimePriceCache, new Dictionary<String, bool>());
if (collect) GC.Collect();
}
}
private Dictionary<String, PricesByDate> BuildEvictedPriceCache(DateTime onOrBeforeDate)
{
Dictionary<String, PricesByDate> newPriceCache = new Dictionary<String, PricesByDate>();
foreach (KeyValuePair<String, PricesByDate> entry in snapshot.PriceCache)
{
String symbol = entry.Key;
PricesByDate filteredPrices = new PricesByDate();
PricesByDate existingPrices = entry.Value;
foreach (KeyValuePair<DateTime, Price> kvp in existingPrices)
{
if (kvp.Key >= onOrBeforeDate)
{
filteredPrices.Add(kvp.Key, kvp.Value);
}
}
if (filteredPrices.Count > 0)
{
newPriceCache.Add(symbol, filteredPrices);
}
}
return newPriceCache;
}
private void ThreadProc() private void ThreadProc()
{ {
int quantums = 0; int quantums = 0;
@@ -302,22 +349,37 @@ namespace MarketData.Cache
while (threadRun) while (threadRun)
{ {
Thread.Sleep(quantumInterval); Thread.Sleep(quantumInterval);
if(!threadRun)break; if (!threadRun) break;
quantums += quantumInterval; quantums += quantumInterval;
if (quantums > cacheRefreshAfter) if (quantums > cacheRefreshAfter)
{ {
quantums = 0; quantums = 0;
lock (thisLock) lock (thisLock)
{ {
UpdateSnapshot(snapshot.PriceCache, new Dictionary<String, Price>(), snapshot.NullCache); DateTime maxDate = snapshot.PriceCache.Values.SelectMany(p => p.Keys).DefaultIfEmpty(DateTime.MinValue).Max();
if (maxDate != DateTime.MinValue)
{
DateTime evictBefore = dateGenerator.GenerateHistoricalDates(maxDate, EVICTION_DAYCOUNT).Min();
int beforeCount = snapshot.PriceCache.Values.Sum(p => p.Count);
Dictionary<String, PricesByDate> newCache = BuildEvictedPriceCache(evictBefore);
int afterCount = newCache.Values.Sum(p => p.Count);
int removed = beforeCount - afterCount;
MDTrace.WriteLine(LogLevel.DEBUG, $"GBPriceCache eviction: removed {removed} prices (before={beforeCount}, after={afterCount}) on or before {evictBefore.ToShortDateString()}");
UpdateSnapshot(newCache, new Dictionary<String, Price>(), new Dictionary<String, bool>());
GC.Collect();
}
} }
} }
} }
} }
private void UpdateSnapshot(Dictionary<String, PricesByDate> newPriceCache,Dictionary<String, Price> newRealtimePriceCache, Dictionary<String, bool> newNullCache) private void UpdateSnapshot(Dictionary<String, PricesByDate> newPriceCache, Dictionary<String, Price> newRealtimePriceCache, Dictionary<String, bool> newNullCache)
{ {
snapshot = new CacheSnapshot(newPriceCache, newRealtimePriceCache, newNullCache); snapshot = new CacheSnapshot(newPriceCache, newRealtimePriceCache, newNullCache);
} }
}
} }
}