private async Task Handle(FreezeAmountForWithdrawalCommand command, IEventPublisher publisher) { var(executionInfo, _) = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : OperationName, operationId : command.OperationId, factory : () => new OperationExecutionInfo <WithdrawalFreezeOperationData>( operationName: OperationName, id: command.OperationId, lastModified: _dateService.Now(), data: new WithdrawalFreezeOperationData { State = OperationState.Initiated, AccountId = command.AccountId, Amount = command.Amount, } )); MarginTradingAccount account = null; try { account = _accountsCacheService.Get(command.AccountId); } catch { publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, $"Failed to get account {command.AccountId}")); return; } if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) { if (account.GetFreeMargin() >= command.Amount) { await _accountUpdateService.FreezeWithdrawalMargin(command.AccountId, command.OperationId, command.Amount); _chaosKitty.Meow(command.OperationId); publisher.PublishEvent(new AmountForWithdrawalFrozenEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, command.Reason)); } else { publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, "Not enough free margin")); } _chaosKitty.Meow(command.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }
private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync <SpecialLiquidationOperationData>( operationName : OperationName, id : e.OperationId); if (executionInfo?.Data == null) { return; } if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Started, SpecialLiquidationOperationState.PriceRequested)) { var positionsVolume = GetNetPositionCloseVolume(executionInfo.Data.PositionIds, executionInfo.Data.AccountId); //special command is sent instantly for timeout control.. it is retried until timeout occurs sender.SendCommand(new GetPriceForSpecialLiquidationTimeoutInternalCommand { OperationId = e.OperationId, CreationTime = _dateService.Now(), TimeoutSeconds = _marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutSec, }, _cqrsContextNamesSettings.TradingEngine); executionInfo.Data.Instrument = e.Instrument; executionInfo.Data.Volume = positionsVolume; if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector) { //send it to the Gavel sender.SendCommand(new GetPriceForSpecialLiquidationCommand { OperationId = e.OperationId, CreationTime = _dateService.Now(), Instrument = e.Instrument, Volume = positionsVolume != 0 ? positionsVolume : 1,//hack, requested by the bank RequestNumber = 1, }, _cqrsContextNamesSettings.Gavel); } else { _specialLiquidationService.FakeGetPriceForSpecialLiquidation(e.OperationId, e.Instrument, positionsVolume); } _chaosKitty.Meow(e.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }
public async Task Handle(LiquidationStartedInternalEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>( operationName : OperationName, id : e.OperationId); if (executionInfo?.Data == null) { return; } if (executionInfo.Data.SwitchState(LiquidationOperationState.Initiated, LiquidationOperationState.Started)) { LiquidatePositionsIfAnyAvailable(e.OperationId, executionInfo.Data, sender); _chaosKitty.Meow( $"{nameof(LiquidationStartedInternalEvent)}:" + $"Save_OperationExecutionInfo:" + $"{e.OperationId}"); await _operationExecutionInfoRepository.Save(executionInfo); } }
private async Task Handle(BlockAccountsForDeletionCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : OperationName, operationId : command.OperationId, factory : () => new OperationExecutionInfo <DeleteAccountsOperationData>( operationName: OperationName, id: command.OperationId, lastModified: _dateService.Now(), data: new DeleteAccountsOperationData { State = OperationState.Initiated } )); //todo think how to remove state saving from commands handler here and for Withdrawal if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) { var failedAccounts = new Dictionary <string, string>(); foreach (var accountId in command.AccountIds) { MarginTradingAccount account = null; try { account = _accountsCacheService.Get(accountId); } catch (Exception exception) { failedAccounts.Add(accountId, exception.Message); continue; } var positionsCount = _orderReader.GetPositions().Count(x => x.AccountId == accountId); if (positionsCount != 0) { failedAccounts.Add(accountId, $"Account contain {positionsCount} open positions which must be closed before account deletion."); continue; } var orders = _orderReader.GetPending().Where(x => x.AccountId == accountId).ToList(); if (orders.Any()) { var(failedToCloseOrderId, failReason) = ((string)null, (string)null); foreach (var order in orders) { try { _tradingEngine.CancelPendingOrder(order.Id, order.AdditionalInfo, command.OperationId, $"{nameof(DeleteAccountsCommandsHandler)}: force close all orders.", OrderCancellationReason.AccountInactivated); } catch (Exception exception) { failedToCloseOrderId = order.Id; failReason = exception.Message; break; } } if (failedToCloseOrderId != null) { failedAccounts.Add(accountId, $"Failed to close order [{failedToCloseOrderId}]: {failReason}."); continue; } } if (account.AccountFpl.WithdrawalFrozenMarginData.Any()) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it contained some frozen withdrawal data. Account is deleted.")); } if (account.AccountFpl.UnconfirmedMarginData.Any()) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it contained some unconfirmed margin data. Account is deleted.")); } if (account.Balance != 0) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it's balance on side of TradingCore was non zero. Account is deleted.")); } if (!await UpdateAccount(account, true, r => failedAccounts.Add(accountId, r), command.Timestamp)) { continue; } } publisher.PublishEvent(new AccountsBlockedForDeletionEvent( operationId: command.OperationId, eventTimestamp: _dateService.Now(), failedAccountIds: failedAccounts )); _chaosKitty.Meow($"{nameof(BlockAccountsForDeletionCommand)}: " + "Save_OperationExecutionInfo: " + $"{command.OperationId}"); await _operationExecutionInfoRepository.Save(executionInfo); } }
private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync <SpecialLiquidationOperationData>( operationName : OperationName, id : e.OperationId); if (executionInfo?.Data == null) { return; } if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Started, SpecialLiquidationOperationState.PriceRequested)) { var positionsVolume = GetNetPositionCloseVolume(executionInfo.Data.PositionIds, executionInfo.Data.AccountId); executionInfo.Data.Instrument = e.Instrument; executionInfo.Data.Volume = positionsVolume; executionInfo.Data.RequestNumber = 1; RequestPrice(sender, executionInfo); _chaosKitty.Meow(e.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }
private async Task Handle(StartSpecialLiquidationInternalCommand command, IEventPublisher publisher) { if (!_marginTradingSettings.SpecialLiquidation.Enabled) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = "Special liquidation is disabled in settings", }); return; } //validate the list of positions contain only the same instrument var positions = _orderReader.GetPositions().Where(x => command.PositionIds.Contains(x.Id)).ToList(); if (!string.IsNullOrEmpty(command.AccountId)) { if (_accountsCacheService.TryGet(command.AccountId) == null) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = $"Account {command.AccountId} does not exist", }); return; } positions = positions.Where(x => x.AccountId == command.AccountId).ToList(); } if (positions.Select(x => x.AssetPairId).Distinct().Count() > 1) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = "The list of positions is of different instruments", }); return; } if (!positions.Any()) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = "No positions to liquidate", }); return; } if (!TryGetExchangeNameFromPositions(positions, out var externalProviderId)) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = "All requested positions must be open on the same external exchange", }); return; } var assetPairId = positions.First().AssetPairId; if (_assetPairsCache.GetAssetPairById(assetPairId).IsDiscontinued) { publisher.PublishEvent(new SpecialLiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = $"Asset pair {assetPairId} is discontinued", }); return; } var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : SpecialLiquidationSaga.OperationName, operationId : command.OperationId, factory : () => new OperationExecutionInfo <SpecialLiquidationOperationData>( operationName: SpecialLiquidationSaga.OperationName, id: command.OperationId, lastModified: _dateService.Now(), data: new SpecialLiquidationOperationData { State = SpecialLiquidationOperationState.Initiated, Instrument = assetPairId, PositionIds = positions.Select(x => x.Id).ToList(), ExternalProviderId = externalProviderId, AccountId = command.AccountId, CausationOperationId = command.CausationOperationId, AdditionalInfo = command.AdditionalInfo, OriginatorType = command.OriginatorType } )); if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Initiated, SpecialLiquidationOperationState.Started)) { publisher.PublishEvent(new SpecialLiquidationStartedInternalEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Instrument = positions.FirstOrDefault()?.AssetPairId, }); _chaosKitty.Meow(command.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }
public async Task Handle(AccountChangedEvent e) { var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : OperationName, operationId : e.OperationId, factory : () => new OperationExecutionInfo <OperationData>( operationName: OperationName, id: e.OperationId, lastModified: _dateService.Now(), data: new OperationData { State = OperationState.Initiated } )); if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Finished)) { var updatedAccount = Convert(e.Account); switch (e.EventType) { case AccountChangedEventTypeContract.Created: _accountsCacheService.TryAddNew(MarginTradingAccount.Create(updatedAccount)); break; case AccountChangedEventTypeContract.Updated: { var account = _accountsCacheService.TryGet(e.Account.Id); if (await ValidateAccount(account, e) && await _accountsCacheService.UpdateAccountChanges(updatedAccount.Id, updatedAccount.TradingConditionId, updatedAccount.WithdrawTransferLimit, updatedAccount.IsDisabled, updatedAccount.IsWithdrawalDisabled, e.ChangeTimestamp)) { _accountUpdateService.RemoveLiquidationStateIfNeeded(e.Account.Id, "Trading conditions changed"); } break; } case AccountChangedEventTypeContract.BalanceUpdated: { if (e.BalanceChange != null) { var account = _accountsCacheService.TryGet(e.Account.Id); if (await ValidateAccount(account, e)) { switch (e.BalanceChange.ReasonType) { case AccountBalanceChangeReasonTypeContract.Withdraw: await _accountUpdateService.UnfreezeWithdrawalMargin(updatedAccount.Id, e.BalanceChange.Id); break; case AccountBalanceChangeReasonTypeContract.UnrealizedDailyPnL: if (_ordersCache.Positions.TryGetPositionById(e.BalanceChange.EventSourceId, out var position)) { position.ChargePnL(e.BalanceChange.Id, e.BalanceChange.ChangeAmount); } else { _log.WriteWarning("AccountChangedEvent Handler", e.ToJson(), $"Position [{e.BalanceChange.EventSourceId} was not found]"); } break; case AccountBalanceChangeReasonTypeContract.RealizedPnL: await _accountUpdateService.UnfreezeUnconfirmedMargin(e.Account.Id, e.BalanceChange.EventSourceId); break; } if (await _accountsCacheService.UpdateAccountBalance(updatedAccount.Id, e.BalanceChange.Balance, e.ChangeTimestamp)) { _accountUpdateService.RemoveLiquidationStateIfNeeded(e.Account.Id, "Balance updated"); _accountBalanceChangedEventChannel.SendEvent(this, new AccountBalanceChangedEventArgs(updatedAccount)); } } } else { _log.WriteWarning("AccountChangedEvent Handler", e.ToJson(), "BalanceChange info is empty"); } break; } case AccountChangedEventTypeContract.Deleted: //account deletion from cache is double-handled by CQRS flow _accountsCacheService.Remove(e.Account.Id); break; default: await _log.WriteErrorAsync(nameof(AccountsProjection), nameof(AccountChangedEvent), e.ToJson(), new Exception("AccountChangedEventTypeContract was in incorrect state")); break; } _chaosKitty.Meow(e.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }