diff --git a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TimeWindowCacheType.cs b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TimeWindowCacheType.cs index 5a78531..b24c595 100644 --- a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TimeWindowCacheType.cs +++ b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TimeWindowCacheType.cs @@ -3,6 +3,7 @@ public enum TimeWindowCacheType { None = 0, + _20_Seconds = 2001, _1_Minute = 1, _5_Minutes = 2, _15_Minutes = 15, diff --git a/KLHZ.Trader.Core.Math/Declisions/Dtos/TimeWindowCacheItem.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/TimeWindowCacheItem.cs index c1c50a7..e3de050 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Dtos/TimeWindowCacheItem.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/TimeWindowCacheItem.cs @@ -51,6 +51,10 @@ namespace KLHZ.Trader.Core.Math.Declisions.Dtos { return TimeSpan.FromMinutes(15); } + case TimeWindowCacheType._20_Seconds: + { + return TimeSpan.FromSeconds(20); + } default: { return TimeSpan.FromMinutes(1); diff --git a/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit2.cs b/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit2.cs index e9523df..b81fe9b 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit2.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Services/Cache/PriceHistoryCacheUnit2.cs @@ -50,6 +50,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache private readonly object _locker = new(); private readonly decimal[] Prices = new decimal[_arrayMaxLength]; private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength]; + private readonly ConcurrentDictionary _20_secTimeWindows = new(); private readonly ConcurrentDictionary _1_minTimeWindows = new(); private readonly ConcurrentDictionary _5_minTimeWindows = new(); private readonly ConcurrentDictionary _15_minTimeWindows = new(); @@ -93,6 +94,10 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache { return _15_minTimeWindows; } + case TimeWindowCacheType._20_Seconds: + { + return _20_secTimeWindows; + } default: { return _1_minTimeWindows; ; diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index ee7f2e5..d12d7e9 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -144,7 +144,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils s = shift; var i2 = size - 1 - shift; var i1 = size - 2 - shift; - + var debugT = timestamps.Reverse().ToArray(); + var debugV = prices.Reverse().ToArray(); var twavs = CalcTimeWindowAverageValue(timestamps, prices, smallWindow, shift); var twavb = CalcTimeWindowAverageValue(timestamps, prices, bigWindow, shift); pricesForFinalComparison[i2] = prices[prices.Length - 1 - shift]; @@ -182,7 +183,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils // если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2]) { - //if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] >= uptrendEndingDetectionMeanfullStep) + var d = pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]]; + //if (d >= uptrendEndingDetectionMeanfullStep) { res |= TradingEvent.UptrendEnd; } @@ -191,7 +193,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils // если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2]) { - //if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep) + var d = pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]]; + //if (d <= -uptrendStartingDetectionMeanfullStep) { res |= TradingEvent.UptrendStart; } diff --git a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs index afad593..d72102e 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs @@ -156,7 +156,7 @@ namespace KLHZ.Trader.Core.Exchange.Services Count = response.Trade.Quantity, }; - await _tradeDataProvider.AddData(message, TimeSpan.FromHours(7)); + //await _tradeDataProvider.AddData(message, TimeSpan.FromHours(7)); await _eventBus.Broadcast(message); var exchangeState = ExchangeScheduler.GetCurrentState(); diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index b4ada6d..a22de26 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -19,6 +19,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; using System.Collections.Immutable; +using System.Linq; using System.Security.Cryptography; using System.Threading.Channels; using Tinkoff.InvestApi; @@ -106,6 +107,26 @@ namespace KLHZ.Trader.Core.Exchange.Services return data2; } + private async ValueTask<(decimal smallWindow, decimal bigWindow)> GetWindowsSizes(INewPrice message) + { + var fftFull = await _tradeDataProvider.GetFFtResult(message.Figi + "_full"); + if (!fftFull.IsEmpty) + { + var harms = fftFull.Harmonics.Skip(1).Take(fftFull.Harmonics.Length - 3).ToArray(); + var sum = harms.Sum(h => h.Magnitude); + var sumtmp = 0f; + foreach (var h in harms) + { + sumtmp += h.Magnitude; + if (sumtmp / sum > 0.7f) + { + return ((decimal)(h.Period.TotalSeconds / 4), (decimal)(h.Period.TotalSeconds)); + } + } + } + return (30m, 180m); + } + private async ValueTask CheckHarmonicPosition((DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists) data, INewPrice message) { var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow; @@ -114,19 +135,20 @@ namespace KLHZ.Trader.Core.Exchange.Services var fftFull = await _tradeDataProvider.GetFFtResult(message.Figi + "_full"); //var highFreq = await _tradeDataProvider.GetFFtResult(message.Figi + "_high_freq"); //var lowFreq = await _tradeDataProvider.GetFFtResult(message.Figi + "_low_freq"); - var step = message.IsHistoricalData ? 5 : 5; + var step = message.IsHistoricalData ? 40 : 5; if (fft.IsEmpty || (currentTime - fft.LastTime).TotalSeconds > step) { if (data.isFullIntervalExists) { var interpolatedData = SignalProcessing.InterpolateData(data.timestamps, data.prices, TimeSpan.FromSeconds(5)); - fftFull = FFT.Analyze(interpolatedData.timestamps, interpolatedData.values, message.Figi+"_full", TimeSpan.FromSeconds(120), TimeSpan.FromHours(24)); - fft = FFT.ReAnalyze(fftFull, message.Figi, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(40)); + fftFull = FFT.Analyze(interpolatedData.timestamps, interpolatedData.values, message.Figi+"_full", TimeSpan.FromSeconds(30), TimeSpan.FromHours(24)); + + fft = FFT.ReAnalyze(fftFull, message.Figi, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(60)); //highFreq = FFT.ReAnalyze(fftFull, message.Figi + "_low_freq", TimeSpan.FromMinutes(20), TimeSpan.FromMinutes(60)); //lowFreq = FFT.ReAnalyze(fftFull, message.Figi + "_high_freq", TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(20)); await _tradeDataProvider.SetFFtResult(fft); - //await _tradeDataProvider.SetFFtResult(fftFull); + await _tradeDataProvider.SetFFtResult(fftFull); //await _tradeDataProvider.SetFFtResult(lowFreq); //await _tradeDataProvider.SetFFtResult(highFreq); @@ -237,13 +259,9 @@ namespace KLHZ.Trader.Core.Exchange.Services var changeMods = GetInitDict(0); try { - if (message.IsHistoricalData) - { - await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6)); - } #region Ускорение обработки исторических данных при отладке if (message.Direction == 1) - { + { if (!pricesCache1.TryGetValue(message.Figi, out var list)) { list = new List(); @@ -273,6 +291,7 @@ namespace KLHZ.Trader.Core.Exchange.Services } if (message.Direction == 2) { + if (!pricesCache2.TryGetValue(message.Figi, out var list)) { list = new List(); @@ -301,6 +320,9 @@ namespace KLHZ.Trader.Core.Exchange.Services } } + + + #endregion #region Подсчёт торгового баланса по сберу и IMOEXF @@ -308,6 +330,11 @@ namespace KLHZ.Trader.Core.Exchange.Services { if (message.Direction == 1) { + await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "1", new Contracts.Declisions.Dtos.CachedValue() + { + Time = message.Time, + Value = message.Count + }); await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue() { Time = message.Time, @@ -331,7 +358,76 @@ namespace KLHZ.Trader.Core.Exchange.Services Time = message.Time, Value = (decimal)message.Count }); + await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "2", new Contracts.Declisions.Dtos.CachedValue() + { + Time = message.Time, + Value = message.Count + }); } + + var buys = await _tradeDataProvider.GetDataFrom20SecondsWindowCache(message.Figi, "1"); + var sells = await _tradeDataProvider.GetDataFrom20SecondsWindowCache(message.Figi, "2"); + + + var buysSpeed = buys.Sum(p => p.Value) / 20; + var sellsSpeed = sells.Sum(p => p.Value) / 20; + + var orderBook = _tradeDataProvider.Orderbooks[message.Figi]; + if (orderBook.Asks.Length>3 && orderBook.Bids.Length>3) + { + var asks = (decimal)(orderBook.Asks[0].Count + orderBook.Asks[1].Count + orderBook.Asks[2].Count); + //var asks = (decimal)(orderBook.Asks[0].Count + orderBook.Asks[1].Count + orderBook.Asks[2].Count + orderBook.Asks[3].Count); + var bids = (decimal)(orderBook.Bids[0].Count + orderBook.Bids[1].Count + orderBook.Bids[2].Count); + //var bids = (decimal)(orderBook.Bids[0].Count + orderBook.Bids[1].Count + orderBook.Bids[2].Count + orderBook.Bids[3].Count); + + if (buysSpeed > 0 && sellsSpeed > 0) + { + await LogPrice(message, "speed_relation", (sellsSpeed / (sellsSpeed + buysSpeed))); + } + + //var diff = buysSpeed - sellsSpeed; + //await LogPrice(message, "speed_diff", diff); + //await LogPrice(message, "stabling", (asks+bids)/(sellsSpeed+buysSpeed)); + + //if (buysSpeed > 0) + //{ + // var asksLifetime = asks / buysSpeed; + // if (asksLifetime > 600) asksLifetime = 600; + // var asksLifetime2 = diff > 0?System.Math.Abs( asks / diff):0; + // await LogPrice(message, "asks_lifetime", asksLifetime); + // await LogPrice(message, "asks_lifetime2", asksLifetime2); + // await LogPrice(message, "asks_lifetime2", asksLifetime2); + // await LogPrice(message, "buys_speed", buysSpeed); + //} + + + //if (sellsSpeed > 0) + //{ + // var bidsLifetime = bids / sellsSpeed; + // if (bidsLifetime > 600) bidsLifetime = 600; + // var bidsLifetime2 = diff < 0 ? System.Math.Abs(bids / diff) : 0; + // await LogPrice(message, "bids_lifetime", bidsLifetime); + // await LogPrice(message, "bids_lifetime2", System.Math.Abs(bidsLifetime2)); + // await LogPrice(message, "sells_speed", sellsSpeed); + //} + + + var buys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey); + var sells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey); + var buysSpeed5min = buys5min.Sum(p => p.Value) / 300; + var sellsSpeed5min = sells5min.Sum(p => p.Value) / 300; + var diff5min = buysSpeed5min - sellsSpeed5min; + await LogPrice(message, "speed_diff_5min", diff5min); + + //var buys1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey); + //var sells1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache(message.Figi, Constants._1minSellCacheKey); + //var buysSpeed1min = buys1min.Sum(p => p.Value) / 60; + //var sellsSpeed1min = sells1min.Sum(p => p.Value) / 60; + //var diff1min = buysSpeed1min - sellsSpeed1min; + //await LogPrice(message, "speed_diff_1min", diff1min); + } + + } #endregion if (_tradingInstrumentsFigis.Contains(message.Figi) && message.Figi == "FUTIMOEXF000" && message.Direction == 1) @@ -413,6 +509,7 @@ namespace KLHZ.Trader.Core.Exchange.Services try { if (message.Direction != 1) continue; + await _tradeDataProvider.AddData(message); ProcessStops(message, currentTime); var windowMaxSize = 2000; var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize); @@ -433,6 +530,35 @@ namespace KLHZ.Trader.Core.Exchange.Services } } + private async Task> GetSpeedResultantMods(INewPrice message) + { + var res = GetInitDict(1); + var buys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey); + var sells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey); + var buysSpeed5min = buys5min.Sum(p => p.Value) / 300; + var sellsSpeed5min = sells5min.Sum(p => p.Value) / 300; + var diff5min = buysSpeed5min - sellsSpeed5min; + await LogPrice(message, "speed_diff_5min", diff5min); + + if (diff5min < 0) + { + res[TradingEvent.UptrendStart] = Constants.BlockingCoefficient; + } + if (diff5min > 0) + { + res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient; + } + if (diff5min > 6) + { + res[TradingEvent.UptrendEnd] = Constants.BlockingCoefficient; + } + if (diff5min < -6) + { + res[TradingEvent.DowntrendEnd] = Constants.BlockingCoefficient; + } + return res.ToImmutableDictionary(); + } + private async Task> GetWindowAverageStartData((DateTime[] timestamps, decimal[] prices) data, int smallWindow, int bigWindow, INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) { @@ -570,33 +696,34 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt { return; } + + var windows = await GetWindowsSizes(message); //var resTask1 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, -2m, 2m,3); - var resTask1 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, 0m, 0.5m); - //var resTask3 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, 0, 0,0.7m); + var resTask1 = GetWindowAverageStartData(data, (int)windows.smallWindow, (int)windows.bigWindow, message, windowMaxSize, -0.5m, 0.5m); + ////var resTask3 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, 0, 0,0.7m); var getFFTModsTask = GetFFTMods(message); var getLocalTrendsModsTask = GetLocalTrendsMods(data, message); - //var getAreasModsTask = GetAreasMods(data, message); var getSellsDiffsModsTask = GetSellsDiffsMods(message); var getTradingModeModsTask = GetTradingModeMods(message); + var getSpeedResultantModsTask = GetSpeedResultantMods(message); - await Task.WhenAll(resTask1, getFFTModsTask, getSellsDiffsModsTask, getTradingModeModsTask, getLocalTrendsModsTask); + await Task.WhenAll(resTask1, getFFTModsTask, getSellsDiffsModsTask, getTradingModeModsTask, getLocalTrendsModsTask, getSpeedResultantModsTask); //var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); //if (resTask1.Result[TradingEvent.UptrendStart] >= 1) //{ //} - var result = getLocalTrendsModsTask.Result; + var result = resTask1.Result; //result = MergeResults(result, resTask2.Result.ToImmutableDictionary()); //result = MergeResults(result, resTask3.Result.ToImmutableDictionary()); //result = MergeResultsMax(result, changeModeData); //result = MergeResultsMax(result, getLocalTrendsModsTask.Result); //result = MergeResultsMult(result, getFFTModsTask.Result); - ////////result = MergeResults(result, getAreasModsTask.Result); //result = MergeResultsMult(result, getSellsDiffsModsTask.Result); - //result = MergeResultsMult(result, getTradingModeModsTask.Result); + //result = MergeResultsMult(result, getSpeedResultantModsTask.Result); if (result[TradingEvent.UptrendStart] >= Constants.UppingCoefficient && !LongOpeningStops.ContainsKey(message.Figi) @@ -614,12 +741,11 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1); LongOpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1); } - - await LogDeclision(DeclisionTradeAction.OpenLong, message); + await LogDeclision(DeclisionTradeAction.OpenLong, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); await LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Value + stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Value - stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } - if (result[TradingEvent.DowntrendStart] > Constants.UppingCoefficient + if (result[TradingEvent.DowntrendStart] >= Constants.UppingCoefficient && !ShortOpeningStops.ContainsKey(message.Figi) && state == ExchangeState.Open ) @@ -636,11 +762,11 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt ShortOpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1); } - await LogDeclision(DeclisionTradeAction.OpenShort, message); + await LogDeclision(DeclisionTradeAction.OpenShort, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); await LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Value - stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Value + stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } - if (result[TradingEvent.UptrendEnd] > Constants.UppingCoefficient) + if (result[TradingEvent.UptrendEnd] >= Constants.UppingCoefficient) { if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) { @@ -650,10 +776,11 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt .ToArray(); await ClosePositions(assetsForClose, message); } - await LogDeclision(DeclisionTradeAction.CloseLong, message); + await LogDeclision(DeclisionTradeAction.CloseLong, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); + } - if (result[TradingEvent.DowntrendEnd] > Constants.UppingCoefficient) + if (result[TradingEvent.DowntrendEnd] >= Constants.UppingCoefficient) { if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { @@ -663,7 +790,8 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt .ToArray(); await ClosePositions(assetsForClose, message); } - await LogDeclision(DeclisionTradeAction.CloseShort, message); + await LogDeclision(DeclisionTradeAction.CloseShort, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); + } } @@ -773,19 +901,9 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt private async Task CalcTradingMode(string figi) { var res = TradingMode.None; - var largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(90)); - var smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(15)); + var largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(45)); + var smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(10)); - if (!largeData.isFullIntervalExists && smallData.isFullIntervalExists) - { - largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(45)); - smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(10)); - } - if (!largeData.isFullIntervalExists && smallData.isFullIntervalExists) - { - largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(15)); - smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(7)); - } if (largeData.isFullIntervalExists && smallData.isFullIntervalExists) { if (LocalTrends.TryCalcTrendDiff(largeData.timestamps, largeData.prices, out var largeDataRes) @@ -879,14 +997,14 @@ INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullSt //res[TradingEvent.UptrendStart] = Constants.UppingCoefficient; res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; //res[TradingEvent.UptrendEnd] = Constants.LowingCoefficient; - res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.LowingCoefficient; } if (position == ValueAmplitudePosition.UpperThen30Decil) { - res[TradingEvent.UptrendStart] = Constants.BlockingCoefficient; + res[TradingEvent.UptrendStart] = Constants.LowingCoefficient; //res[TradingEvent.DowntrendEnd] = Constants.LowingCoefficient; //res[TradingEvent.UptrendEnd] = Constants.UppingCoefficient; - res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; } return res.ToImmutableDictionary(); } diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index dab729e..69a437c 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -29,6 +29,7 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly ILogger _logger; private readonly string[] _instrumentsFigis = []; + public readonly ConcurrentDictionary Orderbooks = new(); private readonly ConcurrentDictionary _fftResults = new(); private readonly ConcurrentDictionary _tickersCache = new(); private readonly ConcurrentDictionary _assetTypesCache = new(); @@ -125,6 +126,16 @@ namespace KLHZ.Trader.Core.Exchange.Services await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute); } + public async ValueTask AddDataTo20SecondsWindowCache(string figi, string key, CachedValue data) + { + if (!_historyCash.TryGetValue(figi, out var unit)) + { + unit = new PriceHistoryCacheUnit2(figi); + _historyCash.TryAdd(figi, unit); + } + await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._20_Seconds); + } + public async ValueTask AddDataTo5MinuteWindowCache(string figi, string key, CachedValue data) { if (!_historyCash.TryGetValue(figi, out var unit)) @@ -135,6 +146,15 @@ namespace KLHZ.Trader.Core.Exchange.Services await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._5_Minutes); } + public ValueTask GetDataFrom20SecondsWindowCache(string figi, string key) + { + if (_historyCash.TryGetValue(figi, out var cahcheItem)) + { + return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._20_Seconds); + } + return ValueTask.FromResult(Array.Empty()); + } + public ValueTask GetDataFrom1MinuteWindowCache(string figi, string key) { if (_historyCash.TryGetValue(figi, out var cahcheItem)) @@ -160,7 +180,7 @@ namespace KLHZ.Trader.Core.Exchange.Services unit = new PriceHistoryCacheUnit2(orderbook.Figi); _historyCash.TryAdd(orderbook.Figi, unit); } - + Orderbooks[orderbook.Figi] = orderbook; await unit.AddOrderbook(orderbook); } diff --git a/KLHZ.Trader.Core/Exchange/Utils/ExchangeScheduler.cs b/KLHZ.Trader.Core/Exchange/Utils/ExchangeScheduler.cs index 8efe354..6433efd 100644 --- a/KLHZ.Trader.Core/Exchange/Utils/ExchangeScheduler.cs +++ b/KLHZ.Trader.Core/Exchange/Utils/ExchangeScheduler.cs @@ -4,7 +4,7 @@ namespace KLHZ.Trader.Core.Exchange.Utils { internal static class ExchangeScheduler { - private readonly static TimeOnly _openTimeMain = new(6, 10); + private readonly static TimeOnly _openTimeMain = new(6, 0); private readonly static TimeOnly _closeTimeMain = new(20, 45); private readonly static TimeOnly _openTimeHoliday = new(7, 10); diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index acf2fc9..12d54df 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -1,12 +1,17 @@ +using Grpc.Core; using KLHZ.Trader.Core.Contracts.Messaging.Dtos; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Orders; using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.Exchange.Services; using KLHZ.Trader.Core.Math.Declisions.Utils; +using KLHZ.Trader.Service.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Telegram.Bot.Types; +using Tinkoff.InvestApi.V1; namespace KLHZ.Trader.Service.Controllers { @@ -39,26 +44,184 @@ namespace KLHZ.Trader.Service.Controllers //var time2 = DateTime.UtcNow.AddMinutes(18); using var context1 = await _dbContextFactory.CreateDbContextAsync(); context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - var data = await context1.PriceChanges - .Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1) - .OrderBy(c => c.Time) - .Select(c => new NewPriceMessage() - { - Figi = c.Figi, - Ticker = c.Ticker, - Time = c.Time, - Value = c.Value, - IsHistoricalData = true, - Direction = c.Direction, - Count = c.Count, - }) - .ToArrayAsync(); - - foreach (var mess in data) + + while (time1 < DateTime.UtcNow) { - await _dataBus.Broadcast(mess); + var data = new List(); + var data2 = new List(); + var time2 = time1.AddHours(1); + var orderbooks = await context1.OrderbookItems + .Where(oi => (oi.Figi == figi1 || oi.Figi == figi2) && oi.Time >= time1 && oi.Time c.Time) + .ToArrayAsync(); + var prices = await context1.PriceChanges + .Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1 && c.Time < time2) + .OrderBy(c => c.Time) + .Select(c => new NewPriceMessage() + { + Figi = c.Figi, + Ticker = c.Ticker, + Time = c.Time, + Value = c.Value, + IsHistoricalData = true, + Direction = c.Direction, + Count = c.Count, + }) + .ToArrayAsync(); + + var lookupImoexf = orderbooks.Where(o=>o.Figi== "FUTIMOEXF000").ToLookup(o => o.Time); + var lookupSber = orderbooks.Where(o=>o.Figi== "BBG004730N88").ToLookup(o => o.Time); + + foreach (var item in lookupImoexf) + { + + var asks = item + .Where(i => i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.Ask) + .OrderBy(o => o.Price) + .ToList(); + var bids = item + .Where(i => i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.Bid) + .OrderByDescending(o => o.Price) + .ToList(); + + var forRemove = new List(); + if (asks.Count > 4 || bids.Count > 4) + { + + } + foreach (var bid in bids) + { + var bids2 = bids.Where(b=>b.Price==bid.Price).ToList(); + var summCount = bids2.Sum(b => b.Count); + var b = bids2.First(); + b.Count = summCount; + bids2.Remove(b); + forRemove.AddRange(bids2); + } + + foreach (var ask in asks) + { + var asks2 = asks.Where(b => b.Price == ask.Price).ToList(); + var summCount = asks2.Sum(b => b.Count); + var b = asks2.First(); + b.Count = summCount; + asks2.Remove(b); + forRemove.AddRange(asks2); + } + + asks.RemoveAll(a => forRemove.Contains(a)); + bids.RemoveAll(a => forRemove.Contains(a)); + var orderbook = new NewOrderbookMessage() { Figi = "FUTIMOEXF000", Ticker = "IMOEXF", Asks = asks.ToArray(), Bids = bids.ToArray(), AsksCount = asks.Sum(a => a.Count), BidsCount = bids.Sum(a => a.Count), Time = item.Key }; + var wrapper = new TimeSeriesData() + { + Figi = orderbook.Figi, + Orderbook = orderbook, + Time = item.Key, + }; + data.Add(wrapper); + } + + foreach (var item in lookupSber) + { + var asks = item + .Where(i => i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.Ask) + .OrderBy(o => o.Price) + .ToList(); + var bids = item + .Where(i => i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.Bid) + .OrderByDescending(o => o.Price) + .ToList(); + var forRemove = new List(); + if (asks.Count > 4 || bids.Count > 4) + { + + } + foreach (var bid in bids) + { + var bids2 = bids.Where(b => b.Price == bid.Price).ToList(); + var summCount = bids2.Sum(b => b.Count); + var b = bids2.First(); + b.Count = summCount; + bids2.Remove(b); + forRemove.AddRange(bids2); + } + + foreach (var ask in asks) + { + var asks2 = asks.Where(b => b.Price == ask.Price).ToList(); + var summCount = asks2.Sum(b => b.Count); + var b = asks2.First(); + b.Count = summCount; + asks2.Remove(b); + forRemove.AddRange(asks2); + } + asks.RemoveAll(a => forRemove.Contains(a)); + bids.RemoveAll(a => forRemove.Contains(a)); + var orderbook = new NewOrderbookMessage() { Figi = "BBG004730N88", Ticker = "SBER", Asks = asks.ToArray(), Bids = bids.ToArray(), AsksCount = asks.Sum(a => a.Count), BidsCount = bids.Sum(a => a.Count), Time = item.Key }; + var wrapper = new TimeSeriesData() + { + Figi = orderbook.Figi, + Orderbook = orderbook, + Time = item.Key, + }; + data.Add(wrapper); + } + time1 = time2; + + foreach (var price in prices) + { + var wrapper = new TimeSeriesData() + { + Figi = price.Figi, + NewPrice = price, + Time = price.Time, + }; + data.Add(wrapper); + } + data = data.OrderBy(d => d.Time).ToList(); + for(int i = 0; i < data.Count; i++) + { + if (data[i].NewPrice != null && i>0) + { + for (int i1=i-1; i1 >=0; i1--) + { + var ob = data[i1].Orderbook; + if (data[i1].Figi == data[i].Figi && ob != null) + { + var d =new TimeSeriesData() + { + Figi = ob.Figi, + Orderbook = ob, + Time = data[i].Time, + NewPrice = data[i].NewPrice, + }; + data2.Add(d); + break; + } + } + } + } + + foreach (var mess in data2) + { + if (mess.Orderbook != null) + { + await _traderDataProvider.AddOrderbook(mess.Orderbook); + } + if (mess.NewPrice != null) + { + //await _traderDataProvider.AddData(mess.NewPrice, TimeSpan.FromHours(6)); + await _dataBus.Broadcast(mess.NewPrice); + } + } + data.Clear(); + data2.Clear(); } + + + + } catch (Exception ex) { diff --git a/KLHZ.Trader.Service/Models/TimeSeriesData.cs b/KLHZ.Trader.Service/Models/TimeSeriesData.cs new file mode 100644 index 0000000..7d86545 --- /dev/null +++ b/KLHZ.Trader.Service/Models/TimeSeriesData.cs @@ -0,0 +1,12 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; + +namespace KLHZ.Trader.Service.Models +{ + public class TimeSeriesData + { + public required string Figi { get; set;} + public DateTime Time { get; set; } + public INewPrice? NewPrice { get; set; } + public IOrderbook? Orderbook { get; set; } + } +}