фиксация первой реализации уровней поддержки

dev
vlad zverzhkhovskiy 2025-10-08 15:45:18 +03:00
parent e57d67d5db
commit 167c2ba119
8 changed files with 395 additions and 5 deletions

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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<ITradeDataItem>();
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)
{

View File

@ -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<decimal, decimal>();
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<ConvolutionResult>();
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<ConvolutionResult>();
}
public static void MergeConvolutionResults(List<ConvolutionResult> results, List<ConvolutionResult> 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);
}
}
}

View File

@ -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<ITradeDataItem>();
var times = new List<DateTime>();
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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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")

View File

@ -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<Core.DataLayer.Entities.Prices.ProcessedPrice>();
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<ConvolutionResult>();
//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<decimal>();
var shifts = new List<decimal>();
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();
}
}
}
}
}