using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Math.Declisions.Dtos; using System.Collections.Concurrent; namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache { public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit { public const int CacheMaxLength = 30000; private const int _arrayMaxLength = 60000; public string Figi { get; init; } public int Length { get { lock (_locker) { return _length; } } } public decimal AsksCount { get { lock (_locker) { return _asksCount; } } } public decimal BidsCount { get { lock (_locker) { return _bidsCount; } } } private readonly object _locker = new(); private readonly decimal[] Prices = new decimal[_arrayMaxLength]; private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength]; private readonly ConcurrentDictionary _1_minTimeWindows = new(); private readonly ConcurrentDictionary _5_minTimeWindows = new(); private readonly ConcurrentDictionary _15_minTimeWindows = new(); private int _length = 0; private int _pointer = -1; private long _asksCount = 1; private long _bidsCount = 1; public ValueTask AddDataToTimeWindowCache(string key, CachedValue data, TimeWindowCacheType timeWindowCacheType) { var dict = GetDict(timeWindowCacheType); if (!dict.TryGetValue(key, out var cahcheItem)) { dict.TryAdd(key, new TimeWindowCacheItem(key, timeWindowCacheType)); } dict[key].AddData(data); return ValueTask.CompletedTask; } public ValueTask GetDataFromTimeWindowCache(string key, TimeWindowCacheType timeWindowCacheType) { var dict = GetDict(timeWindowCacheType); if (dict.TryGetValue(key, out var cahcheItem)) { return cahcheItem.GetValues(); } return ValueTask.FromResult(Array.Empty()); } private ConcurrentDictionary GetDict(TimeWindowCacheType timeWindowCacheType) { switch (timeWindowCacheType) { case TimeWindowCacheType._5_Minutes: { return _5_minTimeWindows; } case TimeWindowCacheType._15_Minutes: { return _15_minTimeWindows; } default: { return _1_minTimeWindows; ; } } } public ValueTask AddData(INewPrice priceChange) { if (priceChange.Figi != Figi) return ValueTask.CompletedTask; lock (_locker) { _pointer++; Prices[_pointer] = priceChange.Value; Timestamps[_pointer] = priceChange.Time; if (_length < CacheMaxLength) { _length++; } if (_pointer == _arrayMaxLength - 1) { Array.Copy(Prices, Prices.Length - CacheMaxLength, Prices, 0, CacheMaxLength); Array.Copy(Timestamps, Timestamps.Length - CacheMaxLength, Timestamps, 0, CacheMaxLength); _pointer = CacheMaxLength - 1; } } return ValueTask.CompletedTask; } public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(int? length = null) { lock (_locker) { if (_pointer < 0) { return ValueTask.FromResult((Array.Empty(), Array.Empty())); } else { var dataLength = length.HasValue ? System.Math.Min(length.Value, _length) : _length; var prices = new decimal[dataLength]; var timestamps = new DateTime[dataLength]; var index = 1 + _pointer - dataLength; Array.Copy(Prices, index, prices, 0, prices.Length); Array.Copy(Timestamps, index, timestamps, 0, timestamps.Length); return ValueTask.FromResult((timestamps, prices)); } } } public ValueTask AddOrderbook(IOrderbook orderbook) { if (orderbook.Figi != Figi) return ValueTask.CompletedTask; lock (_locker) { _asksCount = orderbook.AsksCount; _bidsCount = orderbook.BidsCount; } return ValueTask.CompletedTask; } public ValueTask<(DateTime time, decimal price)> GetLastValues() { lock (_locker) { return _pointer >= 0 ? ValueTask.FromResult((Timestamps[_pointer], Prices[_pointer])) : ValueTask.FromResult((DateTime.UtcNow, 0m)); } } public ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(TimeSpan period) { lock (_locker) { if (_pointer < 0) { return ValueTask.FromResult((Array.Empty(), Array.Empty(), false)); } else { var i = _pointer; var lastTime = Timestamps[i]; for (i = _pointer - 1; i >= 0; i--) { var currentTime = Timestamps[i]; if (lastTime - currentTime >= period) { break; } } var dataLength = _pointer - i; var prices = new decimal[dataLength]; var timestamps = new DateTime[dataLength]; var index = 1 + _pointer - dataLength; Array.Copy(Prices, index, prices, 0, prices.Length); Array.Copy(Timestamps, index, timestamps, 0, timestamps.Length); return ValueTask.FromResult((timestamps, prices, i + 1 != 0)); } } } public PriceHistoryCacheUnit2(string figi, params INewPrice[] priceChanges) { Figi = figi; if (priceChanges.Length == 0) { return; } var selectedPriceChanges = priceChanges .OrderBy(pc => pc.Time) .Skip(priceChanges.Length - CacheMaxLength) .ToArray(); foreach (var pc in selectedPriceChanges) { AddData(pc).AsTask().Wait(); } } } }