From cee02fe66557784d85ecb84252b5ebce767ba2f3 Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Mon, 1 Sep 2025 14:58:43 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20+=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C=20=D1=81=D0=B4=D0=B5=D0=BB=D0=BE=D0=BA=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces/IPriceHistoryCacheUnit.cs | 3 +- .../{INewPriceMessage.cs => INewPrice.cs} | 2 +- .../Dtos/Intarfaces/IProcessedPrice.cs | 7 + .../Messaging/Dtos/NewPriceMessage.cs | 2 +- .../Messaging/Interfaces/IDataBus.cs | 6 +- .../Declisions/Dtos/ProcessedPrice.cs | 14 ++ .../Dtos/Services/KalmanPredictor.cs | 191 ++++++++++++++++++ .../Dtos}/Services/PriceHistoryCacheUnit.cs | 33 ++- .../KLHZ.Trader.Core.Math.csproj | 18 ++ .../HistoryCacheUnitTests.cs | 1 - .../HistoryProcessingInstrumentsTests.cs | 1 - .../Common/Messaging/Services/DataBus.cs | 20 +- .../DataLayer/Entities/Prices/PriceChange.cs | 2 +- .../Entities/Prices/ProcessedPrice.cs | 30 +++ .../Entities/Trades/InstrumentTrade.cs | 33 +++ .../DataLayer/Entities/Trades/Trade.cs | 3 + .../Entities/Trades/TradeDirection.cs | 4 +- KLHZ.Trader.Core/DataLayer/TraderDbContext.cs | 21 ++ .../Services/ProcessedPricesLogger.cs | 74 +++++++ .../Declisions/Services/Trader.cs | 27 +-- .../Services/TradingEventsDetector.cs | 23 --- .../Utils/HistoryProcessingInstruments.cs | 18 +- .../Exchange/Services/ExchangeDataReader.cs | 101 ++++----- .../Exchange/Services/ManagedAccount.cs | 2 +- KLHZ.Trader.Core/KLHZ.Trader.Core.csproj | 2 + .../Controllers/PlayController.cs | 41 ++-- KLHZ.Trader.Service/Program.cs | 3 + KLHZ.Trader.sln | 7 + migration1.sql | 12 ++ 29 files changed, 554 insertions(+), 147 deletions(-) rename KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/{INewPriceMessage.cs => INewPrice.cs} (86%) create mode 100644 KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/IProcessedPrice.cs create mode 100644 KLHZ.Trader.Core.Math/Declisions/Dtos/ProcessedPrice.cs create mode 100644 KLHZ.Trader.Core.Math/Declisions/Dtos/Services/KalmanPredictor.cs rename {KLHZ.Trader.Core/Declisions => KLHZ.Trader.Core.Math/Declisions/Dtos}/Services/PriceHistoryCacheUnit.cs (68%) create mode 100644 KLHZ.Trader.Core.Math/KLHZ.Trader.Core.Math.csproj create mode 100644 KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs create mode 100644 KLHZ.Trader.Core/DataLayer/Entities/Trades/InstrumentTrade.cs create mode 100644 KLHZ.Trader.Core/Declisions/Services/ProcessedPricesLogger.cs create mode 100644 migration1.sql diff --git a/KLHZ.Trader.Core.Contracts/Declisions/Interfaces/IPriceHistoryCacheUnit.cs b/KLHZ.Trader.Core.Contracts/Declisions/Interfaces/IPriceHistoryCacheUnit.cs index 86b40aa..074a7c3 100644 --- a/KLHZ.Trader.Core.Contracts/Declisions/Interfaces/IPriceHistoryCacheUnit.cs +++ b/KLHZ.Trader.Core.Contracts/Declisions/Interfaces/IPriceHistoryCacheUnit.cs @@ -5,7 +5,8 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces public interface IPriceHistoryCacheUnit { public string Figi { get; } - public ValueTask AddData(INewPriceMessage priceChange); + public int Length { get; } + public ValueTask AddData(INewPrice priceChange); public ValueTask<(DateTime[] timestamps, float[] prices)> GetData(); } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPriceMessage.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPrice.cs similarity index 86% rename from KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPriceMessage.cs rename to KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPrice.cs index c6957c5..80dc8f9 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPriceMessage.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/INewPrice.cs @@ -1,6 +1,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces { - public interface INewPriceMessage + public interface INewPrice { public bool IsHistoricalData { get; set; } public decimal Value { get; set; } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/IProcessedPrice.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/IProcessedPrice.cs new file mode 100644 index 0000000..02c1c6b --- /dev/null +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Intarfaces/IProcessedPrice.cs @@ -0,0 +1,7 @@ +namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces +{ + public interface IProcessedPrice : INewPrice + { + public string Processor { get; set; } + } +} diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs index 40a17bf..fddd16d 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs @@ -2,7 +2,7 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos { - public class NewPriceMessage : INewPriceMessage + public class NewPriceMessage : INewPrice { public decimal Value { get; set; } public required string Figi { get; set; } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Interfaces/IDataBus.cs b/KLHZ.Trader.Core.Contracts/Messaging/Interfaces/IDataBus.cs index 8996439..f538319 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Interfaces/IDataBus.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Interfaces/IDataBus.cs @@ -6,12 +6,14 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces { public interface IDataBus { - public bool AddChannel(string key, Channel channel); + public bool AddChannel(string key, Channel channel); + public bool AddChannel(string key, Channel channel); public bool AddChannel(string key, Channel channel); public bool AddChannel(string key, Channel channel); public bool AddChannel(string key, Channel channel); - public Task BroadcastNewPrice(INewPriceMessage newPriceMessage); + public Task BroadcastNewPrice(INewPrice newPriceMessage); public Task BroadcastCommand(TradeCommand command); public Task BroadcastNewCandle(INewCandle command); + public Task BroadcastProcessedPrice(IProcessedPrice command); } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Dtos/ProcessedPrice.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/ProcessedPrice.cs new file mode 100644 index 0000000..f291d1d --- /dev/null +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/ProcessedPrice.cs @@ -0,0 +1,14 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; + +namespace KLHZ.Trader.Core.Math.Declisions.Dtos +{ + public class ProcessedPrice : IProcessedPrice + { + public required string Processor { get; set; } + public bool IsHistoricalData { get; set; } + public decimal Value { get; set; } + public required string Figi { get; set; } + public required string Ticker { get; set; } + public DateTime Time { get; set; } + } +} diff --git a/KLHZ.Trader.Core.Math/Declisions/Dtos/Services/KalmanPredictor.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/Services/KalmanPredictor.cs new file mode 100644 index 0000000..0b449cc --- /dev/null +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/Services/KalmanPredictor.cs @@ -0,0 +1,191 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; +using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; +using MathNet.Filtering.Kalman; +using MathNet.Numerics.LinearAlgebra; +using Microsoft.Extensions.Hosting; +using System.Threading.Channels; + +namespace KLHZ.Trader.Core.Math.Declisions.Dtos.Services +{ + public class KalmanPredictor : IHostedService + { + private readonly double r = 1000.0; // Measurement covariance + private readonly PriceHistoryCacheUnit _cache = new PriceHistoryCacheUnit(""); + private readonly Channel _messages = Channel.CreateUnbounded(); + private readonly IDataBus _dataBus; + + private DiscreteKalmanFilter? _dkf; + + public KalmanPredictor(IDataBus bus) + { + _dataBus = bus; + bus.AddChannel(nameof(KalmanPredictor), _messages); + _ = ProcessMessages(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private async Task Init() + { + + } + + private async Task ProcessCalman(INewPrice message) + { + var H = Matrix.Build.Dense(2, 2, new[] { 1d, 0d, 0d, 1d }); // Measurement matrix + var length = _cache.Length; + + if (length > 2 && _dkf != null) + { + var data = await _cache.GetData(); + + var dt = (data.timestamps[data.prices.Length - 1] - data.timestamps[data.prices.Length - 2]).TotalSeconds; + if (dt < 0.0000001 || dt > 1800) return; + var F = Matrix.Build.Dense(2, 2, new[] { 1d, 0d, dt, 1 }); // State transition matrix + var R = Matrix.Build.Dense(2, 2, new[] { r, r / dt, r / dt, 2 * r / (dt * dt) }); + _dkf.Predict(F); + + var state = _dkf.State; + var value = state[0, 0] + dt * state[1, 0]; + if (!double.IsNaN(value)) + { + await _dataBus.BroadcastProcessedPrice(new ProcessedPrice() + { + Figi = message.Figi, + Processor = nameof(KalmanPredictor) + "_predict", + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = (decimal)value, + }); + } + + + var dprice = data.prices[data.prices.Length - 1] - data.prices[data.prices.Length - 2]; + var k = dprice / dt; + var b = data.prices[data.prices.Length - 2]; + var z = Matrix.Build.Dense(2, 1, new[] { b, k }); + + _dkf.Update(z, H, R); + + var state2 = _dkf.State; + var value2 = state2[0, 0] + dt * state2[1, 0]; + if (!double.IsNaN(value)) + { + await _dataBus.BroadcastProcessedPrice(new ProcessedPrice() + { + Figi = message.Figi, + Processor = nameof(KalmanPredictor) + "_state", + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = (decimal)value, + }); + } + } + else if (length >= 2) + { + var data = await _cache.GetData(); + var dprice = data.prices[data.prices.Length - 1] - data.prices[data.prices.Length - 2]; + var dt = (data.timestamps[data.prices.Length - 1] - data.timestamps[data.prices.Length - 2]).TotalSeconds; + if (dt < 0.0000001) return; + var k = dprice / dt; + var b = data.prices[data.prices.Length - 2]; + + + Matrix x0 = Matrix.Build.Dense(2, 1, new[] { b, k }); + Matrix P0 = Matrix.Build.Dense(2, 2, new[] { r, r / dt, r / dt, 2 * r / (dt * dt) }); + + _dkf = new DiscreteKalmanFilter(x0, P0); + } + } + + private async Task ProcessMovAv(INewPrice message, int window) + { + var data = await _cache.GetData(); + if (data.prices.Length < window) return; + var sum = 0f; + var count = 0; + for (int i = 1; (i <= window); i++) + { + sum += data.prices[data.prices.Length - i]; + count++; + } + + await _dataBus.BroadcastProcessedPrice(new ProcessedPrice() + { + Figi = message.Figi, + Processor = nameof(KalmanPredictor) + "_mov_av_window" + window.ToString(), + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = (decimal)(sum / count), + }); + } + + private async Task ProcessTimeWindow(INewPrice message, int window) + { + var data = await _cache.GetData(); + var sum = data.prices[data.prices.Length - 1]; + var count = 1; + var startTime = data.timestamps[data.prices.Length - 1]; + for (int i = 2; (i < data.prices.Length && (startTime - data.timestamps[data.prices.Length - i]) < TimeSpan.FromSeconds(window)); i++) + { + sum += data.prices[data.prices.Length - i]; + count++; + } + + await _dataBus.BroadcastProcessedPrice(new ProcessedPrice() + { + Figi = message.Figi, + Processor = nameof(KalmanPredictor) + "_timeWindow" + window.ToString(), + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = (decimal)(sum / count), + }); + + var diffValue = System.Math.Abs((decimal)(sum / count) - message.Value); + await _dataBus.BroadcastProcessedPrice(new ProcessedPrice() + { + Figi = message.Figi, + Processor = nameof(KalmanPredictor) + "_diff" + window.ToString(), + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = diffValue > 3 ? diffValue : 0, + }); + } + + private async Task ProcessMessages() + { + + + while (await _messages.Reader.WaitToReadAsync()) + { + var message = await _messages.Reader.ReadAsync(); + await _cache.AddData(message); + try + { + //await ProcessCalman(message); + await ProcessMovAv(message, 3); + await ProcessTimeWindow(message, 5); + await ProcessTimeWindow(message, 15); + await ProcessTimeWindow(message, 120); + } + catch (Exception ex) + { + + } + } + } + } +} diff --git a/KLHZ.Trader.Core/Declisions/Services/PriceHistoryCacheUnit.cs b/KLHZ.Trader.Core.Math/Declisions/Dtos/Services/PriceHistoryCacheUnit.cs similarity index 68% rename from KLHZ.Trader.Core/Declisions/Services/PriceHistoryCacheUnit.cs rename to KLHZ.Trader.Core.Math/Declisions/Dtos/Services/PriceHistoryCacheUnit.cs index f35f544..fe2a76f 100644 --- a/KLHZ.Trader.Core/Declisions/Services/PriceHistoryCacheUnit.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Dtos/Services/PriceHistoryCacheUnit.cs @@ -1,7 +1,7 @@ using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; -namespace KLHZ.Trader.Core.Declisions.Services +namespace KLHZ.Trader.Core.Math.Declisions.Dtos.Services { public class PriceHistoryCacheUnit : IPriceHistoryCacheUnit { @@ -9,13 +9,24 @@ namespace KLHZ.Trader.Core.Declisions.Services public string Figi { get; init; } + public int Length + { + get + { + lock (_locker) + { + return _length; + } + } + } + private readonly object _locker = new(); private readonly float[] Prices = new float[ArrayMaxLength]; private readonly DateTime[] Timestamps = new DateTime[ArrayMaxLength]; - private int Length = 0; + private int _length = 0; - public ValueTask AddData(INewPriceMessage priceChange) + public ValueTask AddData(INewPrice priceChange) { lock (_locker) { @@ -25,9 +36,9 @@ namespace KLHZ.Trader.Core.Declisions.Services Prices[Prices.Length - 1] = (float)priceChange.Value; Timestamps[Timestamps.Length - 1] = priceChange.Time; - if (Length < ArrayMaxLength) + if (_length < ArrayMaxLength) { - Length++; + _length++; } } return ValueTask.CompletedTask; @@ -35,17 +46,17 @@ namespace KLHZ.Trader.Core.Declisions.Services public ValueTask<(DateTime[] timestamps, float[] prices)> GetData() { - var prices = new float[Length]; - var timestamps = new DateTime[Length]; lock (_locker) { - Array.Copy(Prices, Prices.Length - Length, prices, 0, prices.Length); - Array.Copy(Timestamps, Prices.Length - Length, timestamps, 0, timestamps.Length); + var prices = new float[_length]; + var timestamps = new DateTime[_length]; + Array.Copy(Prices, Prices.Length - _length, prices, 0, prices.Length); + Array.Copy(Timestamps, Prices.Length - _length, timestamps, 0, timestamps.Length); return ValueTask.FromResult((timestamps, prices)); } } - public PriceHistoryCacheUnit(string figi, params INewPriceMessage[] priceChanges) + public PriceHistoryCacheUnit(string figi, params INewPrice[] priceChanges) { Figi = figi; @@ -69,7 +80,7 @@ namespace KLHZ.Trader.Core.Declisions.Services Array.Copy(prices, 0, Prices, Prices.Length - prices.Length, prices.Length); Array.Copy(times, 0, Timestamps, Timestamps.Length - times.Length, times.Length); - Length = times.Length > ArrayMaxLength ? ArrayMaxLength : times.Length; + _length = times.Length > ArrayMaxLength ? ArrayMaxLength : times.Length; } } } diff --git a/KLHZ.Trader.Core.Math/KLHZ.Trader.Core.Math.csproj b/KLHZ.Trader.Core.Math/KLHZ.Trader.Core.Math.csproj new file mode 100644 index 0000000..8e5eb63 --- /dev/null +++ b/KLHZ.Trader.Core.Math/KLHZ.Trader.Core.Math.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/KLHZ.Trader.Core.Tests/HistoryCacheUnitTests.cs b/KLHZ.Trader.Core.Tests/HistoryCacheUnitTests.cs index 5266b34..d9b3290 100644 --- a/KLHZ.Trader.Core.Tests/HistoryCacheUnitTests.cs +++ b/KLHZ.Trader.Core.Tests/HistoryCacheUnitTests.cs @@ -1,5 +1,4 @@ using KLHZ.Trader.Core.DataLayer.Entities.Prices; -using KLHZ.Trader.Core.Declisions.Services; namespace KLHZ.Trader.Core.Tests { diff --git a/KLHZ.Trader.Core.Tests/HistoryProcessingInstrumentsTests.cs b/KLHZ.Trader.Core.Tests/HistoryProcessingInstrumentsTests.cs index 6f1fdf6..e500f58 100644 --- a/KLHZ.Trader.Core.Tests/HistoryProcessingInstrumentsTests.cs +++ b/KLHZ.Trader.Core.Tests/HistoryProcessingInstrumentsTests.cs @@ -1,5 +1,4 @@ using KLHZ.Trader.Core.DataLayer.Entities.Prices; -using KLHZ.Trader.Core.Declisions.Services; using KLHZ.Trader.Core.Declisions.Utils; namespace KLHZ.Trader.Core.Tests diff --git a/KLHZ.Trader.Core/Common/Messaging/Services/DataBus.cs b/KLHZ.Trader.Core/Common/Messaging/Services/DataBus.cs index c5a4757..2c34454 100644 --- a/KLHZ.Trader.Core/Common/Messaging/Services/DataBus.cs +++ b/KLHZ.Trader.Core/Common/Messaging/Services/DataBus.cs @@ -10,15 +10,21 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services { private readonly ConcurrentDictionary> _messagesChannels = new(); private readonly ConcurrentDictionary> _candlesChannels = new(); - private readonly ConcurrentDictionary> _priceChannels = new(); + private readonly ConcurrentDictionary> _priceChannels = new(); + private readonly ConcurrentDictionary> _processedPricesChannels = new(); private readonly ConcurrentDictionary> _commandChannels = new(); + public bool AddChannel(string key, Channel channel) + { + return _processedPricesChannels.TryAdd(key, channel); + } + public bool AddChannel(string key, Channel channel) { return _messagesChannels.TryAdd(key, channel); } - public bool AddChannel(string key, Channel channel) + public bool AddChannel(string key, Channel channel) { return _priceChannels.TryAdd(key, channel); } @@ -33,7 +39,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services return _commandChannels.TryAdd(key, channel); } - public async Task BroadcastNewPrice(INewPriceMessage newPriceMessage) + public async Task BroadcastNewPrice(INewPrice newPriceMessage) { foreach (var channel in _priceChannels.Values) { @@ -41,6 +47,14 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services } } + public async Task BroadcastProcessedPrice(IProcessedPrice mess) + { + foreach (var channel in _processedPricesChannels.Values) + { + await channel.Writer.WriteAsync(mess); + } + } + public async Task BroadcastNewCandle(INewCandle newPriceMessage) { foreach (var channel in _candlesChannels.Values) diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs b/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs index bd0db37..6aed638 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace KLHZ.Trader.Core.DataLayer.Entities.Prices { [Table("price_changes")] - public class PriceChange : INewPriceMessage + public class PriceChange : INewPrice { [Column("id")] public long Id { get; set; } diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs b/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs new file mode 100644 index 0000000..57aa685 --- /dev/null +++ b/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs @@ -0,0 +1,30 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; +using System.ComponentModel.DataAnnotations.Schema; + +namespace KLHZ.Trader.Core.DataLayer.Entities.Prices +{ + [Table("processed_prices")] + public class ProcessedPrice : IProcessedPrice + { + [Column("id")] + public long Id { get; set; } + + [Column("time")] + public DateTime Time { get; set; } + + [Column("value")] + public decimal Value { get; set; } + + [Column("figi")] + public required string Figi { get; set; } + + [Column("ticker")] + public required string Ticker { get; set; } + + [NotMapped] + public bool IsHistoricalData { get; set; } + + [Column("processor")] + public required string Processor { get; set; } + } +} diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Trades/InstrumentTrade.cs b/KLHZ.Trader.Core/DataLayer/Entities/Trades/InstrumentTrade.cs new file mode 100644 index 0000000..c0fdcb2 --- /dev/null +++ b/KLHZ.Trader.Core/DataLayer/Entities/Trades/InstrumentTrade.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace KLHZ.Trader.Core.DataLayer.Entities.Trades +{ + /// + /// Обезличенная сделка с биржи над инструментом. + /// + [Table("instrument_trades")] + public class InstrumentTrade + { + [Column("trade_id")] + public long Id { get; set; } + + [Column("bought_at")] + public DateTime BoughtAt { get; set; } + + [Column("figi")] + public required string Figi { get; set; } + + [Column("ticker")] + public required string Ticker { get; set; } + + [Column("price")] + + public decimal Price { get; set; } + + [Column("count")] + public decimal Count { get; set; } + + [Column("direction")] + public TradeDirection Direction { get; set; } + } +} diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Trades/Trade.cs b/KLHZ.Trader.Core/DataLayer/Entities/Trades/Trade.cs index e7c764d..f85138e 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Trades/Trade.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Trades/Trade.cs @@ -2,6 +2,9 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Trades { + /// + /// Сделка, совершенная ботом. + /// [Table("trades")] public class Trade { diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Trades/TradeDirection.cs b/KLHZ.Trader.Core/DataLayer/Entities/Trades/TradeDirection.cs index 63e87b8..62a87f9 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Trades/TradeDirection.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Trades/TradeDirection.cs @@ -3,7 +3,7 @@ public enum TradeDirection { Unknown = 0, - Income = 1, - Outcome = 2 + Buy = 1, + Sell = 2 } } diff --git a/KLHZ.Trader.Core/DataLayer/TraderDbContext.cs b/KLHZ.Trader.Core/DataLayer/TraderDbContext.cs index df5d66b..651811d 100644 --- a/KLHZ.Trader.Core/DataLayer/TraderDbContext.cs +++ b/KLHZ.Trader.Core/DataLayer/TraderDbContext.cs @@ -9,8 +9,10 @@ namespace KLHZ.Trader.Core.DataLayer public class TraderDbContext : DbContext { public DbSet Trades { get; set; } + public DbSet InstrumentTrades { get; set; } public DbSet Declisions { get; set; } public DbSet PriceChanges { get; set; } + public DbSet ProcessedPrices { get; set; } public DbSet Candles { get; set; } public TraderDbContext(DbContextOptions options) : base(options) @@ -30,6 +32,15 @@ namespace KLHZ.Trader.Core.DataLayer v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + entity.Property(e => e.BoughtAt) + .HasConversion( + v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + }); + modelBuilder.Entity(entity => { entity.HasKey(e1 => e1.Id); @@ -49,6 +60,16 @@ namespace KLHZ.Trader.Core.DataLayer v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + entity.Ignore(e1 => e1.IsHistoricalData); + entity.Property(e => e.Time) + .HasConversion( + v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + }); + modelBuilder.Entity(entity => { entity.HasKey(e1 => new { e1.Figi, e1.Time }); diff --git a/KLHZ.Trader.Core/Declisions/Services/ProcessedPricesLogger.cs b/KLHZ.Trader.Core/Declisions/Services/ProcessedPricesLogger.cs new file mode 100644 index 0000000..0512ce5 --- /dev/null +++ b/KLHZ.Trader.Core/Declisions/Services/ProcessedPricesLogger.cs @@ -0,0 +1,74 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; +using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; +using KLHZ.Trader.Core.DataLayer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Threading.Channels; + +namespace KLHZ.Trader.Core.Declisions.Services +{ + public class ProcessedPricesLogger : IHostedService + { + private readonly ILogger _logger; + private readonly IDataBus _dataBus; + private readonly IDbContextFactory _dbContextFactory; + + private readonly Channel _channel = Channel.CreateUnbounded(); + public ProcessedPricesLogger(IDataBus dataBus, IDbContextFactory dbContextFactory, ILogger logger) + { + _dataBus = dataBus; + _dbContextFactory = dbContextFactory; + _logger = logger; + _dataBus.AddChannel(nameof(ProcessedPricesLogger), _channel); + _ = ProcessMessages(); + } + + private async Task ProcessMessages() + { + using var context = await _dbContextFactory.CreateDbContextAsync(); + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var buffer = new List(); + var lastWrite = DateTime.UtcNow; + while (await _channel.Reader.WaitToReadAsync()) + { + try + { + var message = await _channel.Reader.ReadAsync(); + + buffer.Add(new DataLayer.Entities.Prices.ProcessedPrice() + { + Figi = message.Figi, + Processor = message.Processor, + Ticker = message.Ticker, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = message.Value, + }); + + if (buffer.Count > 10000 || (DateTime.UtcNow - lastWrite) > TimeSpan.FromSeconds(5) || _channel.Reader.Count == 0) + { + await context.AddRangeAsync(buffer); + await context.SaveChangesAsync(); + buffer.Clear(); + lastWrite = DateTime.UtcNow; + } + } + catch (Exception ex) + { + + } + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/KLHZ.Trader.Core/Declisions/Services/Trader.cs b/KLHZ.Trader.Core/Declisions/Services/Trader.cs index ed73d28..2b019ff 100644 --- a/KLHZ.Trader.Core/Declisions/Services/Trader.cs +++ b/KLHZ.Trader.Core/Declisions/Services/Trader.cs @@ -5,11 +5,11 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Declisions; -using KLHZ.Trader.Core.Declisions.Utils; using KLHZ.Trader.Core.Exchange; using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Models; using KLHZ.Trader.Core.Exchange.Services; +using KLHZ.Trader.Core.Math.Declisions.Dtos.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -29,7 +29,7 @@ namespace KLHZ.Trader.Core.Declisions.Services private readonly BotModeSwitcher _botModeSwitcher; private readonly IDbContextFactory _dbContextFactory; private readonly ConcurrentDictionary Accounts = new(); - private readonly ConcurrentDictionary _historyCash = new(); + private readonly ConcurrentDictionary _historyCash = new(); private readonly ITradingEventsDetector _tradingEventsDetector; @@ -40,7 +40,7 @@ namespace KLHZ.Trader.Core.Declisions.Services private readonly decimal _defaultBuyPartOfAccount; private readonly string[] _managedAccountsNamePatterns = []; - private readonly Channel _pricesChannel = Channel.CreateUnbounded(); + private readonly Channel _pricesChannel = Channel.CreateUnbounded(); public Trader( ITradingEventsDetector tradingEventsDetector, @@ -89,7 +89,6 @@ namespace KLHZ.Trader.Core.Declisions.Services _ = ProcessMessages(); } - private async Task ProcessMessages() { while (await _pricesChannel.Reader.WaitToReadAsync()) @@ -97,7 +96,7 @@ namespace KLHZ.Trader.Core.Declisions.Services var message = await _pricesChannel.Reader.ReadAsync(); if (_historyCash.TryGetValue(message.Figi, out var data)) { - data.AddData(message); + await data.AddData(message); } else { @@ -146,24 +145,6 @@ namespace KLHZ.Trader.Core.Declisions.Services } } - public async Task Preprocess(string figi) - { - if (_historyCash.TryGetValue(figi, out var unit)) - { - var periodData1 = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, TimeSpan.FromSeconds(10)); - var periodData2 = unit.GetPriceDiffForTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)); - if (Math.Abs(periodData1.PeriodDiff) <= 1 && periodData2.PeriodDiff > 2) - { - //можно покупать. - } - - if (Math.Abs(periodData1.PeriodDiff) <= 1 && periodData2.PeriodDiff < -2) - { - //можно продавать. - } - - } - } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; diff --git a/KLHZ.Trader.Core/Declisions/Services/TradingEventsDetector.cs b/KLHZ.Trader.Core/Declisions/Services/TradingEventsDetector.cs index 9a22c7c..d65efde 100644 --- a/KLHZ.Trader.Core/Declisions/Services/TradingEventsDetector.cs +++ b/KLHZ.Trader.Core/Declisions/Services/TradingEventsDetector.cs @@ -1,6 +1,5 @@ using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; -using KLHZ.Trader.Core.DataLayer.Entities.Declisions; using KLHZ.Trader.Core.Declisions.Utils; namespace KLHZ.Trader.Core.Declisions.Services @@ -36,28 +35,6 @@ namespace KLHZ.Trader.Core.Declisions.Services var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8); uptrendEnds |= uptrendEnds2; - //var uptrendEnds2 = data.CheckUptrendEnding(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(20), meanfullDiff); - - //var uptrendEnds = uptrendEnds1 || uptrendEnds2; - - var declisionAction = DeclisionTradeAction.Unknown; - - //if (downtrendStarts) - //{ - // //declisionAction = DeclisionTradeAction.OpenShort; - //} - if (uptrendStarts) - { - declisionAction = DeclisionTradeAction.OpenLong; - } - //else if (downtrendEnds) - //{ - // //declisionAction = DeclisionTradeAction.CloseShort; - //} - else if (uptrendEnds) - { - declisionAction = DeclisionTradeAction.CloseLong; - } return new TradingEventsDto(uptrendEnds, uptrendStarts); } } diff --git a/KLHZ.Trader.Core/Declisions/Utils/HistoryProcessingInstruments.cs b/KLHZ.Trader.Core/Declisions/Utils/HistoryProcessingInstruments.cs index 9316ae2..a231c77 100644 --- a/KLHZ.Trader.Core/Declisions/Utils/HistoryProcessingInstruments.cs +++ b/KLHZ.Trader.Core/Declisions/Utils/HistoryProcessingInstruments.cs @@ -1,6 +1,6 @@ using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Declisions.Dtos; -using KLHZ.Trader.Core.Declisions.Services; +using KLHZ.Trader.Core.Math.Declisions.Dtos.Services; namespace KLHZ.Trader.Core.Declisions.Utils { @@ -66,32 +66,32 @@ namespace KLHZ.Trader.Core.Declisions.Utils internal static bool CheckStable(this PeriodPricesInfoDto data, float meanfullDiff) { - meanfullDiff = Math.Abs(meanfullDiff); - return data.Success && Math.Abs(data.PeriodDiff) < 1.5 * meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * meanfullDiff; + meanfullDiff = System.Math.Abs(meanfullDiff); + return data.Success && System.Math.Abs(data.PeriodDiff) < 1.5 * meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * meanfullDiff; } internal static bool CheckGrowing(this PeriodPricesInfoDto data, float meanfullDiff) { - return meanfullDiff > 0 && data.Success && data.PeriodDiff > meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * Math.Abs(data.PeriodDiff); + return meanfullDiff > 0 && data.Success && data.PeriodDiff > meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * System.Math.Abs(data.PeriodDiff); } internal static bool CheckFalling(this PeriodPricesInfoDto data, float meanfullDiff) { meanfullDiff = -meanfullDiff; - return meanfullDiff < 0 && data.Success && data.PeriodDiff < meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * Math.Abs(data.PeriodDiff); + return meanfullDiff < 0 && data.Success && data.PeriodDiff < meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * System.Math.Abs(data.PeriodDiff); } internal static float CalcTrendRelationAbs(PeriodPricesInfoDto first, PeriodPricesInfoDto second) { - var k1 = Math.Abs(first.PeriodDiff) / Math.Abs(first.Period.TotalSeconds); - var k2 = Math.Abs(second.PeriodDiff) / Math.Abs(second.Period.TotalSeconds); + var k1 = System.Math.Abs(first.PeriodDiff) / System.Math.Abs(first.Period.TotalSeconds); + var k2 = System.Math.Abs(second.PeriodDiff) / System.Math.Abs(second.Period.TotalSeconds); if (k2 == 0 && k1 != 0) return 1000; return (float)(k1 / k2); } internal static float CalcTrendRelationAbs(TwoPeriodsProcessingDto data) { - var k1 = Math.Abs(data.DiffStart) / Math.Abs(data.PeriodStart.TotalSeconds); - var k2 = Math.Abs(data.DiffEnd) / Math.Abs(data.PeriodEnd.TotalSeconds); + var k1 = System.Math.Abs(data.DiffStart) / System.Math.Abs(data.PeriodStart.TotalSeconds); + var k2 = System.Math.Abs(data.DiffEnd) / System.Math.Abs(data.PeriodEnd.TotalSeconds); if (k2 == 0 && k1 != 0) return 1000; return (float)(k1 / k2); } diff --git a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs index 334e82c..818688f 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs @@ -2,6 +2,7 @@ using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Prices; +using KLHZ.Trader.Core.DataLayer.Entities.Trades; using KLHZ.Trader.Core.Exchange.Extentions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; @@ -10,7 +11,6 @@ using Microsoft.Extensions.Options; using System.Collections.Concurrent; using Tinkoff.InvestApi; using Tinkoff.InvestApi.V1; -using Candle = KLHZ.Trader.Core.DataLayer.Entities.Prices.Candle; namespace KLHZ.Trader.Core.Exchange.Services { @@ -95,6 +95,12 @@ namespace KLHZ.Trader.Core.Exchange.Services SubscriptionAction = SubscriptionAction.Subscribe }; + + var tradesRequest = new SubscribeTradesRequest + { + SubscriptionAction = SubscriptionAction.Subscribe + }; + foreach (var f in _instrumentsFigis) { request.Instruments.Add( @@ -102,6 +108,12 @@ namespace KLHZ.Trader.Core.Exchange.Services { InstrumentId = f }); + + tradesRequest.Instruments.Add( + new TradeInstrument() + { + InstrumentId = f + }); } await stream.RequestStream.WriteAsync(new MarketDataRequest @@ -109,6 +121,17 @@ namespace KLHZ.Trader.Core.Exchange.Services SubscribeLastPriceRequest = request, }); + await stream.RequestStream.WriteAsync(new MarketDataRequest + { + SubscribeTradesRequest = tradesRequest, + }); + + using var context = await _dbContextFactory.CreateDbContextAsync(); + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var pricesBuffer = new List(); + var tradesBuffer = new List(); + var lastWritePrices = DateTime.UtcNow; + var lastWriteTrades = DateTime.UtcNow; await foreach (var response in stream.ResponseStream.ReadAllAsync()) { if (response.LastPrice != null) @@ -122,63 +145,41 @@ namespace KLHZ.Trader.Core.Exchange.Services IsHistoricalData = false, }; await _eventBus.BroadcastNewPrice(message); - using var context = await _dbContextFactory.CreateDbContextAsync(); - context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - await context.PriceChanges.AddAsync(message); - await context.SaveChangesAsync(); + + pricesBuffer.Add(message); } - } - } - - private async Task SubscribeCandles() - { - using var stream = _investApiClient.MarketDataStream.MarketDataStream(); - - var request = new SubscribeCandlesRequest - { - SubscriptionAction = SubscriptionAction.Subscribe, - CandleSourceType = GetCandlesRequest.Types.CandleSource.Exchange - }; - - foreach (var f in _instrumentsFigis) - { - request.Instruments.Add( - new CandleInstrument() - { - InstrumentId = f, - Interval = SubscriptionInterval.OneMinute - }); - } - - await stream.RequestStream.WriteAsync(new MarketDataRequest - { - SubscribeCandlesRequest = request, - }); - - await foreach (var response in stream.ResponseStream.ReadAllAsync()) - { - if (response.Candle != null) + if (response.Trade != null) { - var message = new Candle() + var trade = new KLHZ.Trader.Core.DataLayer.Entities.Trades.InstrumentTrade() { - Figi = response.Candle.Figi, - Ticker = GetTickerByFigi(response.LastPrice.Figi), - Time = response.Candle.Time.ToDateTime().ToUniversalTime(), - Close = response.Candle.Close, - Open = response.Candle.Open, - Low = response.Candle.Low, - High = response.Candle.High, - Volume = response.Candle.Volume, - IsHistoricalData = false, + Figi = response.Trade.Figi, + BoughtAt = response.Trade.Time.ToDateTime().ToUniversalTime(), + Ticker = GetTickerByFigi(response.Trade.Figi), + Price = response.Trade.Price, + Count = response.Trade.Quantity, + Direction = response.Trade.Direction == Tinkoff.InvestApi.V1.TradeDirection.Sell ? KLHZ.Trader.Core.DataLayer.Entities.Trades.TradeDirection.Sell : KLHZ.Trader.Core.DataLayer.Entities.Trades.TradeDirection.Buy, }; - await _eventBus.BroadcastNewCandle(message); - using var context = await _dbContextFactory.CreateDbContextAsync(); - context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - await context.Candles.AddAsync(message); + tradesBuffer.Add(trade); + } + + + //if (pricesBuffer.Count > 200 || (DateTime.UtcNow - lastWritePrices).TotalSeconds > 10) + { + lastWritePrices = DateTime.UtcNow; + await context.PriceChanges.AddRangeAsync(pricesBuffer); + pricesBuffer.Clear(); + await context.SaveChangesAsync(); + } + //if (tradesBuffer.Count > 200 || (DateTime.UtcNow - lastWriteTrades).TotalSeconds > 10) + { + lastWriteTrades = DateTime.UtcNow; + await context.InstrumentTrades.AddRangeAsync(tradesBuffer); + tradesBuffer.Clear(); await context.SaveChangesAsync(); } } } + private string GetTickerByFigi(string figi) { return _tickersCache.TryGetValue(figi, out var ticker) ? ticker : string.Empty; diff --git a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs index 83bf43e..d6f8835 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs @@ -237,7 +237,7 @@ namespace KLHZ.Trader.Core.Exchange.Services Count = res.LotsExecuted, Price = res.ExecutedOrderPrice, Position = DataLayer.Entities.Trades.PositionType.Long, - Direction = DataLayer.Entities.Trades.TradeDirection.Income, + Direction = DataLayer.Entities.Trades.TradeDirection.Buy, Asset = DataLayer.Entities.Trades.AssetType.Common, }; diff --git a/KLHZ.Trader.Core/KLHZ.Trader.Core.csproj b/KLHZ.Trader.Core/KLHZ.Trader.Core.csproj index 2034e46..60ab431 100644 --- a/KLHZ.Trader.Core/KLHZ.Trader.Core.csproj +++ b/KLHZ.Trader.Core/KLHZ.Trader.Core.csproj @@ -12,10 +12,12 @@ + + diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index fa88a28..d28846e 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -22,24 +22,31 @@ namespace KLHZ.Trader.Service.Controllers [HttpGet] public async Task Run(string figi) { - using var context1 = await _dbContextFactory.CreateDbContextAsync(); - context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - var data = await context1.PriceChanges - .Where(c => c.Figi == figi) - .OrderBy(c => c.Time) - .Select(c => new NewPriceMessage() - { - Figi = figi, - Ticker = c.Ticker, - Time = c.Time, - Value = c.Value, - IsHistoricalData = true - }) - .ToArrayAsync(); - - foreach (var mess in data) + try { - await _dataBus.BroadcastNewPrice(mess); + using var context1 = await _dbContextFactory.CreateDbContextAsync(); + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var data = await context1.PriceChanges + .Where(c => c.Figi == figi) + .OrderBy(c => c.Time) + .Select(c => new NewPriceMessage() + { + Figi = figi, + Ticker = c.Ticker, + Time = c.Time, + Value = c.Value, + IsHistoricalData = true + }) + .ToArrayAsync(); + + foreach (var mess in data) + { + await _dataBus.BroadcastNewPrice(mess); + } + } + catch (Exception ex) + { + } } } diff --git a/KLHZ.Trader.Service/Program.cs b/KLHZ.Trader.Service/Program.cs index 6a374f3..12bd241 100644 --- a/KLHZ.Trader.Service/Program.cs +++ b/KLHZ.Trader.Service/Program.cs @@ -46,6 +46,9 @@ builder.Services.AddDbContextFactory(options => builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); +//builder.Services.AddHostedService(); + +//builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/KLHZ.Trader.sln b/KLHZ.Trader.sln index be617e0..b247f42 100644 --- a/KLHZ.Trader.sln +++ b/KLHZ.Trader.sln @@ -26,6 +26,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postrgres", "postrgres", "{174A800A-6040-40CF-B331-8603E097CBAC}" ProjectSection(SolutionItems) = preProject KLHZ.Trader.Infrastructure\postgres\init.sql = KLHZ.Trader.Infrastructure\postgres\init.sql + migration1.sql = migration1.sql EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "loki", "loki", "{63D21DAF-FDF0-4F2D-A671-E9E59BB0CA5B}" @@ -42,6 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{9DE95D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KLHZ.Trader.Core.Contracts", "KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj", "{C1ADC79B-ADDB-435D-A453-9D1623D144C4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KLHZ.Trader.Core.Math", "KLHZ.Trader.Core.Math\KLHZ.Trader.Core.Math.csproj", "{4C224F89-2C33-41FB-94CB-87368C86C2C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +75,10 @@ Global {C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Release|Any CPU.Build.0 = Release|Any CPU + {4C224F89-2C33-41FB-94CB-87368C86C2C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C224F89-2C33-41FB-94CB-87368C86C2C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C224F89-2C33-41FB-94CB-87368C86C2C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C224F89-2C33-41FB-94CB-87368C86C2C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/migration1.sql b/migration1.sql new file mode 100644 index 0000000..7049d06 --- /dev/null +++ b/migration1.sql @@ -0,0 +1,12 @@ +create table processed_prices +( + id bigserial, + time timestamp default current_timestamp, + figi text not null, + processor text not null, + ticker text not null, + value decimal not null, + primary key (id) +); + +CREATE INDEX processed_prices_index ON processed_prices USING btree(figi,processor, time); \ No newline at end of file