восстановление получения данных о ценах и сделках
test / deploy_trader_prod (push) Successful in 4m33s Details

main
vlad zverzhkhovskiy 2025-09-02 17:10:46 +03:00
parent 8edb4351dd
commit 270e807591
14 changed files with 155 additions and 107 deletions

View File

@ -1,16 +0,0 @@
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos
{
public readonly struct TradingEventsDto
{
public readonly bool LongClose;
public readonly bool LongOpen;
public TradingEventsDto(bool longClose, bool longOpen)
{
LongClose = longClose;
LongOpen = longOpen;
}
public readonly static TradingEventsDto Empty = new TradingEventsDto(false, false);
}
}

View File

@ -9,7 +9,7 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
public ValueTask AddData(INewPrice priceChange); public ValueTask AddData(INewPrice priceChange);
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData(); public ValueTask<(DateTime[] timestamps, float[] prices)> GetData();
public ValueTask AddOrderbook(IOrderbook orderbook); public ValueTask AddOrderbook(IOrderbook orderbook);
public long AsksCount { get; } public decimal AsksCount { get; }
public long BidsCount { get; } public decimal BidsCount { get; }
} }
} }

View File

@ -1,9 +0,0 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{
public interface ITradingEventsDetector
{
public ValueTask<TradingEvent> Detect(IPriceHistoryCacheUnit unit);
}
}

View File

@ -20,8 +20,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
} }
} }
public long AsksCount => 1; public decimal AsksCount => 1;
public long BidsCount => 1; public decimal BidsCount => 1;
private readonly object _locker = new(); private readonly object _locker = new();
private readonly float[] Prices = new float[CacheMaxLength]; private readonly float[] Prices = new float[CacheMaxLength];

View File

@ -21,7 +21,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
} }
} }
public long AsksCount public decimal AsksCount
{ {
get get
{ {
@ -32,7 +32,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
} }
} }
public long BidsCount public decimal BidsCount
{ {
get get
{ {

View File

@ -1,14 +0,0 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Utils;
namespace KLHZ.Trader.Core.Math.Declisions.Services.EventsDetection
{
public class IntervalsTradingEventsDetector : ITradingEventsDetector
{
public ValueTask<TradingEvent> Detect(IPriceHistoryCacheUnit unit)
{
return ValueTask.FromResult(TwoPeriods.Detect(unit));
}
}
}

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Math.Common; using KLHZ.Trader.Core.Math.Common;
namespace KLHZ.Trader.Core.Math.Declisions.Utils namespace KLHZ.Trader.Core.Math.Declisions.Utils
@ -18,12 +18,12 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
return (startTime, sum / count); return (startTime, sum / count);
} }
public static TradingEventsDto CheckByWindowAverageMean(DateTime[] timestamps, float[] prices, int size, float meanfullStep = 3f) public static TradingEvent CheckByWindowAverageMean(DateTime[] timestamps, float[] prices, int size, float meanfullStep = 3f)
{ {
var twav15s = new float[size]; var twav15s = new float[size];
var twav120s = new float[size]; var twav120s = new float[size];
var times = new DateTime[size]; var times = new DateTime[size];
var res = TradingEvent.None;
for (int shift = 0; shift < size; shift++) for (int shift = 0; shift < size; shift++)
{ {
var twav15 = CalcTimeWindowAverageValue(timestamps, prices, 15, shift); var twav15 = CalcTimeWindowAverageValue(timestamps, prices, 15, shift);
@ -31,7 +31,11 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
twav15s[size - 1 - shift] = twav15.value; twav15s[size - 1 - shift] = twav15.value;
twav120s[size - 1 - shift] = twav120.value; twav120s[size - 1 - shift] = twav120.value;
times[size - 1 - shift] = twav120.time; times[size - 1 - shift] = twav120.time;
if (System.Math.Abs(twav120.value - prices[prices.Length - 1]) > 3 * meanfullStep)
{
res |= TradingEvent.StopBuy;
return res;
}
if (shift > 0) if (shift > 0)
{ {
var isCrossing = Lines.IsLinesCrossing( var isCrossing = Lines.IsLinesCrossing(
@ -53,7 +57,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
if (twav15s[size - 1 - shift] - twav15s[size - 1] >= meanfullStep) if (twav15s[size - 1 - shift] - twav15s[size - 1] >= meanfullStep)
{ {
return new TradingEventsDto(false, true); res |= TradingEvent.LongOpen;
res |= TradingEvent.ShortClose;
break;
} }
} }
@ -62,14 +68,16 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
if (twav15s[size - 1 - shift] - twav15s[size - 1] <= -meanfullStep) if (twav15s[size - 1 - shift] - twav15s[size - 1] <= -meanfullStep)
{ {
return new TradingEventsDto(true, false); res |= TradingEvent.LongClose;
res |= TradingEvent.ShortOpen;
break;
} }
} }
} }
} }
} }
return new TradingEventsDto(false, false); return res;
} }
} }

