рефакторинг + запись сделок по инструментам
test / deploy_trader_prod (push) Successful in 3m16s Details

main
vlad zverzhkhovskiy 2025-09-01 14:58:43 +03:00
parent 2d6bc10ed8
commit cee02fe665
29 changed files with 554 additions and 147 deletions

View File

@ -5,7 +5,8 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
public interface IPriceHistoryCacheUnit public interface IPriceHistoryCacheUnit
{ {
public string Figi { get; } 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(); public ValueTask<(DateTime[] timestamps, float[] prices)> GetData();
} }

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
{ {
public interface INewPriceMessage public interface INewPrice
{ {
public bool IsHistoricalData { get; set; } public bool IsHistoricalData { get; set; }
public decimal Value { get; set; } public decimal Value { get; set; }

View File

@ -0,0 +1,7 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
{
public interface IProcessedPrice : INewPrice
{
public string Processor { get; set; }
}
}

View File

@ -2,7 +2,7 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{ {
public class NewPriceMessage : INewPriceMessage public class NewPriceMessage : INewPrice
{ {
public decimal Value { get; set; } public decimal Value { get; set; }
public required string Figi { get; set; } public required string Figi { get; set; }

View File

@ -6,12 +6,14 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces
{ {
public interface IDataBus public interface IDataBus
{ {
public bool AddChannel(string key, Channel<INewPriceMessage> channel); public bool AddChannel(string key, Channel<IProcessedPrice> channel);
public bool AddChannel(string key, Channel<INewPrice> channel);
public bool AddChannel(string key, Channel<TradeCommand> channel); public bool AddChannel(string key, Channel<TradeCommand> channel);
public bool AddChannel(string key, Channel<IMessage> channel); public bool AddChannel(string key, Channel<IMessage> channel);
public bool AddChannel(string key, Channel<INewCandle> channel); public bool AddChannel(string key, Channel<INewCandle> channel);
public Task BroadcastNewPrice(INewPriceMessage newPriceMessage); public Task BroadcastNewPrice(INewPrice newPriceMessage);
public Task BroadcastCommand(TradeCommand command); public Task BroadcastCommand(TradeCommand command);
public Task BroadcastNewCandle(INewCandle command); public Task BroadcastNewCandle(INewCandle command);
public Task BroadcastProcessedPrice(IProcessedPrice command);
} }
} }

View File

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

View File

@ -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<INewPrice> _messages = Channel.CreateUnbounded<INewPrice>();
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<double>.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<double>.Build.Dense(2, 2, new[] { 1d, 0d, dt, 1 }); // State transition matrix
var R = Matrix<double>.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<double>.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<double> x0 = Matrix<double>.Build.Dense(2, 1, new[] { b, k });
Matrix<double> P0 = Matrix<double>.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)
{
}
}
}
}
}

View File

@ -1,7 +1,7 @@
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; 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 public class PriceHistoryCacheUnit : IPriceHistoryCacheUnit
{ {
@ -9,13 +9,24 @@ namespace KLHZ.Trader.Core.Declisions.Services
public string Figi { get; init; } public string Figi { get; init; }
public int Length
{
get
{
lock (_locker)
{
return _length;
}
}
}
private readonly object _locker = new(); private readonly object _locker = new();
private readonly float[] Prices = new float[ArrayMaxLength]; private readonly float[] Prices = new float[ArrayMaxLength];
private readonly DateTime[] Timestamps = new DateTime[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) lock (_locker)
{ {
@ -25,9 +36,9 @@ namespace KLHZ.Trader.Core.Declisions.Services
Prices[Prices.Length - 1] = (float)priceChange.Value; Prices[Prices.Length - 1] = (float)priceChange.Value;
Timestamps[Timestamps.Length - 1] = priceChange.Time; Timestamps[Timestamps.Length - 1] = priceChange.Time;
if (Length < ArrayMaxLength) if (_length < ArrayMaxLength)
{ {
Length++; _length++;
} }
} }
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
@ -35,17 +46,17 @@ namespace KLHZ.Trader.Core.Declisions.Services
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData() public ValueTask<(DateTime[] timestamps, float[] prices)> GetData()
{ {
var prices = new float[Length];
var timestamps = new DateTime[Length];
lock (_locker) lock (_locker)
{ {
Array.Copy(Prices, Prices.Length - Length, prices, 0, prices.Length); var prices = new float[_length];
Array.Copy(Timestamps, Prices.Length - Length, timestamps, 0, timestamps.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)); return ValueTask.FromResult((timestamps, prices));
} }
} }
public PriceHistoryCacheUnit(string figi, params INewPriceMessage[] priceChanges) public PriceHistoryCacheUnit(string figi, params INewPrice[] priceChanges)
{ {
Figi = figi; 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(prices, 0, Prices, Prices.Length - prices.Length, prices.Length);
Array.Copy(times, 0, Timestamps, Timestamps.Length - times.Length, times.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;
} }
} }
} }

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MathNet.Filtering.Kalman" Version="0.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,4 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Declisions.Services;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests
{ {

View File

@ -1,5 +1,4 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Declisions.Services;
using KLHZ.Trader.Core.Declisions.Utils; using KLHZ.Trader.Core.Declisions.Utils;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests

View File

@ -10,15 +10,21 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
{ {
private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new(); private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewCandle>> _candlesChannels = new(); private readonly ConcurrentDictionary<string, Channel<INewCandle>> _candlesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewPriceMessage>> _priceChannels = new(); private readonly ConcurrentDictionary<string, Channel<INewPrice>> _priceChannels = new();
private readonly ConcurrentDictionary<string, Channel<IProcessedPrice>> _processedPricesChannels = new();
private readonly ConcurrentDictionary<string, Channel<TradeCommand>> _commandChannels = new(); private readonly ConcurrentDictionary<string, Channel<TradeCommand>> _commandChannels = new();
public bool AddChannel(string key, Channel<IProcessedPrice> channel)
{
return _processedPricesChannels.TryAdd(key, channel);
}
public bool AddChannel(string key, Channel<IMessage> channel) public bool AddChannel(string key, Channel<IMessage> channel)
{ {
return _messagesChannels.TryAdd(key, channel); return _messagesChannels.TryAdd(key, channel);
} }
public bool AddChannel(string key, Channel<INewPriceMessage> channel) public bool AddChannel(string key, Channel<INewPrice> channel)
{ {
return _priceChannels.TryAdd(key, channel); return _priceChannels.TryAdd(key, channel);
} }
@ -33,7 +39,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
return _commandChannels.TryAdd(key, channel); return _commandChannels.TryAdd(key, channel);
} }
public async Task BroadcastNewPrice(INewPriceMessage newPriceMessage) public async Task BroadcastNewPrice(INewPrice newPriceMessage)
{ {
foreach (var channel in _priceChannels.Values) 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) public async Task BroadcastNewCandle(INewCandle newPriceMessage)
{ {
foreach (var channel in _candlesChannels.Values) foreach (var channel in _candlesChannels.Values)

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
{ {
[Table("price_changes")] [Table("price_changes")]
public class PriceChange : INewPriceMessage public class PriceChange : INewPrice
{ {
[Column("id")] [Column("id")]
public long Id { get; set; } public long Id { get; set; }

View File

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

View File

@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
{
/// <summary>
/// Обезличенная сделка с биржи над инструментом.
/// </summary>
[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; }
}
}

View File

@ -2,6 +2,9 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
{ {
/// <summary>
/// Сделка, совершенная ботом.
/// </summary>
[Table("trades")] [Table("trades")]
public class Trade public class Trade
{ {

View File

@ -3,7 +3,7 @@
public enum TradeDirection public enum TradeDirection
{ {
Unknown = 0, Unknown = 0,
Income = 1, Buy = 1,
Outcome = 2 Sell = 2
} }
} }

View File

@ -9,8 +9,10 @@ namespace KLHZ.Trader.Core.DataLayer
public class TraderDbContext : DbContext public class TraderDbContext : DbContext
{ {
public DbSet<Trade> Trades { get; set; } public DbSet<Trade> Trades { get; set; }
public DbSet<InstrumentTrade> InstrumentTrades { get; set; }
public DbSet<Declision> Declisions { get; set; } public DbSet<Declision> Declisions { get; set; }
public DbSet<PriceChange> PriceChanges { get; set; } public DbSet<PriceChange> PriceChanges { get; set; }
public DbSet<ProcessedPrice> ProcessedPrices { get; set; }
public DbSet<Candle> Candles { get; set; } public DbSet<Candle> Candles { get; set; }
public TraderDbContext(DbContextOptions<TraderDbContext> options) public TraderDbContext(DbContextOptions<TraderDbContext> options)
: base(options) : base(options)
@ -30,6 +32,15 @@ namespace KLHZ.Trader.Core.DataLayer
v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
}); });
modelBuilder.Entity<InstrumentTrade>(entity =>
{
entity.HasKey(e1 => e1.Id);
entity.Property(e => e.BoughtAt)
.HasConversion(
v => v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
});
modelBuilder.Entity<Declision>(entity => modelBuilder.Entity<Declision>(entity =>
{ {
entity.HasKey(e1 => e1.Id); entity.HasKey(e1 => e1.Id);
@ -49,6 +60,16 @@ namespace KLHZ.Trader.Core.DataLayer
v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
}); });
modelBuilder.Entity<ProcessedPrice>(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<Candle>(entity => modelBuilder.Entity<Candle>(entity =>
{ {
entity.HasKey(e1 => new { e1.Figi, e1.Time }); entity.HasKey(e1 => new { e1.Figi, e1.Time });

View File

@ -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<ProcessedPricesLogger> _logger;
private readonly IDataBus _dataBus;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly Channel<IProcessedPrice> _channel = Channel.CreateUnbounded<IProcessedPrice>();
public ProcessedPricesLogger(IDataBus dataBus, IDbContextFactory<TraderDbContext> dbContextFactory, ILogger<ProcessedPricesLogger> 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<DataLayer.Entities.Prices.ProcessedPrice>();
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;
}
}
}

View File

@ -5,11 +5,11 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions; using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.Declisions.Utils;
using KLHZ.Trader.Core.Exchange; using KLHZ.Trader.Core.Exchange;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models; using KLHZ.Trader.Core.Exchange.Models;
using KLHZ.Trader.Core.Exchange.Services; using KLHZ.Trader.Core.Exchange.Services;
using KLHZ.Trader.Core.Math.Declisions.Dtos.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -29,7 +29,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly BotModeSwitcher _botModeSwitcher; private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory; private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new(); private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, PriceHistoryCacheUnit> _historyCash = new(); private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
private readonly ITradingEventsDetector _tradingEventsDetector; private readonly ITradingEventsDetector _tradingEventsDetector;
@ -40,7 +40,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly decimal _defaultBuyPartOfAccount; private readonly decimal _defaultBuyPartOfAccount;
private readonly string[] _managedAccountsNamePatterns = []; private readonly string[] _managedAccountsNamePatterns = [];
private readonly Channel<INewPriceMessage> _pricesChannel = Channel.CreateUnbounded<INewPriceMessage>(); private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
public Trader( public Trader(
ITradingEventsDetector tradingEventsDetector, ITradingEventsDetector tradingEventsDetector,
@ -89,7 +89,6 @@ namespace KLHZ.Trader.Core.Declisions.Services
_ = ProcessMessages(); _ = ProcessMessages();
} }
private async Task ProcessMessages() private async Task ProcessMessages()
{ {
while (await _pricesChannel.Reader.WaitToReadAsync()) while (await _pricesChannel.Reader.WaitToReadAsync())
@ -97,7 +96,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
var message = await _pricesChannel.Reader.ReadAsync(); var message = await _pricesChannel.Reader.ReadAsync();
if (_historyCash.TryGetValue(message.Figi, out var data)) if (_historyCash.TryGetValue(message.Figi, out var data))
{ {
data.AddData(message); await data.AddData(message);
} }
else 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) public Task StopAsync(CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -1,6 +1,5 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.Declisions.Utils; using KLHZ.Trader.Core.Declisions.Utils;
namespace KLHZ.Trader.Core.Declisions.Services 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); var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8);
uptrendEnds |= uptrendEnds2; 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); return new TradingEventsDto(uptrendEnds, uptrendStarts);
} }
} }

View File

@ -1,6 +1,6 @@
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Declisions.Dtos; 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 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) internal static bool CheckStable(this PeriodPricesInfoDto data, float meanfullDiff)
{ {
meanfullDiff = Math.Abs(meanfullDiff); meanfullDiff = System.Math.Abs(meanfullDiff);
return data.Success && Math.Abs(data.PeriodDiff) < 1.5 * meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * 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) 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) internal static bool CheckFalling(this PeriodPricesInfoDto data, float meanfullDiff)
{ {
meanfullDiff = -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) internal static float CalcTrendRelationAbs(PeriodPricesInfoDto first, PeriodPricesInfoDto second)
{ {
var k1 = Math.Abs(first.PeriodDiff) / Math.Abs(first.Period.TotalSeconds); var k1 = System.Math.Abs(first.PeriodDiff) / System.Math.Abs(first.Period.TotalSeconds);
var k2 = Math.Abs(second.PeriodDiff) / Math.Abs(second.Period.TotalSeconds); var k2 = System.Math.Abs(second.PeriodDiff) / System.Math.Abs(second.Period.TotalSeconds);
if (k2 == 0 && k1 != 0) return 1000; if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2); return (float)(k1 / k2);
} }
internal static float CalcTrendRelationAbs(TwoPeriodsProcessingDto data) internal static float CalcTrendRelationAbs(TwoPeriodsProcessingDto data)
{ {
var k1 = Math.Abs(data.DiffStart) / Math.Abs(data.PeriodStart.TotalSeconds); var k1 = System.Math.Abs(data.DiffStart) / System.Math.Abs(data.PeriodStart.TotalSeconds);
var k2 = Math.Abs(data.DiffEnd) / Math.Abs(data.PeriodEnd.TotalSeconds); var k2 = System.Math.Abs(data.DiffEnd) / System.Math.Abs(data.PeriodEnd.TotalSeconds);
if (k2 == 0 && k1 != 0) return 1000; if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2); return (float)(k1 / k2);
} }

View File

@ -2,6 +2,7 @@
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.DataLayer.Entities.Trades;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -10,7 +11,6 @@ using Microsoft.Extensions.Options;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Tinkoff.InvestApi; using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1; using Tinkoff.InvestApi.V1;
using Candle = KLHZ.Trader.Core.DataLayer.Entities.Prices.Candle;
namespace KLHZ.Trader.Core.Exchange.Services namespace KLHZ.Trader.Core.Exchange.Services
{ {
@ -95,6 +95,12 @@ namespace KLHZ.Trader.Core.Exchange.Services
SubscriptionAction = SubscriptionAction.Subscribe SubscriptionAction = SubscriptionAction.Subscribe
}; };
var tradesRequest = new SubscribeTradesRequest
{
SubscriptionAction = SubscriptionAction.Subscribe
};
foreach (var f in _instrumentsFigis) foreach (var f in _instrumentsFigis)
{ {
request.Instruments.Add( request.Instruments.Add(
@ -102,6 +108,12 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
InstrumentId = f InstrumentId = f
}); });
tradesRequest.Instruments.Add(
new TradeInstrument()
{
InstrumentId = f
});
} }
await stream.RequestStream.WriteAsync(new MarketDataRequest await stream.RequestStream.WriteAsync(new MarketDataRequest
@ -109,6 +121,17 @@ namespace KLHZ.Trader.Core.Exchange.Services
SubscribeLastPriceRequest = request, 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<PriceChange>();
var tradesBuffer = new List<InstrumentTrade>();
var lastWritePrices = DateTime.UtcNow;
var lastWriteTrades = DateTime.UtcNow;
await foreach (var response in stream.ResponseStream.ReadAllAsync()) await foreach (var response in stream.ResponseStream.ReadAllAsync())
{ {
if (response.LastPrice != null) if (response.LastPrice != null)
@ -122,63 +145,41 @@ namespace KLHZ.Trader.Core.Exchange.Services
IsHistoricalData = false, IsHistoricalData = false,
}; };
await _eventBus.BroadcastNewPrice(message); await _eventBus.BroadcastNewPrice(message);
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; pricesBuffer.Add(message);
await context.PriceChanges.AddAsync(message);
await context.SaveChangesAsync();
} }
} if (response.Trade != null)
}
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)
{ {
var message = new Candle() var trade = new KLHZ.Trader.Core.DataLayer.Entities.Trades.InstrumentTrade()
{ {
Figi = response.Candle.Figi, Figi = response.Trade.Figi,
Ticker = GetTickerByFigi(response.LastPrice.Figi), BoughtAt = response.Trade.Time.ToDateTime().ToUniversalTime(),
Time = response.Candle.Time.ToDateTime().ToUniversalTime(), Ticker = GetTickerByFigi(response.Trade.Figi),
Close = response.Candle.Close, Price = response.Trade.Price,
Open = response.Candle.Open, Count = response.Trade.Quantity,
Low = response.Candle.Low, 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,
High = response.Candle.High,
Volume = response.Candle.Volume,
IsHistoricalData = false,
}; };
await _eventBus.BroadcastNewCandle(message); tradesBuffer.Add(trade);
using var context = await _dbContextFactory.CreateDbContextAsync(); }
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
await context.Candles.AddAsync(message);
//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(); await context.SaveChangesAsync();
} }
} }
} }
private string GetTickerByFigi(string figi) private string GetTickerByFigi(string figi)
{ {
return _tickersCache.TryGetValue(figi, out var ticker) ? ticker : string.Empty; return _tickersCache.TryGetValue(figi, out var ticker) ? ticker : string.Empty;

View File

@ -237,7 +237,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Count = res.LotsExecuted, Count = res.LotsExecuted,
Price = res.ExecutedOrderPrice, Price = res.ExecutedOrderPrice,
Position = DataLayer.Entities.Trades.PositionType.Long, Position = DataLayer.Entities.Trades.PositionType.Long,
Direction = DataLayer.Entities.Trades.TradeDirection.Income, Direction = DataLayer.Entities.Trades.TradeDirection.Buy,
Asset = DataLayer.Entities.Trades.AssetType.Common, Asset = DataLayer.Entities.Trades.AssetType.Common,
}; };

