132 lines
6.3 KiB
C#
132 lines
6.3 KiB
C#
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<TradingCommandsExecutor> _logger;
|
||
private readonly Channel<ITradeCommand> _channel = Channel.CreateUnbounded<ITradeCommand>();
|
||
|
||
public TradingCommandsExecutor(InvestApiClient investApiClient, IDataBus dataBus, ILogger<TradingCommandsExecutor> 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(2),
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|