View File

@ -3,6 +3,7 @@
public enum DeclisionTradeAction public enum DeclisionTradeAction
{ {
Unknown = 0, Unknown = 0,
StopBuy = 1,
OpenLong = 100, OpenLong = 100,
CloseLong = 200, CloseLong = 200,
OpenShort = 300, OpenShort = 300,

View File

@ -3,6 +3,7 @@
public class ExchangeConfig public class ExchangeConfig
{ {
public bool ExchangeDataRecievingEnabled { get; set; } public bool ExchangeDataRecievingEnabled { get; set; }
public decimal StopBuyLengthMinuts { get; set; }
public decimal FutureComission { get; set; } public decimal FutureComission { get; set; }
public decimal ShareComission { get; set; } public decimal ShareComission { get; set; }
public decimal AccountCashPart { get; set; } public decimal AccountCashPart { get; set; }

View File

@ -132,10 +132,16 @@ namespace KLHZ.Trader.Core.Exchange.Services
await stream.RequestStream.WriteAsync(new MarketDataRequest await stream.RequestStream.WriteAsync(new MarketDataRequest
{ {
SubscribeLastPriceRequest = request, SubscribeLastPriceRequest = request,
SubscribeTradesRequest = tradesRequest,
SubscribeOrderBookRequest = bookRequest
}); });
await stream.RequestStream.WriteAsync(new MarketDataRequest
{
SubscribeTradesRequest = tradesRequest,
});
await stream.RequestStream.WriteAsync(new MarketDataRequest
{
SubscribeOrderBookRequest = bookRequest
});
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var pricesBuffer = new List<PriceChange>(); var pricesBuffer = new List<PriceChange>();

View File

@ -10,6 +10,7 @@ using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
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.Math.Declisions.Services.Cache; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -29,25 +30,26 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly IDataBus _dataBus; private readonly IDataBus _dataBus;
private readonly BotModeSwitcher _botModeSwitcher; private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory; private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ConcurrentDictionary<string, DateTime> BuyStops = new();
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new(); private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new(); private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
private readonly ITradingEventsDetector _tradingEventsDetector;
private readonly ILogger<Trader> _logger; private readonly ILogger<Trader> _logger;
private readonly double _buyStopLength;
private readonly decimal _futureComission; private readonly decimal _futureComission;
private readonly decimal _shareComission; private readonly decimal _shareComission;
private readonly decimal _accountCashPart; private readonly decimal _accountCashPart;
private readonly decimal _accountCashPartFutures; private readonly decimal _accountCashPartFutures;
private readonly decimal _defaultBuyPartOfAccount; private readonly decimal _defaultBuyPartOfAccount;
private readonly string[] _managedAccountsNamePatterns = []; private readonly string[] _managedAccountsNamePatterns = [];
private readonly string[] _tradingInstrumentsFigis = [];
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>(); private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>(); private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
private readonly CancellationTokenSource _cts = new();
public Trader( public Trader(
ILogger<Trader> logger, ILogger<Trader> logger,
ITradingEventsDetector tradingEventsDetector,
BotModeSwitcher botModeSwitcher, BotModeSwitcher botModeSwitcher,
IServiceProvider provider, IServiceProvider provider,
IOptions<ExchangeConfig> options, IOptions<ExchangeConfig> options,
@ -56,7 +58,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
InvestApiClient investApiClient) InvestApiClient investApiClient)
{ {
_logger = logger; _logger = logger;
_tradingEventsDetector = tradingEventsDetector;
_botModeSwitcher = botModeSwitcher; _botModeSwitcher = botModeSwitcher;
_dataBus = dataBus; _dataBus = dataBus;
_provider = provider; _provider = provider;
@ -68,10 +69,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
_accountCashPart = options.Value.AccountCashPart; _accountCashPart = options.Value.AccountCashPart;
_accountCashPartFutures = options.Value.AccountCashPartFutures; _accountCashPartFutures = options.Value.AccountCashPartFutures;
_defaultBuyPartOfAccount = options.Value.DefaultBuyPartOfAccount; _defaultBuyPartOfAccount = options.Value.DefaultBuyPartOfAccount;
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
_buyStopLength = (double)options.Value.StopBuyLengthMinuts;
} }
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
//await InitStops();
var accounts = await _investApiClient.GetAccounts(_managedAccountsNamePatterns); var accounts = await _investApiClient.GetAccounts(_managedAccountsNamePatterns);
var accountsList = new List<ManagedAccount>(); var accountsList = new List<ManagedAccount>();
int i = 0; int i = 0;
@ -94,6 +98,20 @@ namespace KLHZ.Trader.Core.Exchange.Services
_dataBus.AddChannel(nameof(Trader), _ordersbookChannel); _dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
_ = ProcessPrices(); _ = ProcessPrices();
_ = ProcessOrdersbooks(); _ = ProcessOrdersbooks();
_ = BackgroundWorker();
}
private async Task InitStops()
{
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var dt = DateTime.UtcNow.AddMinutes(-_buyStopLength);
var stops = await context.Declisions.Where(d => d.Time > dt && d.Action == DeclisionTradeAction.StopBuy).ToArrayAsync();
foreach (var stop in stops)
{
var time = stop.Time.AddMinutes(_buyStopLength);
BuyStops.TryAdd(stop.Figi, time);
}
} }
private async Task ProcessPrices() private async Task ProcessPrices()
@ -101,54 +119,86 @@ namespace KLHZ.Trader.Core.Exchange.Services
while (await _pricesChannel.Reader.WaitToReadAsync()) while (await _pricesChannel.Reader.WaitToReadAsync())
{ {
var message = await _pricesChannel.Reader.ReadAsync(); var message = await _pricesChannel.Reader.ReadAsync();
if (_historyCash.TryGetValue(message.Figi, out var data)) //if (_tradingInstrumentsFigis.Contains(message.Figi))
{ //{
await data.AddData(message); // if (_historyCash.TryGetValue(message.Figi, out var unit))
} // {
else // await unit.AddData(message);
{ // }
data = new PriceHistoryCacheUnit2(message.Figi, message); // else
_historyCash.TryAdd(message.Figi, data); // {
} // unit = new PriceHistoryCacheUnit2(message.Figi, message);
var result = await _tradingEventsDetector.Detect(data); // _historyCash.TryAdd(message.Figi, unit);
// }
// var data = await unit.GetData();
// var declisionsForSave = new List<Declision>();
// if (message.Figi == "FUTIMOEXF000")
// {
// var result = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, 100, 3f);
// if ((result & TradingEvent.StopBuy) == TradingEvent.StopBuy)
// {
// var stopTo = DateTime.UtcNow.AddMinutes(_buyStopLength);
// BuyStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
// declisionsForSave.Add(new Declision()
// {
// AccountId = string.Empty,
// Figi = message.Figi,
// Ticker = message.Ticker,
// Price = message.Value,
// Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
// Action = DeclisionTradeAction.StopBuy,
// });
// }
try // if ((result & TradingEvent.LongOpen) == TradingEvent.LongOpen
{ // && !BuyStops.TryGetValue(message.Figi, out _))
if ((result & TradingEvent.LongOpen) == TradingEvent.LongOpen) // {
{ // var stopTo = DateTime.UtcNow.AddMinutes(_buyStopLength);
using var context = await _dbContextFactory.CreateDbContextAsync(); // BuyStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; // declisionsForSave.Add(new Declision()
await context.Declisions.AddAsync(new Declision() // {
{ // AccountId = string.Empty,
AccountId = string.Empty, // Figi = message.Figi,
Figi = message.Figi, // Ticker = message.Ticker,
Ticker = message.Ticker, // Price = message.Value,
Price = message.Value, // Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow, // Action = DeclisionTradeAction.OpenLong,
Action = DeclisionTradeAction.OpenLong, // });
}); // }
await context.SaveChangesAsync();
}
if ((result & TradingEvent.LongClose) == TradingEvent.LongClose)
{
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
await context.Declisions.AddAsync(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Price = message.Value,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Action = DeclisionTradeAction.CloseLong,
});
await context.SaveChangesAsync();
}
}
catch (Exception ex)
{
} // if ((result & TradingEvent.LongClose) == TradingEvent.LongClose)
// {
// declisionsForSave.Add(new Declision()
// {
// AccountId = string.Empty,
// Figi = message.Figi,
// Ticker = message.Ticker,
// Price = message.Value,
// Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
// Action = DeclisionTradeAction.CloseLong,
// });
// }
// if ((result & TradingEvent.ShortOpen) == TradingEvent.ShortOpen && (unit.AsksCount/ unit.BidsCount>2 ))
// {
// declisionsForSave.Add(new Declision()
// {
// AccountId = string.Empty,
// Figi = message.Figi,
// Ticker = message.Ticker,
// Price = message.Value,
// Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
// Action = DeclisionTradeAction.OpenShort,
// });
// }
// using var context = await _dbContextFactory.CreateDbContextAsync();
// context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
// await context.AddRangeAsync(declisionsForSave);
// await context.SaveChangesAsync();
// }
//}
} }
} }
@ -166,8 +216,30 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
} }
private async Task BackgroundWorker()
{
var keysForRemove = new List<string>();
while (!_cts.IsCancellationRequested)
{
var time = DateTime.UtcNow;
foreach (var kvp in BuyStops)
{
if (kvp.Value > time)
{
keysForRemove.Add(kvp.Key);
}
}
foreach (var key in keysForRemove)
{
BuyStops.TryRemove(key, out _);
}
await Task.Delay(10000);
}
}
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
_cts.Cancel();
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -1,11 +1,9 @@
using KLHZ.Trader.Core.Common; using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Common.Messaging.Services; using KLHZ.Trader.Core.Common.Messaging.Services;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
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.Exchange; using KLHZ.Trader.Core.Exchange;
using KLHZ.Trader.Core.Exchange.Services; using KLHZ.Trader.Core.Exchange.Services;
using KLHZ.Trader.Core.Math.Declisions.Services.EventsDetection;
using KLHZ.Trader.Core.TG; using KLHZ.Trader.Core.TG;
using KLHZ.Trader.Core.TG.Services; using KLHZ.Trader.Core.TG.Services;
using KLHZ.Trader.Service.Infrastructure; using KLHZ.Trader.Service.Infrastructure;
@ -53,7 +51,6 @@ builder.Services.AddHostedService<Trader>();
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>(); builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
builder.Services.AddSingleton<BotModeSwitcher>(); builder.Services.AddSingleton<BotModeSwitcher>();
builder.Services.AddSingleton<IDataBus, DataBus>(); builder.Services.AddSingleton<IDataBus, DataBus>();
builder.Services.AddSingleton<ITradingEventsDetector, IntervalsTradingEventsDetector>();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {

View File

@ -8,6 +8,7 @@
}, },
"LokiUrl": "", "LokiUrl": "",
"ExchangeConfig": { "ExchangeConfig": {
"StopBuyLengthMinuts": 20,
"ExchangeDataRecievingEnabled": true, "ExchangeDataRecievingEnabled": true,
"Token": "", "Token": "",
"ManagingAccountNamePatterns": [ "автотрейд 1" ], "ManagingAccountNamePatterns": [ "автотрейд 1" ],

View File

@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postrgres", "postrgres", "{
KLHZ.Trader.Infrastructure\postgres\init.sql = KLHZ.Trader.Infrastructure\postgres\init.sql KLHZ.Trader.Infrastructure\postgres\init.sql = KLHZ.Trader.Infrastructure\postgres\init.sql
KLHZ.Trader.Infrastructure\postgres\migration1.sql = KLHZ.Trader.Infrastructure\postgres\migration1.sql KLHZ.Trader.Infrastructure\postgres\migration1.sql = KLHZ.Trader.Infrastructure\postgres\migration1.sql
KLHZ.Trader.Infrastructure\postgres\migration2.sql = KLHZ.Trader.Infrastructure\postgres\migration2.sql KLHZ.Trader.Infrastructure\postgres\migration2.sql = KLHZ.Trader.Infrastructure\postgres\migration2.sql
KLHZ.Trader.Infrastructure\postgres\migration3.sql = KLHZ.Trader.Infrastructure\postgres\migration3.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}"