From 167c2ba119e95cd3a0962859c0a483a5933e2743 Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Wed, 8 Oct 2025 15:45:18 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D1=83=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=B5=D0=B9=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Declisions/Dtos/ConvolutionResult.cs | 16 +++ .../Declisions/Dtos/HistItem.cs | 14 ++ .../Declisions/Utils/SignalProcessing.cs | 102 ++++++++++++++- .../Declisions/Utils/Statistics.cs | 87 +++++++++++++ .../SignalProcessingTests.cs | 29 ++++- KLHZ.Trader.Core.Tests/StatisticTests.cs | 27 +++- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 2 +- .../Controllers/PlayController.cs | 123 ++++++++++++++++++ 8 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 KLHZ.Trader.Core.Math/Declisions/Dtos/ConvolutionResult.cs create mode 100644 KLHZ.Trader.Core.Math/Declisions/Dtos/HistItem.cs diff --git a/KLHZ.Trader.Core.Math/Declisions/Dtos/ConvolutionResult.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/ConvolutionResult.cs new file mode 100644 index 0000000..176b3d4 --- /dev/null +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/ConvolutionResult.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KLHZ.Trader.Core.Math.Declisions.Dtos +{ + public class ConvolutionResult + { + public decimal Sum { get; set; } + public decimal Value { get;set; } + public int Shift { get; set; } + public decimal Leverage { get; set; } + } +} diff --git a/KLHZ.Trader.Core.Math/Declisions/Dtos/HistItem.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/HistItem.cs new file mode 100644 index 0000000..db2289b --- /dev/null +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/HistItem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KLHZ.Trader.Core.Math.Declisions.Dtos +{ + public class HistItem + { + public decimal Value { get; set; } + public decimal Count { get; set; } + } +} diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/SignalProcessing.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/SignalProcessing.cs index c4cc4bc..26066e8 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/SignalProcessing.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/SignalProcessing.cs @@ -1,4 +1,7 @@ -namespace KLHZ.Trader.Core.Math.Declisions.Utils +using KLHZ.Trader.Core.Contracts.Messaging.Dtos; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; + +namespace KLHZ.Trader.Core.Math.Declisions.Utils { public static class SignalProcessing { @@ -54,6 +57,103 @@ return (res2.ToArray(), res.ToArray()); } + public static ITradeDataItem[] InterpolateData(ITradeDataItem[] items, TimeSpan timeStep) + { + var result = new List(); + + var firstItem = items[0]; + var startTime = new DateTime(firstItem.Time.Year, firstItem.Time.Month, firstItem.Time.Day, 0, 0, 0, DateTimeKind.Utc); + var dt = items[0].Time - startTime; + + var totalSteps = System.Math.Ceiling((items[items.Length - 1].Time - firstItem.Time).TotalSeconds / timeStep.TotalSeconds); + var deltaSeconds = System.Math.Floor(dt.TotalSeconds / timeStep.TotalSeconds); + startTime = startTime.AddSeconds(deltaSeconds * timeStep.TotalSeconds); + + var firstBound = startTime; + var secondBound = startTime + timeStep; + var boundD1 = 0; + var boundD2 = 0; + for (int i = 0; i < totalSteps; i++) + { + var countD1 = 0; + var sumD1 = 0m; + var cD1 = 0L; + + var countD2 = 0; + var sumD2 = 0m; + var cD2 = 0L; + + for (int i1 = boundD1; i1 < items.Length; i1++) + { + if (items[i1].Direction == 1) + { + if (items[i1].Time > firstBound && items[i1].Time <= secondBound) + { + countD1++; + sumD1 += items[i1].Price; + cD1 += items[i1].Count; + } + else if (countD1 != 0) + { + boundD1 = i1; + break; + } + } + } + + for (int i1 = boundD2; i1 < items.Length; i1++) + { + if (items[i1].Direction == 2) + { + if (items[i1].Time > firstBound && items[i1].Time <= secondBound) + { + countD2++; + sumD2 += items[i1].Price; + cD2 += items[i1].Count; + } + else if (countD2 != 0) + { + boundD2 = i1; + break; + } + } + } + + if (countD1 != 0) + { + result.Add(new TradeDataItem() + { + Figi = firstItem.Figi, + Ticker = firstItem.Ticker, + Price = sumD1 / countD1, + Time = secondBound, + IsHistoricalData = firstItem.IsHistoricalData, + Direction = 1, + Count = cD1 + }); + } + + if (countD2 != 0) + { + result.Add(new TradeDataItem() + { + Figi = firstItem.Figi, + Ticker = firstItem.Ticker, + Price = sumD2 / countD2, + Time = secondBound, + IsHistoricalData = firstItem.IsHistoricalData, + Direction = 2, + Count = cD2 + }); + } + + firstBound += timeStep; + secondBound += timeStep; + } + + + return result.OrderBy(r => r.Time).ToArray(); + } public static decimal[] CalcDiffs(decimal[] values) { diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs index c16d500..f0068df 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs @@ -1,4 +1,5 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; +using KLHZ.Trader.Core.Math.Declisions.Dtos; namespace KLHZ.Trader.Core.Math.Declisions.Utils { @@ -128,5 +129,91 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } return false; } + + public static HistItem[] CalcHistogram(ITradeDataItem[] values) + { + var result = new Dictionary(); + foreach(var item in values) + { + if (!result.TryGetValue(item.Price,out var val)) + { + result[item.Price] = 0; + } + result[item.Price] = val + 1; + } + return result.Select(r => new HistItem() { Value = r.Key, Count = r.Value }).OrderBy(i=>i.Value).ToArray(); + } + + public static decimal[] GetParabolaCore(int leverageSize, decimal maxValue = 1) + { + var d = new double[2 * leverageSize + 1]; + for (int i = 0; i < d.Length; i++) + { + d[i] = -System.Math.Pow((i - leverageSize), 2); + } + + var min = d.Min(); + var amin = System.Math.Abs(min); + for (int i = 0; i < d.Length; i++) + { + d[i] = (d[i] - min) / amin * (double)maxValue; + } + return d.Select(i => (decimal)i).ToArray(); + } + + public static ConvolutionResult[] CalcConvolution(HistItem[] hist, int leverageSize) + { + if (hist.Length>2* leverageSize+1) + { + var results = new List(); + var coreSize = 2 * leverageSize + 1; + for (int shift = 0; shift < hist.Length - coreSize; shift++) + { + var s = 0m; + var k = 0; + for (int i = 0; i < 2 * leverageSize + 1; i++) + { + var core = GetParabolaCore(leverageSize, hist[i + shift].Count); + s += (hist[i + shift].Count - core[i])*(hist[i + shift].Count - core[i]); + if (i== leverageSize) + { + k = i + shift; + } + } + s = s / coreSize; + s = (decimal)System.Math.Pow((double)s, 0.5d); + //s /= coreSum; + results.Add(new ConvolutionResult() + { + Leverage = leverageSize, + Sum = s, + Value = hist[k].Value, + Shift = k, + }); + } + return results.OrderByDescending(r=>r.Value).ToArray(); + } + return Array.Empty(); + } + + public static void MergeConvolutionResults(List results, List mergedResults) + { + if (results.Count == 0) + { + return; + } + var resultsOrdered = results.OrderBy(r => r.Sum).ToList(); + var res = resultsOrdered[0]; + var b1 = res.Shift - res.Leverage; + var b2 = res.Shift + res.Leverage; + var forMerge = results.Where(r => r.Shift >= b1 && r.Shift <= b2).ToList(); + res.Sum = forMerge.Sum(r => r.Sum); + foreach (var m in forMerge) + { + results.Remove(m); + } + mergedResults.Add(res); + MergeConvolutionResults(results, mergedResults); + } } } diff --git a/KLHZ.Trader.Core.Tests/SignalProcessingTests.cs b/KLHZ.Trader.Core.Tests/SignalProcessingTests.cs index e29191c..1a2712f 100644 --- a/KLHZ.Trader.Core.Tests/SignalProcessingTests.cs +++ b/KLHZ.Trader.Core.Tests/SignalProcessingTests.cs @@ -1,4 +1,6 @@ -using KLHZ.Trader.Core.Math.Declisions.Utils; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; +using KLHZ.Trader.Core.Math.Declisions.Utils; using System.Security.Cryptography; namespace KLHZ.Trader.Core.Tests @@ -38,5 +40,30 @@ namespace KLHZ.Trader.Core.Tests Assert.IsTrue(r == 1); } } + + [Test] + public static void Test3() + { + var results = new List(); + var times = new List(); + var startDt = DateTime.UtcNow; + for (int i = 0; i < 100; i++) + { + startDt = startDt.AddSeconds(((double)(RandomNumberGenerator.GetInt32(1, 100))) / 100); + var t = new TradeDataItem() + { + Figi = "", + Ticker = "", + Time = startDt, + Count = 1, + Direction = RandomNumberGenerator.GetInt32(1, 3), + IsHistoricalData = true, + Price = (decimal)System.Math.Sin(0.01 * i) + (decimal)System.Math.Cos(0.01 * i), + }; + results.Add(t); + } + + var res = SignalProcessing.InterpolateData(results.ToArray(), TimeSpan.FromSeconds(5)); + } } } diff --git a/KLHZ.Trader.Core.Tests/StatisticTests.cs b/KLHZ.Trader.Core.Tests/StatisticTests.cs index 59029ff..6f32e4d 100644 --- a/KLHZ.Trader.Core.Tests/StatisticTests.cs +++ b/KLHZ.Trader.Core.Tests/StatisticTests.cs @@ -1,4 +1,5 @@ -using KLHZ.Trader.Core.Math.Declisions.Utils; +using KLHZ.Trader.Core.Contracts.Declisions.Dtos; +using KLHZ.Trader.Core.Math.Declisions.Utils; using System.Security.Cryptography; namespace KLHZ.Trader.Core.Tests @@ -6,7 +7,7 @@ namespace KLHZ.Trader.Core.Tests internal class StatisticTests { [Test] - public static void Test() + public static void Test1() { var data = new decimal[1000]; for (int i = 0; i < data.Length; i++) @@ -21,5 +22,27 @@ namespace KLHZ.Trader.Core.Tests Assert.IsTrue(data.Length != res.Length); Assert.IsTrue(res[0] != 1000); } + + [Test] + public static void Test2() + { + var data = new decimal[1000]; + for (int i = 0; i < data.Length; i++) + { + data[i] = RandomNumberGenerator.GetInt32(-10, 10); + } + + + var res = Statistics.CalcHistogram(data.Select(d=>new CachedValue() + { + Figi ="", + Ticker = "", + Direction =1, + Price = d/2, + Time = DateTime.UtcNow, + }).ToArray()); + + Statistics.CalcConvolution(res, 5); + } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 1195ff8..aace132 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -131,7 +131,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var changeMods = TraderUtils.GetInitDict(0); try { - message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2); + //message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2); #region Добавление данных в кеши. if (message.Figi == "BBG004730N88" || message.Figi == "FUTIMOEXF000") diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index c58fbe5..4ce204c 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -2,9 +2,12 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Orders; +using KLHZ.Trader.Core.Math.Declisions.Dtos; +using KLHZ.Trader.Core.Math.Declisions.Utils; using KLHZ.Trader.Service.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System.Linq; namespace KLHZ.Trader.Service.Controllers { @@ -234,6 +237,126 @@ namespace KLHZ.Trader.Service.Controllers } } + [HttpGet] + public async Task RunConvolution(double? shift = null) + { + var timeStep = 30; + var time = DateTime.UtcNow.AddDays(-shift ?? -7).Date; + while (time < DateTime.UtcNow) + { + var forSave = new List(); + time = time.AddMinutes(timeStep); + var figi1 = "FUTIMOEXF000"; + //var figi1 = "BBG004730N88"; + var figi2 = "FUTIMOEXF000"; + //var figi2 = "FUTIMOEXF000"; + var time2 = time; + //var time1 = new DateTime(2025, 9, 24, 11, 00, 0, DateTimeKind.Utc); + //var time2 = DateTime.UtcNow.AddMinutes(18); + using var context1 = await _dbContextFactory.CreateDbContextAsync(); + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var time1 = time2.AddHours(-3); + var prices = await context1.PriceChanges + .Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1 && c.Time < time2 && c.Direction == 1) + .OrderBy(c => c.Time) + .Select(c => new TradeDataItem() + { + Figi = c.Figi, + Ticker = c.Ticker, + Time = c.Time, + Price = c.Price, + IsHistoricalData = true, + Direction = c.Direction, + Count = c.Count, + }) + .ToArrayAsync(); + var pricesToSave = prices.Where(p => p.Time < time && p.Time >= time.AddMinutes(-timeStep)).ToArray(); + if (pricesToSave.Any()) + { + var p1 = pricesToSave.Last(); + forSave.Add(new Core.DataLayer.Entities.Prices.ProcessedPrice() + { + Figi = p1.Figi, + Processor = "support_level_calc", + Ticker = p1.Ticker, + Count = p1.Count, + Direction = p1.Direction, + Time = p1.Time, + Price = p1.Price, + }); + var leverage = 3; + var hist = Statistics.CalcHistogram(prices); + var convs = Statistics.CalcConvolution(hist, leverage).ToList(); + //var result = new List(); + //Statistics.MergeConvolutionResults(convs, result); + var orderedConvs = convs.OrderByDescending(c => c.Sum).Take(5).ToList(); + orderedConvs = orderedConvs.OrderBy(c => c.Value).ToList(); + var values = new List(); + var shifts = new List(); + + foreach (var c in orderedConvs) + { + var left = c.Value - 0.5m * (leverage - 1); + var right = c.Value + 0.5m * (leverage + 1); + + if (values.Count > 0) + { + var last = values.Last(); + if (last < left) + { + values.Add(left); + values.Add(right); + } + else if (last >= left && last < right) + { + values.Remove(last); + values.Add(right); + } + else if (last >= right) + { + + } + } + else + { + values.Add(left); + values.Add(right); + } + } + + var pairs = new List<(decimal left, decimal right)>(); + for (int i = 0; i < values.Count - 1; i += 2) + { + pairs.Add((values[i], values[i + 1])); + } + + foreach (var price in pricesToSave) + { + foreach (var p in pairs) + { + if (price.Price >= p.left && price.Price <= p.right) + { + forSave.Add(new Core.DataLayer.Entities.Prices.ProcessedPrice() + { + Figi = price.Figi, + Processor = "support_level", + Ticker = price.Ticker, + Count = price.Count, + Direction = price.Direction, + Time = price.Time, + Price = price.Price, + }); + } + } + } + + await context1.ProcessedPrices.AddRangeAsync(forSave); + await context1.SaveChangesAsync(); + } + } + } + } }