using MarketData.MarketDataModel; using MarketData.Utils; using MarketData.DataAccess; // This cache is mainly used by gainloss generator. This cache is intended to be front loaded and then used. // This cache will not attempt to load an item that is not found. It does have a Refresh() that will reload only the most recent pricing data from the database in order to // maintain the most updated pricing. namespace MarketData.Cache { public class LocalPriceCache { private Dictionary priceCache=new Dictionary(); private static LocalPriceCache instance=null; private Thread cacheMonitorThread=null; private volatile bool threadRun=true; private int cacheCycle=300000; private Object thisLock=new Object(); private LocalPriceCache() { cacheMonitorThread=new Thread(new ThreadStart(ThreadProc)); cacheMonitorThread.Start(); } public void Clear() { lock(thisLock) { priceCache=new Dictionary(); } } public void Dispose() { lock(thisLock) { if(null==instance || false==threadRun)return; threadRun=false; if(null!=cacheMonitorThread) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[LocalPriceCache:Dispose]Thread state is '{0}'. Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread))); cacheMonitorThread.Join(5000); this.cacheMonitorThread=null; } MDTrace.WriteLine(LogLevel.DEBUG,"[LocalPriceCache:Dispose] End"); instance=null; } } public static LocalPriceCache GetInstance() { lock(typeof(LocalPriceCache)) { if(null==instance)instance=new LocalPriceCache(); return instance; } } public void Refresh() { lock(typeof(LocalPriceCache)) { List symbols=new List(priceCache.Keys); Dictionary maxDbDates = PricingDA.GetLatestDates(symbols); foreach(String symbol in symbols) { PricesByDate symbolPrices=priceCache[symbol]; DateTime maxDate=symbolPrices.MaxDate; // get the latest date in the cache if(maxDbDates.ContainsKey(symbol) && !maxDbDates[symbol].Date.Equals(maxDate.Date)) // if the cache date and the database date are not equal then reload the cache { MDTrace.WriteLine(LogLevel.DEBUG,$"[LocalPriceCache] Cache date and Database date for {symbol} are not equal, reloading cache. Cache Date:{maxDate.ToShortDateString()} Database Date:{maxDbDates[symbol].Date.ToShortDateString()}"); Prices prices=PricingDA.GetPrices(symbol,symbolPrices.MinDate); // reload the prices for this symbol using the current minDate in the cache as a lower boundary if(null==prices)continue; // if we can't load any prices for symbol then just continue priceCache.Remove(symbol); // remove the pricing entries in the price cache for the symbol priceCache.Add(symbol,prices.GetPricesByDate()); // reload the cache } else { MDTrace.WriteLine(LogLevel.DEBUG,$"[LocalPriceCache] Fetching latest price from database for {symbol} on {maxDate.ToShortDateString()}"); Price price=PricingDA.GetPrice(symbol,maxDate); // the max date from the cache equals the max date from the database so just reload the latest price from the database if(null==price)continue; // if no latest price then just continue symbolPrices.Remove(maxDate); // remove the current price associated with the max date symbolPrices.Add(maxDate,price); // reload the latest price for maxDate(symbol) into the cache } } } } // This version of Add(PortfolioTrades) will account for adding multiple lots at different times. So instead of just checking for the existance of the symbol in the cache // we look to see if the symbol is in the cache and what dates are available. If the date range specified in the trade are not available then we load those date ranges. // This is a brute force approach always maintaining the gap between successive TradeDates in th.e portfolio trades and the maximum date for the symbol in the database. // So while it is inefficient in terms of memory usage it alleviates the need for figuring out contiguous price sections public void Add(PortfolioTrades portfolioTrades) { lock(typeof(LocalPriceCache)) { Profiler profiler=new Profiler(); profiler.Start(); List symbols=portfolioTrades.Symbols; foreach(String symbol in symbols) { DateTime minPortfolioTradeDate=portfolioTrades.GetMinTradeDate(symbol); if(!ContainsSymbol(symbol)) { Prices prices=PricingDA.GetPrices(symbol,minPortfolioTradeDate); if(null==prices)continue; foreach(Price price in prices)Add(price); } else { DateTime minCacheDate=GetMinCacheDate(symbol); if(minPortfolioTradeDate symbols,DateTime pricingDate) { foreach(String symbol in symbols) { if(ContainsPrice(symbol,pricingDate))continue; Price price=PricingDA.GetPrice(symbol,pricingDate); if(null==price)continue; Add(price); } } public void Add(Price price) { lock(typeof(LocalPriceCache)) { if(null==price)return; if(ContainsPrice(price.Symbol,price.Date))return; PricesByDate pricesByDate=null; if(!priceCache.ContainsKey(price.Symbol)) { pricesByDate=new PricesByDate(); pricesByDate.Add(price.Date,price); priceCache.Add(price.Symbol,pricesByDate); return; } pricesByDate=priceCache[price.Symbol]; if(pricesByDate.ContainsKey(price.Date))return; pricesByDate.Add(price.Date,price); } } public DateTime GetMinCacheDate(String symbol) { if(!ContainsSymbol(symbol))return Utility.Epoch; PricesByDate symbolPrices=priceCache[symbol]; return symbolPrices.MinDate; } public void RemoveDate(DateTime date) { lock(typeof(LocalPriceCache)) { List symbols=new List(priceCache.Keys); foreach(String key in symbols) { PricesByDate pricesByDate=priceCache[key]; if(pricesByDate.ContainsKey(date))pricesByDate.Remove(date); } } } public Price GetPrice(String symbol,DateTime date) { lock(typeof(LocalPriceCache)) { if(!priceCache.ContainsKey(symbol))return null; PricesByDate pricesByDate=priceCache[symbol]; if(!pricesByDate.ContainsKey(date))return null; return pricesByDate[date]; } } public bool ContainsPrice(String symbol,DateTime date) { lock(typeof(LocalPriceCache)) { if(!priceCache.ContainsKey(symbol))return false; PricesByDate pricesByDate=priceCache[symbol]; if(!pricesByDate.ContainsKey(date))return false; return true; } } public bool ContainsPrice(List symbols,DateTime date) { lock(typeof(LocalPriceCache)) { foreach(String symbol in symbols)if(!ContainsPrice(symbol,date))return false; return true; } } public bool ContainsSymbol(String symbol) { lock(typeof(LocalPriceCache)) { if(priceCache.ContainsKey(symbol))return true; return false; } } public long Count() { long count=0; List symbols=priceCache.Keys.ToList(); foreach(String symbol in symbols) { PricesByDate pricesByDate=priceCache[symbol]; count+=pricesByDate.Count; } return count; } private void ThreadProc() { int quantums=0; int quantumInterval=1000; long lastCount=0; while(threadRun) { Thread.Sleep(quantumInterval); quantums+=quantumInterval; if(quantums>cacheCycle) { quantums=0; lock(thisLock) { lastCount=Count(); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[LocalPriceCache:ThreadProc] Symbols: {0}. Items in cache: {1}.",priceCache.Keys.Count,Utility.FormatNumber(lastCount,0,true))); } } } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[LocalPriceCache:ThreadProc] Thread ended. Items in cache:{0}",Utility.FormatNumber(lastCount,0,true))); } } }