261 lines
8.7 KiB
C#
261 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.");
|
|
}
|
|
}
|
|
}
|
|
|