using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Threading.Channels; using Tinkoff.InvestApi; using Tinkoff.InvestApi.V1; namespace KLHZ.Trader.Core.Exchange.Services { public class TradingCommandsExecutor : IHostedService { private readonly TraderDataProvider _tradeDataProvider; private readonly InvestApiClient _investApiClient; private readonly IDataBus _dataBus; private readonly ILogger _logger; private readonly Channel _channel = Channel.CreateUnbounded(); public TradingCommandsExecutor(InvestApiClient investApiClient, IDataBus dataBus, ILogger logger, TraderDataProvider tradeDataProvider) { _investApiClient = investApiClient; _dataBus = dataBus; _dataBus.AddChannel(nameof(TradingCommandsExecutor), _channel); _logger = logger; _tradeDataProvider = tradeDataProvider; } public Task StartAsync(CancellationToken cancellationToken) { _ = ProcessCommands(); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } internal async Task ExecuteCommand(ITradeCommand tradeCommand) { try { if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.CancelOrder && !string.IsNullOrEmpty(tradeCommand.OrderId)) { var res = await _investApiClient.Orders.CancelOrderAsync(new CancelOrderRequest() { AccountId = tradeCommand.AccountId, OrderId = tradeCommand.OrderId }); } else { var dir = OrderDirection.Unspecified; var orderType = OrderType.Unspecified; if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy) { dir = OrderDirection.Buy; orderType = OrderType.Market; } else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell) { dir = OrderDirection.Sell; orderType = OrderType.Market; } else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy && tradeCommand.RecomendPrice.HasValue) { dir = OrderDirection.Buy; orderType = OrderType.Limit; } else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell && tradeCommand.RecomendPrice.HasValue) { dir = OrderDirection.Sell; orderType = OrderType.Limit; } if (orderType == OrderType.Unspecified) { return; } var req = new PostOrderRequest() { AccountId = tradeCommand.AccountId, InstrumentId = tradeCommand.Figi, Direction = dir, Price = tradeCommand.RecomendPrice ?? 0, OrderType = orderType, Quantity = tradeCommand.Count, ConfirmMarginTrade = tradeCommand.EnableMargin, }; _logger.LogWarning("Получена команда c id {commandId} на операцию с активом {figi}! Тип заявки сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", tradeCommand.CommandId, req.InstrumentId, req.OrderType, req.Quantity, req.ConfirmMarginTrade); var res = await _investApiClient.Orders.PostOrderAsync(req); if ((tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy || tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell) && tradeCommand.RecomendPrice.HasValue) { _tradeDataProvider.Accounts[tradeCommand.AccountId].Orders[res.OrderId] = new Models.AssetsAccounting.Order() { AccountId = tradeCommand.AccountId, Figi = tradeCommand.Figi, OrderId = res.OrderId, Ticker = _tradeDataProvider.GetTickerByFigi(tradeCommand.Figi), Count = res.LotsRequested, Direction = (DealDirection)(int)dir, ExpirationTime = DateTime.UtcNow.AddMinutes(10), OpenDate = DateTime.UtcNow, Price = tradeCommand.RecomendPrice.Value, }; } _logger.LogWarning("Исполнена команда c id {commandId} на операцию с активом {figi}! Направление: {dir}; Число лотов: {lots};", tradeCommand.CommandId, res.Figi, res.Direction, res.LotsExecuted); } } catch (Exception ex) { _logger.LogError(ex, "Ошибка при покупке актива на счёт {acc}. figi: {figi}", tradeCommand.AccountId, tradeCommand.Figi); } tradeCommand.ExchangeObject?.Unlock(); } private async Task ProcessCommands() { while (await _channel.Reader.WaitToReadAsync()) { var command = await _channel.Reader.ReadAsync(); await ExecuteCommand(command); } } } }