diff --git a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/CachedValue.cs b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/CachedValue.cs index 61eb4c0..7fcb4f5 100644 --- a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/CachedValue.cs +++ b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/CachedValue.cs @@ -1,15 +1,26 @@ -namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; + +namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos { - public class CachedValue + public class CachedValue : ITradeDataItem { public DateTime Time { get; init; } public long Count { get; init; } public decimal Price { get; init; } public decimal Value { get; init; } public decimal Value2 { get; init; } - //public bool IsHistoricalData { get; init; } - //public required string Figi { get; init; } = string.Empty; - //public required string Ticker { get; init; } = string.Empty; - //public int Direction { get; init; } + + public CachedValue() + { + Figi = string.Empty; + Ticker = string.Empty; + Direction = 0; + IsHistoricalData = false; + } + + public bool IsHistoricalData { get; init; } + public required string Figi { get; init; } + public required string Ticker { get; init; } + public int Direction { get; init; } } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit3.cs b/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit3.cs index f717f37..3aed46f 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit3.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit3.cs @@ -103,6 +103,20 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache 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; diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs index a2a92a2..c16d500 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs @@ -1,16 +1,16 @@ -using KLHZ.Trader.Core.Contracts.Declisions.Dtos; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; namespace KLHZ.Trader.Core.Math.Declisions.Utils { public static class Statistics { - public static decimal Mean(this CachedValue[] values) + public static decimal MeanCount(this ITradeDataItem[] values) { return values.Sum(x => x.Count) / values.Length; } - public static decimal Mean2(this CachedValue[] values) + public static decimal MeanPrice(this ITradeDataItem[] values) { return values.Sum(x => x.Price) / values.Length; } @@ -54,8 +54,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } } - public static bool TryCalcTimeWindowsDiff(this CachedValue[] values, TimeSpan boundLeft, TimeSpan boundRight, - Func fieldSelector, bool calcMean, out decimal result) + public static bool TryCalcTimeWindowsDiff(this ITradeDataItem[] values, TimeSpan boundLeft, TimeSpan boundRight, + Func fieldSelector, bool calcMean, out decimal result) { result = default; if (values.Length > 1) @@ -82,8 +82,30 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } return false; } + public static bool TryCalcTimeDiff(this ITradeDataItem[] values, TimeSpan boundLeft, TimeSpan boundRight, + Func fieldSelector, bool calcMean, out decimal result) + { + result = default; + if (values.Length > 1) + { + var shiftTimeR = values.Last().Time - boundRight; + var shiftTimeL = values.Last().Time - boundLeft; - public static bool TryCalcPirsonCorrelation(this CachedValue[] values, TimeSpan period, out decimal result) + var valuesOld = values.Where(b => b.Time < shiftTimeR && b.Time >= shiftTimeL).ToArray(); + var valuesNew = values.Where(b => b.Time >= shiftTimeR).ToArray(); + + if (valuesOld.Length > 0 && valuesNew.Length > 0) + { + var valNew = fieldSelector(valuesNew.Last()); + var valOld = fieldSelector(valuesOld.Last()); + + result = valNew - valOld; + return true; + } + } + return false; + } + public static bool TryCalcPirsonCorrelation(this ITradeDataItem[] values, TimeSpan period, out decimal result) { result = default; if (values.Any()) @@ -92,8 +114,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils values = values.Where(b => b.Time >= shiftTimeDiffs1).ToArray(); if (values.Any()) { - var tradevolume_diffMean = values.Mean(); - var dprice_diffMean = values.Mean2(); + var tradevolume_diffMean = values.MeanCount(); + var dprice_diffMean = values.MeanPrice(); var sum1 = (double)values.Sum(d => (d.Value2 - tradevolume_diffMean) * (d.Value - dprice_diffMean)); var sum2 = values.Sum(d => (d.Value2 - tradevolume_diffMean) * (d.Value2 - tradevolume_diffMean)); var sum3 = values.Sum(d => (d.Value - dprice_diffMean) * (d.Value - dprice_diffMean)); diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 072c3b2..1195ff8 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -35,6 +35,7 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly ConcurrentDictionary TradingModes = new(); private readonly ConcurrentDictionary DPirsonValues = new(); + private readonly ConcurrentDictionary DPricesValues = new(); private readonly Channel _pricesChannel = Channel.CreateUnbounded(); private readonly Channel _commands = Channel.CreateUnbounded(); @@ -135,117 +136,77 @@ namespace KLHZ.Trader.Core.Exchange.Services #region Добавление данных в кеши. if (message.Figi == "BBG004730N88" || message.Figi == "FUTIMOEXF000") { - if (message.Direction == 1) - { - await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "1", new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - - await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - - await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - } - if (message.Direction == 2) - { - await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "2", new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Count = message.Count, - Price = message.Price, - }); - } - + await _tradeDataProvider.AddData(message); } #endregion if (_exchangeConfig.TradingInstrumentsFigis.Contains(message.Figi) && message.Direction == 1) { - var smallWindow = TimeSpan.FromSeconds(120); - var bigWindow = TimeSpan.FromSeconds(240); - var meanWindow = TimeSpan.FromSeconds(240); + var _15minCacheSize = TimeSpan.FromSeconds(400); + var smallWindow = TimeSpan.FromSeconds(180); + var bigWindow = TimeSpan.FromSeconds(360); + var meanWindow = TimeSpan.FromSeconds(360); var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow; - var buys = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey); - var sells = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey); - var trades = buys.ToList(); - trades.AddRange(sells); - var trades2 = trades.OrderBy(t => t.Time).ToArray(); - if (trades2.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff) - && buys.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff)) + var buys = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize, selector: (i) => i.Direction == 1); + var trades = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize); + if (trades.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff) + && buys.TryCalcTimeDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff)) { await _tradeDataProvider.LogPrice(message, "privcesDiff", pricesDiff); await _tradeDataProvider.LogPrice(message, "tradevolume_diff", tradesDiff); - await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue() + await _tradeDataProvider.AddData(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue() { Time = message.Time, Value2 = tradesDiff, Value = pricesDiff, + Figi = message.Figi, + Ticker = message.Ticker, }); - var diffs = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, "5min_diff"); - if (diffs.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Value, true, out var resdp) - && diffs.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Value2, true, out var resv)) + if (DPricesValues.TryGetValue(message.Figi, out var olddPrice)) { - await _tradeDataProvider.LogPrice(message, "privcesDiffDiff", (decimal)resdp); - await _tradeDataProvider.LogPrice(message, "tradevolume_diff_diff", (decimal)resv); - - if (diffs.TryCalcPirsonCorrelation(meanWindow, out var pirson)) + if (olddPrice < 0m && pricesDiff >0) { - await _tradeDataProvider.LogPrice(message, "diffs_pirson", (decimal)pirson); - await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue() + //await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_in", message.Price); + } + if (olddPrice > 0m && pricesDiff < 0m) + { + //await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_in", message.Price); + } + } + DPricesValues[message.Figi] = pricesDiff; + var diffs = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize, "5min_diff"); + if (diffs.TryCalcPirsonCorrelation(meanWindow, out var pirson)) + { + var res = pirson; + await _tradeDataProvider.LogPrice(message, "diffs_pirson", (decimal)pirson); + await _tradeDataProvider.AddData(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue() + { + Time = message.Time, + Value = (decimal)pirson, + Figi = message.Figi, + Ticker = message.Ticker, + }); + if (DPirsonValues.TryGetValue(message.Figi, out var olddpirs)) + { + if (olddpirs < -0.3m && res > -0.3m && pricesDiff>0 && (tradesDiff > 0)) { - Time = message.Time, - Value = (decimal)pirson, - }); - var diffs_pirson = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, "diffs_pirson"); - if (diffs_pirson.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Value, true, out var res)) + await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_in", message.Price); + } + if (olddpirs > 0.7m && res < 0.7m) { - if (DPirsonValues.TryGetValue(message.Figi, out var olddpirs)) - { - if (olddpirs < 0.5m && res > 0.5m) - { - await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point0.5", message.Price); - } - } - DPirsonValues[message.Figi] = res; + // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_out", message.Price); + } + if (olddpirs > 0.3m && res < 0.3m && pricesDiff < 0 && (tradesDiff > 0)) + { + await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_in", message.Price); + } + if (olddpirs < -0.7m && res > -0.7m) + { + // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_out", message.Price); } } + DPirsonValues[message.Figi] = res; } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index acf0420..520460e 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -1,14 +1,10 @@ -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; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Declisions; using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums; using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.Exchange.Models.Configs; -using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT; using KLHZ.Trader.Core.Math.Declisions.Services.Cache; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -22,7 +18,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { public class TraderDataProvider { - private readonly ConcurrentDictionary _historyCash = new(); + private readonly ConcurrentDictionary _historyCash3 = new(); private readonly InvestApiClient _investApiClient; private readonly IDbContextFactory _dbContextFactory; @@ -30,7 +26,6 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly string[] _instrumentsFigis = []; public readonly ConcurrentDictionary Orderbooks = new(); - private readonly ConcurrentDictionary _fftResults = new(); private readonly ConcurrentDictionary _tickersCache = new(); private readonly ConcurrentDictionary _assetTypesCache = new(); @@ -48,156 +43,71 @@ namespace KLHZ.Trader.Core.Exchange.Services _isDataRecievingAllowed = options.Value.ExchangeDataRecievingEnabled; } - public ValueTask GetFFtResult(string figi) - { - if (_fftResults.TryGetValue(figi, out var res)) - { - return ValueTask.FromResult(res); - } - return ValueTask.FromResult(FFTAnalyzeResult.Empty); - } - - public ValueTask SetFFtResult(FFTAnalyzeResult result) - { - _fftResults[result.Key] = result; - return ValueTask.CompletedTask; - } - public async ValueTask GetLastPrice(string figi) { var res = 0m; - if (_historyCash.TryGetValue(figi, out var unit)) + if (_historyCash3.TryGetValue(figi, out var unit)) { res = (await unit.GetLastValues()).price; } return res; } - public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(string figi, TimeSpan timeSpan) - { - if (_historyCash.TryGetValue(figi, out var unit)) - { - var res = await unit.GetData(timeSpan); - return (res.timestamps, res.prices, res.isFullIntervalExists); - } - return (Array.Empty(), Array.Empty(), false); - } - - public async ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(string figi, int? length = null) - { - if (_historyCash.TryGetValue(figi, out var unit)) - { - var res = await unit.GetData(length); - return (res.timestamps, res.prices); - } - return (Array.Empty(), Array.Empty()); - } - - public async ValueTask AddData(ITradeDataItem message, TimeSpan? clearingInterval = null) + public async ValueTask AddData(ITradeDataItem message) { if (message.Direction != 1) return; - if (_historyCash.TryGetValue(message.Figi, out var unit)) + if (_historyCash3.TryGetValue(message.Figi, out var unit)) { - if (clearingInterval.HasValue) - { - var lasts = await unit.GetLastValues(); - if (message.Time - lasts.time > clearingInterval.Value) - { - unit = new PriceHistoryCacheUnit2(message.Figi); - _historyCash[message.Figi] = unit; - } - } await unit.AddData(message); } else { - unit = new PriceHistoryCacheUnit2(message.Figi, message); - _historyCash.TryAdd(message.Figi, unit); + unit = new PriceHistoryCacheUnit3(message.Figi, message); + _historyCash3.TryAdd(message.Figi, unit); } } - public async ValueTask AddDataTo1MinuteWindowCache(string figi, string key, CachedValue data) + public async ValueTask AddData(string figi, string key, ITradeDataItem data) { - if (!_historyCash.TryGetValue(figi, out var unit)) + if (!_historyCash3.TryGetValue(figi, out var item)) { - unit = new PriceHistoryCacheUnit2(figi); - _historyCash.TryAdd(figi, unit); + item = new PriceHistoryCacheUnit3(figi); + _historyCash3.TryAdd(figi, item); } - await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute); + + await _historyCash3[figi].AddData(data, key); } - public async ValueTask AddDataTo20SecondsWindowCache(string figi, string key, CachedValue data) + public ValueTask GetDataForTimeWindow(string figi, TimeSpan time, string? key = null, Func? selector = null) { - if (!_historyCash.TryGetValue(figi, out var unit)) + if (_historyCash3.TryGetValue(figi, out var cahcheItem)) { - unit = new PriceHistoryCacheUnit2(figi); - _historyCash.TryAdd(figi, unit); + return cahcheItem.GetData(time, key: key, selector); } - await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._20_Seconds); + return ValueTask.FromResult(Array.Empty()); } - - public async ValueTask AddDataTo5MinuteWindowCache(string figi, string key, CachedValue data) + public ValueTask GetDataFrom20SecondsWindowCache2(string figi, string key) { - if (!_historyCash.TryGetValue(figi, out var unit)) - { - unit = new PriceHistoryCacheUnit2(figi); - _historyCash.TryAdd(figi, unit); - } - await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._5_Minutes); + return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(20), key); } - - public async ValueTask AddDataTo15MinuteWindowCache(string figi, string key, CachedValue data) + public ValueTask GetDataFrom1MinuteWindowCache2(string figi, string key) { - if (!_historyCash.TryGetValue(figi, out var unit)) - { - unit = new PriceHistoryCacheUnit2(figi); - _historyCash.TryAdd(figi, unit); - } - await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._15_Minutes); + return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(60), key); } - - public ValueTask GetDataFrom20SecondsWindowCache(string figi, string key) + public ValueTask GetDataFrom5MinuteWindowCache2(string figi, string key) { - if (_historyCash.TryGetValue(figi, out var cahcheItem)) - { - return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._20_Seconds); - } - return ValueTask.FromResult(Array.Empty()); + return GetDataForTimeWindow(figi, TimeSpan.FromMinutes(5), key); } - - public ValueTask GetDataFrom1MinuteWindowCache(string figi, string key) + public ValueTask GetDataFrom15MinuteWindowCache2(string figi, string key) { - if (_historyCash.TryGetValue(figi, out var cahcheItem)) - { - return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._1_Minute); - } - return ValueTask.FromResult(Array.Empty()); + return GetDataForTimeWindow(figi, TimeSpan.FromMinutes(15), key); } - - public ValueTask GetDataFrom5MinuteWindowCache(string figi, string key) - { - if (_historyCash.TryGetValue(figi, out var cahcheItem)) - { - return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._5_Minutes); - } - return ValueTask.FromResult(Array.Empty()); - } - - public ValueTask GetDataFrom15MinuteWindowCache(string figi, string key) - { - if (_historyCash.TryGetValue(figi, out var cahcheItem)) - { - return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._15_Minutes); - } - return ValueTask.FromResult(Array.Empty()); - } - public async ValueTask AddOrderbook(IOrderbook orderbook) { - if (!_historyCash.TryGetValue(orderbook.Figi, out var unit)) + if (!_historyCash3.TryGetValue(orderbook.Figi, out var unit)) { - unit = new PriceHistoryCacheUnit2(orderbook.Figi); - _historyCash.TryAdd(orderbook.Figi, unit); + unit = new PriceHistoryCacheUnit3(orderbook.Figi); + _historyCash3.TryAdd(orderbook.Figi, unit); } Orderbooks[orderbook.Figi] = orderbook; await unit.AddOrderbook(orderbook);