using System.Collections.Concurrent; namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting { public class ManagedAccount { public readonly string AccountId; private readonly object _locker = new(); private decimal _balance = 0; private decimal _total = 0; internal decimal Balance { get { lock (_locker) return _balance; } set { lock (_locker) _balance = value; } } internal decimal Total { get { lock (_locker) return _total; } set { lock (_locker) _total = value; } } internal readonly ConcurrentDictionary Assets = new(); public ManagedAccount(string accountId) { 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(); // } } }