259 lines
11 KiB
C#
259 lines
11 KiB
C#
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<string, Asset> 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<DealResult> 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<DealResult> 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();
|
|
// }
|
|
}
|
|
}
|