View File

@ -12,10 +12,12 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="Telegram.Bot" Version="22.6.0" /> <PackageReference Include="Telegram.Bot" Version="22.6.0" />
<PackageReference Include="Tinkoff.InvestApi" Version="0.6.17" /> <PackageReference Include="Tinkoff.InvestApi" Version="0.6.17" />
<PackageReference Include="MathNet.Filtering.Kalman" Version="0.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj" /> <ProjectReference Include="..\KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj" />
<ProjectReference Include="..\KLHZ.Trader.Core.Math\KLHZ.Trader.Core.Math.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -22,24 +22,31 @@ namespace KLHZ.Trader.Service.Controllers
[HttpGet] [HttpGet]
public async Task Run(string figi) public async Task Run(string figi)
{ {
using var context1 = await _dbContextFactory.CreateDbContextAsync(); try
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); 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)
{
} }
} }
} }

View File

@ -46,6 +46,9 @@ builder.Services.AddDbContextFactory<TraderDbContext>(options =>
builder.Services.AddHostedService<BotStarter>(); builder.Services.AddHostedService<BotStarter>();
builder.Services.AddHostedService<ExchangeDataReader>(); builder.Services.AddHostedService<ExchangeDataReader>();
builder.Services.AddHostedService<Trader>(); builder.Services.AddHostedService<Trader>();
//builder.Services.AddHostedService<ProcessedPricesLogger>();
//builder.Services.AddHostedService<KalmanPredictor>();
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>(); builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
builder.Services.AddSingleton<BotModeSwitcher>(); builder.Services.AddSingleton<BotModeSwitcher>();

View File

@ -26,6 +26,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postrgres", "postrgres", "{174A800A-6040-40CF-B331-8603E097CBAC}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postrgres", "postrgres", "{174A800A-6040-40CF-B331-8603E097CBAC}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
KLHZ.Trader.Infrastructure\postgres\init.sql = KLHZ.Trader.Infrastructure\postgres\init.sql KLHZ.Trader.Infrastructure\postgres\init.sql = KLHZ.Trader.Infrastructure\postgres\init.sql
migration1.sql = migration1.sql
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "loki", "loki", "{63D21DAF-FDF0-4F2D-A671-E9E59BB0CA5B}" 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 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}" 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 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

12
migration1.sql Normal file
View File

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