diff --git a/MarketData/MarketDataLib/Cache/GLPriceCache.cs b/MarketData/MarketDataLib/Cache/GLPriceCache.cs
index 99e7d1a..fa67c83 100644
--- a/MarketData/MarketDataLib/Cache/GLPriceCache.cs
+++ b/MarketData/MarketDataLib/Cache/GLPriceCache.cs
@@ -2,444 +2,508 @@ using MarketData.MarketDataModel;
using MarketData.Utils;
using MarketData.DataAccess;
using System.Collections.Concurrent;
-using System.Threading;
using System.Collections.Generic;
using System;
-using System.Threading.Tasks;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace MarketData.Cache
{
- ///
- /// This cache is used in the GainLoss Generators
- ///
- public class GLPriceCache
- {
- private Dictionary priceCache = new Dictionary();
- private static GLPriceCache instance = null;
- private DateTime latestDate = Utility.Epoch;
- private Thread cacheMonitorThread = null;
- private volatile bool threadRun = true;
- private int cacheCycle = 300000;
- private object thisLock = new object();
- private object fetchLock = new object();
-
- private GLPriceCache()
+ public class GLPriceCache : IDisposable
{
- cacheMonitorThread = new Thread(new ThreadStart(ThreadProc));
- cacheMonitorThread.Start();
- }
+ // -- Singleton ------------------------------------------------------------
+ private static readonly object instanceLock = new object();
+ private static GLPriceCache instance = null;
- public void Clear()
- {
- lock (thisLock)
- {
- priceCache = new Dictionary();
- RefreshLatestDate();
- }
- }
-
- public void Dispose()
- {
- Thread threadToJoin = null;
-
- lock (thisLock)
- {
- if (instance == null || !threadRun) return;
- threadRun = false;
- threadToJoin = cacheMonitorThread;
- cacheMonitorThread = null;
- instance = null;
- }
-
- if (threadToJoin != null)
- {
- MDTrace.WriteLine(LogLevel.DEBUG, $"[GLPriceCache:Dispose] Thread state is '{Utility.ThreadStateToString(threadToJoin)}'. Joining...");
- threadToJoin.Join(5000);
- }
-
- MDTrace.WriteLine(LogLevel.DEBUG, "[GLPriceCache:Dispose] End");
- }
-
- public static GLPriceCache GetInstance()
- {
- lock (typeof(LocalPriceCache))
- {
- if (instance == null)
+ public static GLPriceCache GetInstance()
{
- instance = new GLPriceCache();
- }
- return instance;
- }
- }
-
- public void RefreshLatestDate()
- {
- lock (thisLock)
- {
- latestDate = PricingDA.GetLatestDate();
- }
- }
-
- public DateTime GetLatestDate()
- {
- lock (thisLock)
- {
- if (Utility.IsEpoch(latestDate))
- {
- RefreshLatestDate();
- }
- return latestDate;
- }
- }
-
- public void Refresh()
- {
- List symbols;
- Dictionary currentMaxDates;
-
- lock (thisLock)
- {
- symbols = priceCache.Keys.ToList();
- currentMaxDates = priceCache.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.MaxDate);
- }
-
- if (symbols.Count == 0) return;
-
- ConcurrentDictionary fullReloads = new ConcurrentDictionary();
- ConcurrentDictionary singleUpdates = new ConcurrentDictionary();
- DateTime latestDateFromDb;
-
- lock (fetchLock)
- {
- Dictionary maxDbDates = PricingDA.GetLatestDates(symbols);
- latestDateFromDb = PricingDA.GetLatestDate();
-
- Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = 8 }, symbol =>
- {
- DateTime cachedMax;
- if (!currentMaxDates.TryGetValue(symbol, out cachedMax)) return;
-
- DateTime dbMax;
- if (maxDbDates.TryGetValue(symbol, out dbMax) && dbMax.Date != cachedMax.Date)
- {
- Prices prices = PricingDA.GetPrices(symbol, cachedMax);
- if (prices != null) fullReloads[symbol] = prices.GetPricesByDate();
- }
- else
- {
- Price price = PricingDA.GetPrice(symbol, cachedMax);
- if (price != null) singleUpdates[symbol] = price;
- }
- });
- }
-
- lock (thisLock)
- {
- latestDate = latestDateFromDb;
-
- foreach (KeyValuePair kvp in fullReloads)
- {
- if (priceCache.TryGetValue(kvp.Key, out PricesByDate existing) && existing.MaxDate == currentMaxDates[kvp.Key])
- {
- priceCache[kvp.Key] = kvp.Value;
- }
+ lock (instanceLock)
+ {
+ if (instance == null)
+ instance = new GLPriceCache();
+ return instance;
+ }
}
- foreach (KeyValuePair kvp in singleUpdates)
+ // -- State ----------------------------------------------------------------
+ private readonly Dictionary priceCache = new Dictionary();
+ private readonly ConcurrentDictionary symbolFetchLocks = new ConcurrentDictionary();
+ private readonly object cacheLock = new object();
+ private DateTime latestDate = Utility.Epoch;
+
+ // -- Background refresh ---------------------------------------------------
+ private readonly TimeSpan cacheCycle = TimeSpan.FromMinutes(5);
+ private Timer refreshTimer = null;
+ private int tickCount = 0;
+ private const int evictionTickInterval = 12; // every 12 ticks x 5 min = 1 hour
+
+ // -- Parallelism ----------------------------------------------------------
+ private static readonly int maxParallelDbCalls = ResolveMaxParallelDbCalls();
+
+ private static int ResolveMaxParallelDbCalls()
{
- if (priceCache.TryGetValue(kvp.Key, out PricesByDate pricesByDate) && pricesByDate.MaxDate == currentMaxDates[kvp.Key])
- {
- if (pricesByDate.ContainsKey(kvp.Value.Date))
- pricesByDate.Remove(kvp.Value.Date);
- pricesByDate.Add(kvp.Value.Date, kvp.Value);
- }
- }
- }
-
- MDTrace.WriteLine(LogLevel.DEBUG, $"Full reloads: {fullReloads.Count}, Single updates: {singleUpdates.Count}");
- }
-
- public void Add(PortfolioTrades portfolioTrades)
- {
- List symbols = portfolioTrades.Symbols;
- DateTime today = DateTime.Today;
-
- Dictionary minTradeDates = symbols.ToDictionary(symbol => symbol, symbol => portfolioTrades.GetMinTradeDate(symbol));
-
- // Symbols that need an intraday refresh:
- // - open positions (no close date), or
- // *** REMOVED THIS - closed today (close price may still be settling) TODO *****
- HashSet mutableSymbols = new HashSet(symbols.Where(symbol => portfolioTrades.HasOpenPositions(symbol)));
-
- Dictionary minCacheDates;
- lock (thisLock)
- {
- minCacheDates = symbols.ToDictionary(symbol => symbol,symbol => priceCache.ContainsKey(symbol) ? priceCache[symbol].MinDate : DateTime.MaxValue);
+ return Math.Min(Math.Max(1, Environment.ProcessorCount) * 3, 32);
+ // int @default = Math.Min(Math.Max(1, Environment.ProcessorCount) * 3, 32);
+ // string configured = Environment.GetEnvironmentVariable("GL_PRICE_CACHE_PARALLEL_DB_CALLS");
+ // return int.TryParse(configured, out int parsed) && parsed > 0 ? parsed : @default;
}
- ConcurrentDictionary fetchedPrices = new ConcurrentDictionary();
- ConcurrentDictionary latestPrices = new ConcurrentDictionary();
+ // -- Disposal -------------------------------------------------------------
+ private volatile bool disposed = false;
+ private int refreshInProgress = 0;
- Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = 8 }, symbol =>
+ // -- Constructor ----------------------------------------------------------
+ private GLPriceCache()
{
- DateTime minTradeDate = minTradeDates[symbol];
- DateTime minCacheDate = minCacheDates[symbol];
+ refreshTimer = new Timer(OnCacheRefreshTick, null, cacheCycle, cacheCycle);
+ }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed) return;
+ disposed = true;
+
+ if (disposing)
+ {
+ Timer timerToDispose;
+ lock (instanceLock)
+ {
+ timerToDispose = refreshTimer;
+ refreshTimer = null;
+ instance = null;
+ }
+
+ // Block until any in-flight tick completes before disposing
+ using (ManualResetEventSlim waited = new ManualResetEventSlim(false))
+ {
+ if (timerToDispose != null)
+ timerToDispose.Dispose(waited.WaitHandle);
+ waited.Wait(TimeSpan.FromSeconds(10));
+ }
+
+ MDTrace.WriteLine(LogLevel.DEBUG, "[GLPriceCache:Dispose] Disposed.");
+ }
+ }
+
+ // -- Background tick ------------------------------------------------------
+ private void OnCacheRefreshTick(object state)
+ {
+ if (disposed) return;
try
{
- // Historical fetch � only when cache is missing or incomplete
- Prices prices = null;
- if (minCacheDate == DateTime.MaxValue)
+ lock (cacheLock)
{
- prices = PricingDA.GetPrices(symbol, minTradeDate);
- }
- else if (minTradeDate < minCacheDate)
- {
- prices = PricingDA.GetPrices(symbol, minCacheDate, minTradeDate);
+ long count = CountInternal();
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Tick] Symbols: {priceCache.Keys.Count}. " +
+ $"Items in cache: {Utility.FormatNumber(count, 0, true)}.");
}
- if (prices != null && prices.Count > 0)
- {
- fetchedPrices[symbol] = prices;
- }
-
- // Intraday refresh open positions and positions closed today only
- if (mutableSymbols.Contains(symbol))
- {
- Price latestPrice = PricingDA.GetPrice(symbol);
- if (latestPrice != null)
- latestPrices[symbol] = latestPrice;
- }
+ if (++tickCount % evictionTickInterval == 0)
+ EvictStaleSymbols();
}
catch (Exception ex)
{
- MDTrace.WriteLine(LogLevel.DEBUG, $"Error fetching prices for {symbol}: {ex.Message}");
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Tick] [ERROR] Unhandled exception: {ex}");
}
- });
+ }
- lock (thisLock)
+ // -- Eviction -------------------------------------------------------------
+ private void EvictStaleSymbols()
{
- // Historical prices idempotent, will not overwrite existing entries
- foreach (var kvp in fetchedPrices)
+ if (Interlocked.CompareExchange(ref refreshInProgress, 1, 0) == 1)
{
- foreach (var price in kvp.Value)
+ MDTrace.WriteLine(LogLevel.DEBUG, "[GLPriceCache:Evict] Skipped refresh in progress.");
+ return;
+ }
+
+ try
+ {
+ int count;
+ lock (cacheLock)
{
- Add(price);
+ count = priceCache.Count;
+ priceCache.Clear();
+ symbolFetchLocks.Clear();
+ }
+
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Evict] Cache cleared. {count} symbols evicted.");
+ }
+ finally
+ {
+ Interlocked.Exchange(ref refreshInProgress, 0);
+ }
+ }
+
+ // -- Public API -----------------------------------------------------------
+
+ public void Add(PortfolioTrades portfolioTrades)
+ {
+ List symbols = portfolioTrades.Symbols;
+
+ Dictionary minTradeDates = symbols.ToDictionary(
+ sym => sym, sym => portfolioTrades.GetMinTradeDate(sym));
+
+ // Only open positions need an intraday price refresh.
+ // Closed positions, regardless of close date, have immutable prices skip the DB call.
+ HashSet mutableSymbols = new HashSet(
+ symbols.Where(sym => portfolioTrades.HasOpenPositions(sym)));
+
+ Dictionary minCacheDates;
+ lock (cacheLock)
+ {
+ minCacheDates = symbols.ToDictionary(
+ sym => sym,
+ sym => priceCache.ContainsKey(sym) ? priceCache[sym].MinDate : DateTime.MaxValue);
+ }
+
+ ConcurrentDictionary fetchedPrices = new ConcurrentDictionary();
+ ConcurrentDictionary latestPrices = new ConcurrentDictionary();
+
+ Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = maxParallelDbCalls }, symbol =>
+ {
+ if (disposed) return;
+
+ DateTime minTradeDate = minTradeDates[symbol];
+ DateTime minCacheDate = minCacheDates[symbol];
+
+ // Acquire a per-symbol lock to prevent concurrent clients from issuing
+ // duplicate DB fetches for the same symbol. First thread fetches;
+ // subsequent threads for the same symbol block here, then re-check
+ // the cache on entry and skip the fetch if already populated.
+ object symbolLock = symbolFetchLocks.GetOrAdd(symbol, _ => new object());
+ lock (symbolLock)
+ {
+ try
+ {
+ // Re-check cache state after acquiring symbol lock another
+ // client thread may have already fetched this symbol while we waited
+ lock (cacheLock)
+ {
+ if (priceCache.ContainsKey(symbol))
+ minCacheDate = priceCache[symbol].MinDate;
+ }
+
+ // Historical fetch only when cache is missing or incomplete
+ Prices prices = null;
+ if (minCacheDate == DateTime.MaxValue)
+ prices = PricingDA.GetPrices(symbol, minTradeDate);
+ else if (minTradeDate < minCacheDate)
+ prices = PricingDA.GetPrices(symbol, minCacheDate, minTradeDate);
+
+ if (prices != null && prices.Count > 0)
+ fetchedPrices[symbol] = prices;
+
+ // Intraday refresh mutable symbols only
+ if (mutableSymbols.Contains(symbol))
+ {
+ Price latestPrice = PricingDA.GetPrice(symbol);
+ if (latestPrice != null)
+ latestPrices[symbol] = latestPrice;
+ }
+ }
+ catch (Exception ex)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Add] [ERROR] Failed fetching prices for {symbol}: {ex.Message}");
+ }
+ }
+ });
+
+ lock (cacheLock)
+ {
+ // Historical prices idempotent, will not overwrite existing entries
+ foreach (KeyValuePair kvp in fetchedPrices)
+ foreach (Price price in kvp.Value)
+ AddInternal(price);
+
+ // Latest prices unconditional overwrite to capture intraday updates
+ foreach (KeyValuePair kvp in latestPrices)
+ {
+ PricesByDate pricesByDate;
+ if (!priceCache.TryGetValue(kvp.Key, out pricesByDate))
+ {
+ pricesByDate = new PricesByDate();
+ priceCache[kvp.Key] = pricesByDate;
+ }
+
+ if (pricesByDate.ContainsKey(kvp.Value.Date))
+ pricesByDate.Remove(kvp.Value.Date);
+ pricesByDate.Add(kvp.Value.Date, kvp.Value);
}
}
- // Latest prices unconditional overwrite to capture any intraday updates
- foreach (var kvp in latestPrices)
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Add] Symbols: {symbols.Count}, Mutable: {mutableSymbols.Count}, " +
+ $"Historical fetches: {fetchedPrices.Count}, Intraday updates: {latestPrices.Count}");
+ }
+
+ public void Add(Prices prices)
+ {
+ foreach (Price price in prices)
+ Add(price);
+ }
+
+ public void Add(List symbols, DateTime pricingDate)
+ {
+ if (symbols == null || symbols.Count == 0) return;
+
+ ConcurrentDictionary fetchedPrices = new ConcurrentDictionary();
+
+ Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = maxParallelDbCalls }, symbol =>
{
- if (!priceCache.TryGetValue(kvp.Key, out var pricesByDate))
+ if (disposed) return;
+
+ lock (cacheLock)
{
- pricesByDate = new PricesByDate();
- priceCache[kvp.Key] = pricesByDate;
+ if (ContainsPriceInternal(symbol, pricingDate)) return;
}
- if (pricesByDate.ContainsKey(kvp.Value.Date))
- pricesByDate.Remove(kvp.Value.Date);
- pricesByDate.Add(kvp.Value.Date, kvp.Value);
+ try
+ {
+ Price price = PricingDA.GetPrice(symbol, pricingDate);
+ if (price != null) fetchedPrices[symbol] = price;
+ }
+ catch (Exception ex)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Add] [ERROR] Failed fetching price for {symbol} on {pricingDate:yyyy-MM-dd}: {ex.Message}");
+ }
+ });
+
+ lock (cacheLock)
+ {
+ foreach (KeyValuePair kvp in fetchedPrices)
+ AddInternal(kvp.Value);
}
}
- MDTrace.WriteLine(LogLevel.DEBUG,
- $"[GLPriceCache:Add] Symbols: {symbols.Count}, Mutable: {mutableSymbols.Count}, " +
- $"Historical fetches: {fetchedPrices.Count}, Intraday updates: {latestPrices.Count}");
- }
-
- public void Add(Prices prices)
- {
- foreach (Price price in prices)
- {
- Add(price);
- }
- }
-
- public void Add(List symbols, DateTime pricingDate)
- {
- if (symbols == null || symbols.Count == 0) return;
-
- ConcurrentDictionary fetchedPrices = new ConcurrentDictionary();
-
- Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = 8 }, symbol =>
- {
- lock (thisLock)
+ public void Add(Price price)
{
- if (ContainsPrice(symbol, pricingDate)) return;
+ lock (cacheLock)
+ {
+ AddInternal(price);
+ }
}
- try
+ public void Refresh()
{
- Price price = PricingDA.GetPrice(symbol, pricingDate);
- if (price != null) fetchedPrices[symbol] = price;
- }
- catch (Exception ex)
- {
- MDTrace.WriteLine(LogLevel.DEBUG, $"GLPriceCache: Error fetching price for {symbol} on {pricingDate:yyyy-MM-dd}: {ex.Message}");
- }
- });
+ if (Interlocked.CompareExchange(ref refreshInProgress, 1, 0) == 1)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG, "[GLPriceCache:Refresh] Skipped refresh already in progress.");
+ return;
+ }
- lock (thisLock)
- {
- foreach (KeyValuePair kvp in fetchedPrices)
- {
- Add(kvp.Value);
- }
- }
- }
+ try
+ {
+ List symbols;
+ Dictionary currentMaxDates;
- public void Add(Price price)
- {
- if (price == null) return;
+ lock (cacheLock)
+ {
+ symbols = priceCache.Keys.ToList();
+ currentMaxDates = priceCache.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.MaxDate);
+ }
- lock (thisLock)
- {
- PricesByDate pricesByDate;
- if (!priceCache.TryGetValue(price.Symbol, out pricesByDate))
- {
- pricesByDate = new PricesByDate();
- priceCache[price.Symbol] = pricesByDate;
- }
- if (!pricesByDate.ContainsKey(price.Date))
- {
- pricesByDate.Add(price.Date, price);
- }
- }
- }
+ if (symbols.Count == 0) return;
- public DateTime GetMinCacheDate(string symbol)
- {
- lock (thisLock)
- {
- PricesByDate symbolPrices;
- if (!priceCache.TryGetValue(symbol, out symbolPrices) || symbolPrices.Count == 0)
- {
- return Utility.Epoch;
- }
- return symbolPrices.MinDate;
- }
- }
+ ConcurrentDictionary fullReloads = new ConcurrentDictionary();
+ ConcurrentDictionary singleUpdates = new ConcurrentDictionary();
- public void RemoveDate(DateTime date)
- {
- lock (thisLock)
- {
- foreach (KeyValuePair kvp in priceCache)
- {
- kvp.Value.Remove(date);
- }
- }
- }
+ // Fetch outside the cache lock no timeout risk holding cacheLock over I/O
+ Dictionary maxDbDates = PricingDA.GetLatestDates(symbols);
+ DateTime latestDateFromDb = PricingDA.GetLatestDate();
- public Prices GetPrices(string symbol, DateTime endDate, int dayCount)
- {
- lock (thisLock)
- {
- PricesByDate pricesByDate;
- if (!priceCache.TryGetValue(symbol, out pricesByDate)) return new Prices();
+ Parallel.ForEach(symbols, new ParallelOptions { MaxDegreeOfParallelism = maxParallelDbCalls }, symbol =>
+ {
+ if (disposed) return;
- DateGenerator dateGenerator = new DateGenerator();
- List historicalDates = dateGenerator.GenerateHistoricalDates(endDate, dayCount);
+ DateTime cachedMax;
+ if (!currentMaxDates.TryGetValue(symbol, out cachedMax)) return;
- Prices result = new Prices();
- foreach (DateTime date in historicalDates)
- {
- if (pricesByDate.ContainsKey(date))
- {
- result.Add(pricesByDate[date]);
- }
+ try
+ {
+ DateTime dbMax;
+ if (maxDbDates.TryGetValue(symbol, out dbMax) && dbMax.Date != cachedMax.Date)
+ {
+ Prices prices = PricingDA.GetPrices(symbol, cachedMax);
+ if (prices != null) fullReloads[symbol] = prices.GetPricesByDate();
+ }
+ else
+ {
+ Price price = PricingDA.GetPrice(symbol, cachedMax);
+ if (price != null) singleUpdates[symbol] = price;
+ }
+ }
+ catch (Exception ex)
+ {
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Refresh] [ERROR] Failed refreshing {symbol}: {ex.Message}");
+ }
+ });
+
+ lock (cacheLock)
+ {
+ latestDate = latestDateFromDb;
+
+ foreach (KeyValuePair kvp in fullReloads)
+ {
+ PricesByDate existing;
+ if (priceCache.TryGetValue(kvp.Key, out existing) &&
+ existing.MaxDate == currentMaxDates[kvp.Key])
+ {
+ priceCache[kvp.Key] = kvp.Value;
+ }
+ }
+
+ foreach (KeyValuePair kvp in singleUpdates)
+ {
+ PricesByDate pricesByDate;
+ if (priceCache.TryGetValue(kvp.Key, out pricesByDate) &&
+ pricesByDate.MaxDate == currentMaxDates[kvp.Key])
+ {
+ if (pricesByDate.ContainsKey(kvp.Value.Date))
+ pricesByDate.Remove(kvp.Value.Date);
+ pricesByDate.Add(kvp.Value.Date, kvp.Value);
+ }
+ }
+ }
+
+ MDTrace.WriteLine(LogLevel.DEBUG,
+ $"[GLPriceCache:Refresh] Full reloads: {fullReloads.Count}, Single updates: {singleUpdates.Count}");
+ }
+ finally
+ {
+ Interlocked.Exchange(ref refreshInProgress, 0);
+ }
}
- return result;
- }
- }
-
- public Price GetPrice(string symbol, DateTime date)
- {
- lock (thisLock)
- {
- PricesByDate pricesByDate;
- if (!priceCache.TryGetValue(symbol, out pricesByDate)) return null;
-
- Price price;
- return pricesByDate.TryGetValue(date, out price) ? price : null;
- }
- }
-
- public bool ContainsPrice(string symbol, DateTime date)
- {
- lock (thisLock)
- {
- PricesByDate pricesByDate;
- if (!priceCache.TryGetValue(symbol, out pricesByDate)) return false;
- return pricesByDate.ContainsKey(date);
- }
- }
-
- public bool ContainsPrice(List symbols, DateTime date)
- {
- if (symbols == null || symbols.Count == 0) return false;
-
- lock (thisLock)
- {
- foreach (string symbol in symbols)
+ public DateTime GetLatestDate()
{
- PricesByDate pricesByDate;
- if (!priceCache.TryGetValue(symbol, out pricesByDate) || !pricesByDate.ContainsKey(date))
- {
- return false;
- }
+ lock (cacheLock)
+ {
+ if (Utility.IsEpoch(latestDate))
+ latestDate = PricingDA.GetLatestDate();
+ return latestDate;
+ }
}
- return true;
- }
- }
- public bool ContainsSymbol(string symbol)
- {
- lock (thisLock)
- {
- return priceCache.ContainsKey(symbol);
- }
- }
-
- private long Count()
- {
- lock (thisLock)
- {
- long count = 0;
- foreach (PricesByDate pricesByDate in priceCache.Values)
+ public void RefreshLatestDate()
{
- count += pricesByDate.Count;
+ lock (cacheLock)
+ {
+ latestDate = PricingDA.GetLatestDate();
+ }
}
- return count;
- }
- }
- private void ThreadProc()
- {
- int quantums = 0;
- int quantumInterval = 1000;
- long lastCount = 0;
-
- while (threadRun)
- {
- Thread.Sleep(quantumInterval);
- if(!threadRun)break;
- quantums += quantumInterval;
- if (quantums > cacheCycle)
+ public DateTime GetMinCacheDate(string symbol)
{
- quantums = 0;
- lock (thisLock)
- {
- lastCount = Count();
- MDTrace.WriteLine(LogLevel.DEBUG, $"[GLPriceCache:ThreadProc] Symbols: {priceCache.Keys.Count}. Items in cache: {Utility.FormatNumber(lastCount,0,true)}.");
- }
+ lock (cacheLock)
+ {
+ PricesByDate symbolPrices;
+ if (!priceCache.TryGetValue(symbol, out symbolPrices) || symbolPrices.Count == 0)
+ return Utility.Epoch;
+ return symbolPrices.MinDate;
+ }
}
- }
- MDTrace.WriteLine(LogLevel.DEBUG, $"[GLPriceCache:ThreadProc] Thread ended. Items in cache:{Utility.FormatNumber(lastCount,0,true)}");
+ public Prices GetPrices(string symbol, DateTime endDate, int dayCount)
+ {
+ lock (cacheLock)
+ {
+ PricesByDate pricesByDate;
+ if (!priceCache.TryGetValue(symbol, out pricesByDate)) return new Prices();
+
+ DateGenerator dateGenerator = new DateGenerator();
+ List historicalDates = dateGenerator.GenerateHistoricalDates(endDate, dayCount);
+
+ Prices result = new Prices();
+ foreach (DateTime date in historicalDates)
+ if (pricesByDate.ContainsKey(date))
+ result.Add(pricesByDate[date]);
+
+ return result;
+ }
+ }
+
+ public Price GetPrice(string symbol, DateTime date)
+ {
+ lock (cacheLock)
+ {
+ PricesByDate pricesByDate;
+ if (!priceCache.TryGetValue(symbol, out pricesByDate)) return null;
+ Price price;
+ return pricesByDate.TryGetValue(date, out price) ? price : null;
+ }
+ }
+
+ public bool ContainsPrice(string symbol, DateTime date)
+ {
+ lock (cacheLock)
+ {
+ return ContainsPriceInternal(symbol, date);
+ }
+ }
+
+ public bool ContainsPrice(List symbols, DateTime date)
+ {
+ if (symbols == null || symbols.Count == 0) return false;
+ lock (cacheLock)
+ {
+ foreach (string symbol in symbols)
+ if (!ContainsPriceInternal(symbol, date)) return false;
+ return true;
+ }
+ }
+
+ public bool ContainsSymbol(string symbol)
+ {
+ lock (cacheLock)
+ {
+ return priceCache.ContainsKey(symbol);
+ }
+ }
+
+ // -- Private helpers ------------------------------------------------------
+
+ // Must be called under cacheLock
+ private void AddInternal(Price price)
+ {
+ if (price == null) return;
+ PricesByDate pricesByDate;
+ if (!priceCache.TryGetValue(price.Symbol, out pricesByDate))
+ {
+ pricesByDate = new PricesByDate();
+ priceCache[price.Symbol] = pricesByDate;
+ }
+ if (!pricesByDate.ContainsKey(price.Date))
+ pricesByDate.Add(price.Date, price);
+ }
+
+ // Must be called under cacheLock
+ private bool ContainsPriceInternal(string symbol, DateTime date)
+ {
+ PricesByDate pricesByDate;
+ if (!priceCache.TryGetValue(symbol, out pricesByDate)) return false;
+ return pricesByDate.ContainsKey(date);
+ }
+
+ // Must be called under cacheLock
+ private long CountInternal()
+ {
+ long count = 0;
+ foreach (PricesByDate pricesByDate in priceCache.Values)
+ count += pricesByDate.Count;
+ return count;
+ }
}
- }
-}
+}
\ No newline at end of file