private void LiquidatePositionsIfAnyAvailable(string operationId, LiquidationOperationData data, ICommandSender sender) { var liquidationData = GetLiquidationData(data); if (!liquidationData.HasValue || !liquidationData.Value.Positions.Any()) { sender.SendCommand(new FailLiquidationInternalCommand { OperationId = operationId, CreationTime = _dateService.Now(), Reason = "Nothing to liquidate", LiquidationType = data.LiquidationType, }, _cqrsContextNamesSettings.TradingEngine); } else { sender.SendCommand(new LiquidatePositionsInternalCommand { OperationId = operationId, CreationTime = _dateService.Now(), PositionIds = liquidationData.Value.Positions, AssetPairId = liquidationData.Value.AssetPairId, }, _cqrsContextNamesSettings.TradingEngine); } }
private (string AssetPairId, string[] Positions)? GetLiquidationData( LiquidationOperationData data) { var positionsOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(data.AccountId); //group positions and take only not processed, filtered and with open market var positionGroups = positionsOnAccount .Where(p => !data.ProcessedPositionIds.Contains(p.Id) && (string.IsNullOrEmpty(data.AssetPairId) || p.AssetPairId == data.AssetPairId) && (data.Direction == null || p.Direction == data.Direction)) .GroupBy(p => p.AssetPairId) .Where(gr => !_assetPairDayOffService.IsDayOff(gr.Key)) .ToArray(); IGrouping <string, Position> targetPositions = null; //take positions from group with max margin used or max initially used margin targetPositions = positionGroups .OrderByDescending(gr => gr.Sum(p => Math.Max(p.GetMarginMaintenance(), p.GetInitialMargin()))) .FirstOrDefault(); if (targetPositions == null) { return(null); } return(targetPositions.Key, targetPositions.Select(p => p.Id).ToArray()); }
private void ContinueOrFinishLiquidation(string operationId, LiquidationOperationData data, ICommandSender sender) { void FinishWithReason(string reason) => sender.SendCommand(new FinishLiquidationInternalCommand { OperationId = operationId, CreationTime = _dateService.Now(), Reason = reason, LiquidationType = data.LiquidationType, ProcessedPositionIds = data.ProcessedPositionIds, LiquidatedPositionIds = data.LiquidatedPositionIds, }, _cqrsContextNamesSettings.TradingEngine); var account = _accountsCacheService.TryGet(data.AccountId); if (account == null) { sender.SendCommand(new FailLiquidationInternalCommand { OperationId = operationId, CreationTime = _dateService.Now(), Reason = "Account does not exist", LiquidationType = data.LiquidationType, }, _cqrsContextNamesSettings.TradingEngine); return; } var accountLevel = account.GetAccountLevel(); if (data.LiquidationType == LiquidationType.Forced) { if (!_ordersCache.Positions.GetPositionsByAccountIds(data.AccountId) .Any(x => (string.IsNullOrWhiteSpace(data.AssetPairId) || x.AssetPairId == data.AssetPairId) && x.OpenDate < data.StartedAt && (data.Direction == null || x.Direction == data.Direction))) { FinishWithReason("All positions are closed"); } else { LiquidatePositionsIfAnyAvailable(operationId, data, sender); } return; } if (accountLevel < AccountLevel.StopOut) { FinishWithReason($"Account margin level is {accountLevel}"); } else { LiquidatePositionsIfAnyAvailable(operationId, data, sender); } }
private (string AssetPairId, string[] Positions)? GetLiquidationData( LiquidationOperationData data) { var positionsOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(data.AccountId); if (data.LiquidationType == LiquidationType.Forced) { positionsOnAccount = positionsOnAccount.Where(p => p.OpenDate < data.StartedAt).ToList(); //group positions by asset and pnl sign, take only not processed, filtered and with open market //groups are ordered by positive pnl first var targetPositionsByPnlSign = positionsOnAccount .Where(p => !data.ProcessedPositionIds.Contains(p.Id)) .GroupBy(p => (p.AssetPairId, p.GetUnrealisedFpl() >= 0)) .Where(gr => !_assetPairDayOffService.IsAssetTradingDisabled(gr.Key.AssetPairId)) .OrderByDescending(gr => gr.Key.Item2) .FirstOrDefault(); if (targetPositionsByPnlSign == null) { return(null); } return(targetPositionsByPnlSign.Key.AssetPairId, targetPositionsByPnlSign.Select(p => p.Id).ToArray()); } //group positions and take only not processed, filtered and with open market var positionGroups = positionsOnAccount .Where(p => !data.ProcessedPositionIds.Contains(p.Id) && (string.IsNullOrEmpty(data.AssetPairId) || p.AssetPairId == data.AssetPairId) && (data.Direction == null || p.Direction == data.Direction)) .GroupBy(p => p.AssetPairId) .Where(gr => !_assetPairDayOffService.IsAssetTradingDisabled(gr.Key)) .ToArray(); IGrouping <string, Position> targetPositions = null; //take positions from group with max margin used or max initially used margin targetPositions = positionGroups .OrderByDescending(gr => gr.Sum(p => Math.Max(p.GetMarginMaintenance(), p.GetInitialMargin()))) .FirstOrDefault(); if (targetPositions == null) { return(null); } return(targetPositions.Key, targetPositions.Select(p => p.Id).ToArray()); }