From 0f5284f4720625a41fbf871162acc86e625544ae Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Wed, 17 Sep 2025 11:01:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D1=83=20?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/Interfaces/ILockableObject.cs | 15 ++ .../Dtos/Interfaces/ITradeCommand.cs | 1 + .../Messaging/Dtos/TradeCommand.cs | 1 + KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs | 2 +- KLHZ.Trader.Core.Tests/AssetTests.cs | 26 +++ .../Exchange/Models/AssetsAccounting/Asset.cs | 2 +- .../AssetsAccounting/LockedExchangeObject.cs | 35 +++ .../Models/AssetsAccounting/ManagedAccount.cs | 214 +----------------- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 160 +++++++------ .../Services/TradingCommandsExecutor.cs | 1 + 10 files changed, 168 insertions(+), 289 deletions(-) create mode 100644 KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ILockableObject.cs create mode 100644 KLHZ.Trader.Core.Tests/AssetTests.cs create mode 100644 KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/LockedExchangeObject.cs diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ILockableObject.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ILockableObject.cs new file mode 100644 index 0000000..690241a --- /dev/null +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ILockableObject.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces +{ + public interface ILockableObject + { + public Task Lock(TimeSpan duration); + + public void Unlock(); + } +} diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs index 9903f42..e3e8494 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs @@ -11,5 +11,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces public long Count { get; } public string AccountId { get; } public bool EnableMargin { get; } + public ILockableObject? ExchangeObject { get; } } } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs index 5550df0..5da9d04 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs @@ -12,5 +12,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos public long Count { get; init; } public required string AccountId { get; init; } public bool EnableMargin { get; init; } = true; + public ILockableObject? ExchangeObject { get; init; } } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs index e1024df..842f049 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs @@ -15,7 +15,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils { return ValueAmplitudePosition.UpperThen30Decil; } - else if (value < fftData.Mediana && System.Math.Sign(value2)>=0) + else if (value < fftData.Mediana && System.Math.Sign(value2) >= 0) { return ValueAmplitudePosition.LowerThenMediana; } diff --git a/KLHZ.Trader.Core.Tests/AssetTests.cs b/KLHZ.Trader.Core.Tests/AssetTests.cs new file mode 100644 index 0000000..975b911 --- /dev/null +++ b/KLHZ.Trader.Core.Tests/AssetTests.cs @@ -0,0 +1,26 @@ +using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; + +namespace KLHZ.Trader.Core.Tests +{ + public class AssetTests + { + [Test] + public void Test1() + { + var asset = new Asset() { AccountId = "", Figi = "", Ticker = "" }; + var dur = TimeSpan.FromSeconds(5); + Assert.IsTrue(asset.Lock(dur).Result); + Assert.IsFalse(asset.Lock(dur).Result); + } + + [Test] + public void Test2() + { + var asset = new Asset() { AccountId = "", Figi = "", Ticker = "" }; + var dur = TimeSpan.FromSeconds(5); + Assert.IsTrue(asset.Lock(dur).Result); + Task.Delay(dur + dur).Wait(); + Assert.IsTrue(asset.Lock(dur).Result); + } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/Asset.cs b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/Asset.cs index af38b82..19638fb 100644 --- a/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/Asset.cs +++ b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/Asset.cs @@ -1,6 +1,6 @@ namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting { - public class Asset + public class Asset : LockableExchangeObject { public long? TradeId { get; init; } public decimal BlockedItems { get; init; } diff --git a/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/LockedExchangeObject.cs b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/LockedExchangeObject.cs new file mode 100644 index 0000000..f315982 --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/LockedExchangeObject.cs @@ -0,0 +1,35 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; + +namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting +{ + public abstract class LockableExchangeObject : ILockableObject + { + private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); + + public Task Lock(TimeSpan duration) + { + var lockerTask = _sem.WaitAsync(0); + _ = lockerTask.ContinueWith(async (t) => + { + if (t.Result) + { + await Task.Delay(duration); + _sem.Release(); + } + }); + return lockerTask; + } + + public void Unlock() + { + try + { + _sem.Release(); + } + catch + { + + } + } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/ManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/ManagedAccount.cs index 4b8f822..7132619 100644 --- a/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/ManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Models/AssetsAccounting/ManagedAccount.cs @@ -2,7 +2,7 @@ namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting { - public class ManagedAccount + public class ManagedAccount : LockableExchangeObject { public readonly string AccountId; private readonly object _locker = new(); @@ -42,217 +42,5 @@ namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting { AccountId = accountId; } - - // private async Task ProcessCommands() - // { - // while (await _channel.Reader.WaitToReadAsync()) - // { - // var command = await _channel.Reader.ReadAsync(); - // try - // { - // await ProcessMarketCommand(command); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Ошибка при обработке команды."); - // } - // } - // } - - // internal async Task SyncPortfolio() - // { - // try - // { - // //await _semaphoreSlim.WaitAsync(); - // var portfolio = await _investApiClient.Operations.GetPortfolioAsync(new PortfolioRequest() - // { - // AccountId = AccountId, - // }); - - // var oldAssets = Assets.Keys.ToHashSet(); - // using var context = await _dbContextFactory.CreateDbContextAsync(); - // context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - - // var trades = await context.Trades - // .Where(t => t.AccountId == AccountId && t.ArchiveStatus == 0) - // .ToListAsync(); - // foreach (var position in portfolio.Positions) - // { - // decimal price = 0; - // var trade = trades.FirstOrDefault(t => t.Figi == position.Figi); - - // if (trade != null) - // { - // trades.Remove(trade); - // price = trade.Price; - // } - // else - // { - // price = position.AveragePositionPrice; - // } - - //#pragma warning disable CS0612 // Тип или член устарел - // var asset = new Models.Assets.Asset() - // { - // TradeId = trade?.Id, - // AccountId = AccountId, - // Figi = position.Figi, - // Ticker = position.Ticker, - // BoughtAt = trade?.BoughtAt ?? DateTime.UtcNow, - // BoughtPrice = price, - // Type = position.InstrumentType.ParseInstrumentType(), - // Position = position.Quantity > 0 ? PositionType.Long : PositionType.Short, - // BlockedItems = position.BlockedLots, - // Count = position.Quantity, - // CountLots = position.QuantityLots, - // }; - //#pragma warning restore CS0612 // Тип или член устарел - // Assets.AddOrUpdate(asset.Figi, asset, (k, v) => asset); - // oldAssets.Remove(asset.Figi); - // } - - // Total = portfolio.TotalAmountPortfolio; - // Balance = portfolio.TotalAmountCurrencies; - - // foreach (var asset in oldAssets) - // { - // Assets.TryRemove(asset, out _); - // } - - // var ids = trades.Select(t => t.Id).ToArray(); - // await context.Trades - // .Where(t => ids.Contains(t.Id)) - // .ExecuteUpdateAsync(t => t.SetProperty(tr => tr.ArchiveStatus, 1)); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Ошибка при синхранизации портфеля счёта {accountId}", AccountId); - // } - // finally - // { - // //_semaphoreSlim.Release(); - // } - - // } - - // internal async Task ClosePosition(string figi) - // { - // if (!string.IsNullOrEmpty(figi) && Assets.TryGetValue(figi, out var asset)) - // { - // try - // { - // var req = new PostOrderRequest() - // { - // AccountId = AccountId, - // InstrumentId = figi, - // }; - // if (asset != null) - // { - // req.Direction = OrderDirection.Sell; - // req.OrderType = OrderType.Market; - // req.Quantity = (long)asset.Count; - // req.ConfirmMarginTrade = true; - // var res = await _investApiClient.Orders.PostOrderAsync(req); - // return new DealResult - // { - // Count = res.LotsExecuted, - // Price = res.ExecutedOrderPrice, - // Success = true, - // }; - // } - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Ошибка при закрытии позиции по счёту {acc}. figi: {figi}", AccountId, figi); - // } - // } - // return new DealResult - // { - // Count = 0, - // Price = 0, - // Success = false, - // }; - // } - - // internal async Task BuyAsset(string figi, decimal count, string? ticker = null, decimal? recommendedPrice = null) - // { - // try - // { - // var req = new PostOrderRequest() - // { - // AccountId = AccountId, - // InstrumentId = figi, - // Direction = OrderDirection.Buy, - // OrderType = OrderType.Market, - // Quantity = (long)count, - // }; - - // var res = await _investApiClient.Orders.PostOrderAsync(req); - - // using var context = await _dbContextFactory.CreateDbContextAsync(); - // context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - - // var trade = await context.Trades.FirstOrDefaultAsync(t => t.ArchiveStatus == 0 && t.Figi == figi); - // if (trade == null) - // { - // var newTrade = new DataLayer.Entities.Trades.Trade() - // { - // AccountId = AccountId, - // Figi = figi, - // Ticker = ticker ?? string.Empty, - // BoughtAt = DateTime.UtcNow, - // Count = res.LotsExecuted, - // Price = res.ExecutedOrderPrice, - // Position = DataLayer.Entities.Trades.Enums.PositionType.Long, - // Direction = DataLayer.Entities.Trades.Enums.TradeDirection.Buy, - // Asset = DataLayer.Entities.Trades.Enums.AssetType.Common, - // }; - - // await context.Trades.AddAsync(newTrade); - // } - // else - // { - // var oldAmount = trade.Price * trade.Count; - // var newAmount = res.ExecutedOrderPrice * res.LotsExecuted; - // trade.Count = res.LotsExecuted + trade.Count; - // trade.Price = (oldAmount + newAmount) / trade.Count; - // context.Trades.Update(trade); - // } - - // await context.SaveChangesAsync(); - // return new DealResult - // { - // Count = res.LotsExecuted, - // Price = res.ExecutedOrderPrice, - // Success = true, - // }; - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Ошибка при покупке актива на счёт {acc}. figi: {figi}", AccountId, figi); - // } - // return new DealResult - // { - // Count = 0, - // Price = 0, - // Success = false, - // }; - // } - - // private async Task ProcessMarketCommand(TradeCommand command) - // { - // if (string.IsNullOrWhiteSpace(command.Figi)) return; - // if (command.CommandType == TradeCommandType.MarketBuy) - // { - // await BuyAsset(command.Figi, command.Count ?? 1, command.Ticker, command.RecomendPrice); - // } - // else if (command.CommandType == TradeCommandType.ForceClosePosition) - // { - // await ClosePosition(command.Figi); - // } - // else return; - - // await SyncPortfolio(); - // } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 0ea1a82..2305923 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -20,6 +20,7 @@ using System.Collections.Concurrent; using System.Security.Cryptography; using System.Threading.Channels; using Tinkoff.InvestApi; +using Tinkoff.InvestApi.V1; using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType; namespace KLHZ.Trader.Core.Exchange.Services @@ -190,7 +191,7 @@ namespace KLHZ.Trader.Core.Exchange.Services try { - if (message.Figi == "FUTIMOEXF000" && message.Direction==1) + if (message.Figi == "FUTIMOEXF000" && message.Direction == 1) { ProcessStops(message, currentTime); var windowMaxSize = 2000; @@ -224,26 +225,29 @@ namespace KLHZ.Trader.Core.Exchange.Services var assets = acc.Assets.Values.Where(a => a.Figi == message.Figi).ToArray(); foreach (var asset in assets) { - var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, - GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); - var stoppingKey = message.Figi + asset.AccountId; - if (profit < -66m) + if (await asset.Lock(TimeSpan.FromSeconds(60))) { - var command = new TradeCommand() + var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, + GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); + var stoppingKey = message.Figi + asset.AccountId; + if (profit < -100m) { - AccountId = asset.AccountId, - Figi = message.Figi, - CommandType = asset.Count < 0 ? Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy - : Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, - Count = System.Math.Abs((long)asset.Count), - RecomendPrice = null, - EnableMargin = false, - }; - await _dataBus.Broadcast(command); - _logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", - message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); - await LogDeclision(DeclisionTradeAction.CloseLong, message, profit); - await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit); + var command = new TradeCommand() + { + AccountId = acc.AccountId, + Figi = message.Figi, + CommandType = asset.Count < 0 ? Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy + : Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, + Count = System.Math.Abs((long)asset.Count), + RecomendPrice = null, + EnableMargin = false, + }; + await _dataBus.Broadcast(command); + _logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", + message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); + await LogDeclision(DeclisionTradeAction.CloseLong, message, profit); + await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit); + } } } } @@ -369,7 +373,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var trendTask = CalcTrendDiff(message); var ends = mavTaskEnds.Result & TradingEvent.UptrendEnd; - + await Task.WhenAll(mavTask, ltTask, areasTask, positionTask, trendTask, mavTaskShorts, mavTaskEnds); var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); var res = mavTask.Result | ltTask.Result; @@ -394,7 +398,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart)) { - if (RandomNumberGenerator.GetInt32(100) > 50) + if (RandomNumberGenerator.GetInt32(100) > 50 && await acc.Value.Lock(TimeSpan.FromSeconds(60))) { var command = new TradeCommand() { @@ -403,6 +407,7 @@ namespace KLHZ.Trader.Core.Exchange.Services CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy, Count = 1, RecomendPrice = null, + ExchangeObject = acc.Value, }; await _dataBus.Broadcast(command); _logger.LogWarning("Покупка актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", @@ -431,38 +436,41 @@ namespace KLHZ.Trader.Core.Exchange.Services .ToArray(); foreach (var asset in assetsForClose) { - var profit = 0m; + if (await asset.Lock(TimeSpan.FromSeconds(60))) + { + var profit = 0m; - if (assetType == AssetType.Common && asset.Count > 0) - { - profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, - GetComission(assetType), 1, false); - } - if (assetType == AssetType.Futures) - { - profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, - GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); - } + if (assetType == AssetType.Common && asset.Count > 0) + { + profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, + GetComission(assetType), 1, false); + } + if (assetType == AssetType.Futures) + { + profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, + GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); + } - if (profit > 0) - { - LongClosingStops[message.Figi] = message.Time.AddSeconds(30); - var command = new TradeCommand() + if (profit > 0) { - AccountId = asset.AccountId, - Figi = message.Figi, - CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, - Count = (long)asset.Count, - RecomendPrice = null, - EnableMargin = false, - }; - await _dataBus.Broadcast(command); - _logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", - message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); - if (loggedDeclisions == 0) - { - loggedDeclisions++; - await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit); + LongClosingStops[message.Figi] = message.Time.AddSeconds(30); + var command = new TradeCommand() + { + AccountId = asset.AccountId, + Figi = message.Figi, + CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, + Count = (long)asset.Count, + RecomendPrice = null, + EnableMargin = false, + }; + await _dataBus.Broadcast(command); + _logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", + message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); + if (loggedDeclisions == 0) + { + loggedDeclisions++; + await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit); + } } } } @@ -482,7 +490,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var loggedDeclisions = 0; foreach (var acc in accounts) { - if (BotModeSwitcher.CanSell()) + if (BotModeSwitcher.CanSell() && await acc.Value.Lock(TimeSpan.FromSeconds(60))) { if (RandomNumberGenerator.GetInt32(100) > 50) { @@ -493,7 +501,8 @@ namespace KLHZ.Trader.Core.Exchange.Services CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, Count = 1, RecomendPrice = null, - EnableMargin = true + EnableMargin = true, + ExchangeObject = acc.Value, }; await _dataBus.Broadcast(command); _logger.LogWarning("Открытие шорта {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", @@ -524,31 +533,34 @@ namespace KLHZ.Trader.Core.Exchange.Services .ToArray(); foreach (var asset in assetsForClose) { - var profit = 0m; + if (await asset.Lock(TimeSpan.FromSeconds(60))) + { + var profit = 0m; - if (assetType == AssetType.Futures) - { - profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, - GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); - } - if (profit > 0) - { - var command = new TradeCommand() + if (assetType == AssetType.Futures) { - AccountId = asset.AccountId, - Figi = message.Figi, - CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy, - Count = System.Math.Abs((long)asset.Count), - RecomendPrice = null, - EnableMargin = false, - }; - await _dataBus.Broadcast(command); - _logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", - message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); - if (loggedDeclisions == 0) + profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, + GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); + } + if (profit > 0) { - loggedDeclisions++; - await LogDeclision(DeclisionTradeAction.CloseShortReal, message, profit); + var command = new TradeCommand() + { + AccountId = asset.AccountId, + Figi = message.Figi, + CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy, + Count = System.Math.Abs((long)asset.Count), + RecomendPrice = null, + EnableMargin = false, + }; + await _dataBus.Broadcast(command); + _logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}", + message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin); + if (loggedDeclisions == 0) + { + loggedDeclisions++; + await LogDeclision(DeclisionTradeAction.CloseShortReal, message, profit); + } } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs index 8c61e0a..afeb558 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs @@ -91,6 +91,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { _logger.LogError(ex, "Ошибка при покупке актива на счёт {acc}. figi: {figi}", tradeCommand.AccountId, tradeCommand.Figi); } + tradeCommand.ExchangeObject?.Unlock(); } private async Task ProcessCommands()