восстановление получения данных о ценах и сделках
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<(DateTime[] timestamps, float[] prices)> GetData();
public ValueTask AddOrderbook(IOrderbook orderbook);
public long AsksCount { get; }
public long BidsCount { get; }
public decimal AsksCount { 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 long BidsCount => 1;
public decimal AsksCount => 1;
public decimal BidsCount => 1;
private readonly object _locker = new();
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
{
@ -32,7 +32,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
}
}
public long BidsCount
public decimal BidsCount
{
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;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
@ -18,12 +18,12 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
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 twav120s = new float[size];
var times = new DateTime[size];
var res = TradingEvent.None;
for (int shift = 0; shift < size; 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;
twav120s[size - 1 - shift] = twav120.value;
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)
{
var isCrossing = Lines.IsLinesCrossing(
@ -53,7 +57,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
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)
{
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
{
Unknown = 0,
StopBuy = 1,
OpenLong = 100,
CloseLong = 200,
OpenShort = 300,

View File

@ -3,6 +3,7 @@
public class ExchangeConfig
{
public bool ExchangeDataRecievingEnabled { get; set; }
public decimal StopBuyLengthMinuts { get; set; }
public decimal FutureComission { get; set; }
public decimal ShareComission { 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
{
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();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
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.Models;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -29,25 +30,26 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly IDataBus _dataBus;
private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ConcurrentDictionary<string, DateTime> BuyStops = new();
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
private readonly ITradingEventsDetector _tradingEventsDetector;
private readonly ILogger<Trader> _logger;
private readonly double _buyStopLength;
private readonly decimal _futureComission;
private readonly decimal _shareComission;
private readonly decimal _accountCashPart;
private readonly decimal _accountCashPartFutures;
private readonly decimal _defaultBuyPartOfAccount;
private readonly string[] _managedAccountsNamePatterns = [];
private readonly string[] _tradingInstrumentsFigis = [];
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
private readonly CancellationTokenSource _cts = new();
public Trader(
ILogger<Trader> logger,
ITradingEventsDetector tradingEventsDetector,
BotModeSwitcher botModeSwitcher,
IServiceProvider provider,
IOptions<ExchangeConfig> options,
@ -56,7 +58,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
InvestApiClient investApiClient)
{
_logger = logger;
_tradingEventsDetector = tradingEventsDetector;
_botModeSwitcher = botModeSwitcher;
_dataBus = dataBus;
_provider = provider;
@ -68,10 +69,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
_accountCashPart = options.Value.AccountCashPart;
_accountCashPartFutures = options.Value.AccountCashPartFutures;
_defaultBuyPartOfAccount = options.Value.DefaultBuyPartOfAccount;
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
_buyStopLength = (double)options.Value.StopBuyLengthMinuts;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
//await InitStops();
var accounts = await _investApiClient.GetAccounts(_managedAccountsNamePatterns);
var accountsList = new List<ManagedAccount>();
int i = 0;
@ -94,6 +98,20 @@ namespace KLHZ.Trader.Core.Exchange.Services
_dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
_ = ProcessPrices();
_ = 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()
@ -101,54 +119,86 @@ namespace KLHZ.Trader.Core.Exchange.Services
while (await _pricesChannel.Reader.WaitToReadAsync())
{
var message = await _pricesChannel.Reader.ReadAsync();
if (_historyCash.TryGetValue(message.Figi, out var data))
{
await data.AddData(message);
}
else
{
data = new PriceHistoryCacheUnit2(message.Figi, message);
_historyCash.TryAdd(message.Figi, data);
}
var result = await _tradingEventsDetector.Detect(data);
//if (_tradingInstrumentsFigis.Contains(message.Figi))
//{
// if (_historyCash.TryGetValue(message.Figi, out var unit))
// {
// await unit.AddData(message);
// }
// else
// {
// unit = new PriceHistoryCacheUnit2(message.Figi, message);
// _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)
{
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.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.LongOpen) == TradingEvent.LongOpen
// && !BuyStops.TryGetValue(message.Figi, out _))
// {
// 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.OpenLong,
// });
// }
}
// 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)
{
_cts.Cancel();
return Task.CompletedTask;
}

View File

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

View File

@ -8,6 +8,7 @@
},
"LokiUrl": "",
"ExchangeConfig": {
"StopBuyLengthMinuts": 20,
"ExchangeDataRecievingEnabled": true,
"Token": "",
"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\migration1.sql = KLHZ.Trader.Infrastructure\postgres\migration1.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
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "loki", "loki", "{63D21DAF-FDF0-4F2D-A671-E9E59BB0CA5B}"