using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache { public class PriceHistoryCacheUnit3 { private readonly Dictionary _windowSizes = new() { {string.Empty, TimeSpan.FromDays(3)} }; public string Figi { get; init; } public IOrderbook? Orderbook { get { lock (_locker) { return _orderbook; } } } private IOrderbook? _orderbook; private readonly object _locker = new(); private readonly Dictionary> _items = new Dictionary>(); public ValueTask AddData(ITradeDataItem item, string? key = null) { lock (_locker) { key = key ?? string.Empty; if (key == string.Empty) { if (item.Figi != Figi) { throw new ArgumentException("Для дефолтного кеша Figi должен совпадать с figi элемента."); } } var windowSize = _windowSizes.TryGetValue(key, out var size) ? size : TimeSpan.FromMinutes(60); if (!_items.TryGetValue(key, out var list)) { list = new LinkedList(); _items[key] = list; } list.AddFirst(item); while (list.Last != null && list.First != null && list.First.Value.Time - list.Last.Value.Time > windowSize) { list.RemoveLast(); } } return ValueTask.CompletedTask; } public ValueTask AddOrderbook(IOrderbook orderbook) { if (orderbook.Figi != Figi) return ValueTask.CompletedTask; lock (_locker) { _orderbook = orderbook; } return ValueTask.CompletedTask; } public ValueTask GetData(TimeSpan? period = null, string? key = null, Func? selector = null) { key = key ?? string.Empty; var res = new List(); selector = selector ?? defaultSelector; lock (_locker) { if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null) { var startTime = list.First.Value.Time; if (period.HasValue) { foreach (var item in list) { if (startTime - item.Time < period.Value && selector(item)) { res.Add(item); } } } else { return ValueTask.FromResult(list.Where(selector).Reverse().ToArray()); } } } res.Reverse(); return ValueTask.FromResult(res.ToArray()); } public ValueTask<(DateTime time, decimal price)> GetLastValues(string? key = null) { key = key ?? string.Empty; lock (_locker) { if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null) { return ValueTask.FromResult((list.Last.Value.Time, list.Last.Value.Price)); } } return ValueTask.FromResult((DateTime.MinValue, 0m)); } public ValueTask GetData(TimeSpan shift, TimeSpan period, string? key = null, Func? selector = null) { key = key ?? string.Empty; var res = new List(); var maxPeriod = shift + period; selector = selector ?? defaultSelector; lock (_locker) { if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null) { var startTime = list.First.Value.Time; foreach (var item in list) { var dt = startTime - item.Time; if (dt > shift && dt < maxPeriod && selector(item)) { res.Add(item); } } } } res.Reverse(); return ValueTask.FromResult(res.ToArray()); } public PriceHistoryCacheUnit3(string figi, params ITradeDataItem[] priceChanges) { Figi = figi; if (priceChanges.Length == 0) { return; } var selectedPriceChanges = priceChanges .OrderBy(pc => pc.Time) .ToArray(); foreach (var pc in selectedPriceChanges) { AddData(pc).AsTask().Wait(); } } private static bool defaultSelector(ITradeDataItem item) => true; } }