Files
marketdata/MarketDataLib/Cache/GBPriceCache.cs
2025-02-14 18:56:47 -05:00

260 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
using MarketData.Numerical;
using System.Threading;
// This cache is mainly used by the models. It is a short lived cache that gets cleared out every 2 minutes.
// This cache will attempt to load a price from the database if it is found in the cache.
namespace MarketData.Cache
{
public class GBPriceCache
{
private Thread cacheMonitorThread=null;
private volatile bool threadRun=true;
private Object thisLock=new Object();
private Dictionary<String,PricesByDate> priceCache=new Dictionary<String,PricesByDate>(); // the main cache
private Dictionary<String,Price> realTimePriceCache=new Dictionary<String,Price>(); // short lived cache of realtime prices gets cleared out every cacheRefreshAfter(ms)
private Dictionary<String,bool> nullCache=new Dictionary<String,bool>();
private DateGenerator dateGenerator=new DateGenerator();
private static GBPriceCache priceCacheInstance=null;
private int cacheRefreshAfter=120000; // the cache will be cleaned up after 2 minutes
protected GBPriceCache()
{
cacheMonitorThread=new Thread(new ThreadStart(ThreadProc));
cacheMonitorThread.Start();
}
public static GBPriceCache GetInstance()
{
lock(typeof(GBPriceCache))
{
if(null==priceCacheInstance)
{
priceCacheInstance=new GBPriceCache();
}
return priceCacheInstance;
}
}
public void Clear()
{
lock(thisLock)
{
priceCache=new Dictionary<String,PricesByDate>();
realTimePriceCache=new Dictionary<String,Price>();
nullCache=new Dictionary<String,bool>();
}
}
public void Dispose()
{
lock(thisLock)
{
if(null==priceCacheInstance || false==threadRun)return;
threadRun=false;
if(null!=cacheMonitorThread)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[GBPriceCache:Dispose]Thread state is '{0}'. Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread)));
cacheMonitorThread.Join(5000);
this.cacheMonitorThread=null;
}
MDTrace.WriteLine(LogLevel.DEBUG,"[GBPriceCache:Dispose] End.");
priceCacheInstance=null;
}
}
public void ClearCacheOnOrBefore(DateTime onOrBeforeDate,bool collect=false)
{
lock(thisLock)
{
MDTrace.WriteLine(LogLevel.DEBUG,"Clearing GBPriceCache cache.");
List<String> symbols=new List<String>(priceCache.Keys);
foreach(String symbol in symbols)
{
PricesByDate pricesByDate=priceCache[symbol];
List<DateTime> symbolDates=new List<DateTime>(pricesByDate.Keys);
foreach(DateTime symbolDate in symbolDates)
{
if(symbolDate<onOrBeforeDate) pricesByDate.Remove(symbolDate);
}
}
MDTrace.WriteLine(LogLevel.DEBUG,"Calling garbage collector...");
if(collect) GC.Collect();
}
}
public Price GetPriceOrLatestAvailable(String symbol,DateTime date)
{
lock(thisLock)
{
Price price=GetPrice(symbol,date);
if(null!=price) return price;
DateTime latestPricingDate=PricingDA.GetLatestDateOnOrBefore(symbol,date);
price=GetPrice(symbol,latestPricingDate);
if(null!=price) return price;
price=PricingDA.GetPrice(symbol,latestPricingDate);
if(null!=price) AddPrice(price);
return price;
}
}
public Price GetRealtimePrice(String symbol)
{
if(realTimePriceCache.ContainsKey(symbol)) return realTimePriceCache[symbol];
Price price=MarketDataHelper.GetLatestPrice(symbol);
if(null!=price)
{
realTimePriceCache.Add(symbol,price);
}
return price;
}
public Price GetPrice(String symbol,DateTime date)
{
lock(thisLock)
{
date=date.Date;
if(!ContainsPrice(symbol,date))
{
String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(date);
if(nullCache.ContainsKey(key)) return null;
Price price=PricingDA.GetPrice(symbol,date);
if(null==price)
{
nullCache.Add(key,true);
return price;
}
AddPrice(price);
}
if(!priceCache.ContainsKey(symbol)) return null;
PricesByDate pricesByDate=priceCache[symbol];
if(!pricesByDate.ContainsKey(date.Date)) return null;
return pricesByDate[date];
}
}
public Prices GetPrices(String symbol, DateTime earlierDate, DateTime laterDate)
{
DateGenerator dateGenerator = new DateGenerator();
if(laterDate<earlierDate)
{
DateTime tempDate = earlierDate;
earlierDate = laterDate;
laterDate=tempDate;
}
List<DateTime> datesList = dateGenerator.GenerateHistoricalDates(earlierDate, laterDate);
datesList = datesList.Where(x => x >= earlierDate).ToList();
return GetPrices(symbol, laterDate, datesList.Count);
}
// The most recent price is returned at the lowest index
public Prices GetPrices(String symbol,DateTime startDate,int dayCount)
{
lock(thisLock)
{
List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(startDate,dayCount+60);
Prices prices=null;
List<DateTime> missingDates=null;
foreach(DateTime historicalDate in historicalDates)
{
if(!ContainsPrice(symbol,historicalDate))
{
String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(historicalDate);
if(nullCache.ContainsKey(key)) continue;
if(null==missingDates)missingDates=new List<DateTime>();
missingDates.Add(historicalDate);
}
}
if(null!=missingDates)
{
DateTime minDate=(from DateTime date in missingDates select date).Min();
DateTime maxDate=(from DateTime date in missingDates select date).Max();
prices=PricingDA.GetPrices(symbol,maxDate,minDate);
foreach(Price price in prices) AddPrice(price);
prices=new Prices();
foreach(DateTime historicalDate in historicalDates)
{
if(!ContainsPrice(symbol,historicalDate))
{
String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(historicalDate);
if(!nullCache.ContainsKey(key)) nullCache.Add(key,true);
}
else
{
if(!priceCache.ContainsKey(symbol)) continue;
PricesByDate pricesByDate=priceCache[symbol];
if(!pricesByDate.ContainsKey(historicalDate.Date)) continue;
prices.Add(pricesByDate[historicalDate]);
}
}
}
else
{
prices=new Prices();
foreach(DateTime historicalDate in historicalDates)
{
if(!priceCache.ContainsKey(symbol)) continue;
if(!priceCache[symbol].ContainsKey(historicalDate.Date))
{
continue;
}
prices.Add((priceCache[symbol])[historicalDate]);
}
}
return new Prices(prices.OrderByDescending(x => x.Date).ToList().Take(dayCount).ToList());
}
}
private void AddPrice(Price price)
{
lock(thisLock)
{
if(null==price) return;
PricesByDate pricesByDate=null;
if(!priceCache.ContainsKey(price.Symbol))
{
pricesByDate=new PricesByDate();
pricesByDate.Add(price.Date,price);
priceCache.Add(price.Symbol,pricesByDate);
}
else
{
pricesByDate=priceCache[price.Symbol];
if(pricesByDate.ContainsKey(price.Date.Date)) return;
pricesByDate.Add(price.Date.Date,price);
}
}
}
public bool ContainsPrice(String symbol,DateTime date)
{
if(!priceCache.ContainsKey(symbol)) return false;
PricesByDate pricesByDate=priceCache[symbol];
if(!pricesByDate.ContainsKey(date.Date)) return false;
return true;
}
private void ThreadProc()
{
int quantums=0;
int quantumInterval=1000;
while(threadRun)
{
Thread.Sleep(quantumInterval);
if(!threadRun) break;
quantums+=quantumInterval;
if(quantums>cacheRefreshAfter)
{
quantums=0;
lock(thisLock)
{
realTimePriceCache.Clear();
MDTrace.WriteLine(LogLevel.DEBUG,"Clearing GBPriceCache price cache.");
}
}
}
MDTrace.WriteLine(LogLevel.DEBUG,"[GBPriceCache:ThreadProc]Thread ended.");
}
}
}