private async Task Handle(DeleteAccountsStartedInternalEvent e, ICommandSender sender)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <DeleteAccountsData>(
                OperationName,
                e.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            if (executionInfo.Data.SwitchState(DeleteAccountsState.Initiated, DeleteAccountsState.Started))
            {
                executionInfo.Data.AddFailedIfNotExist(e.FailedAccountIds);

                sender.SendCommand(new BlockAccountsForDeletionCommand
                {
                    OperationId = e.OperationId,
                    Timestamp   = _systemClock.UtcNow.UtcDateTime,
                    AccountIds  = executionInfo.Data.GetAccountsToDelete(),
                },
                                   _contextNames.TradingEngine);

                _chaosKitty.Meow(
                    $"{nameof(DeleteAccountsStartedInternalEvent)}: " +
                    "Save_OperationExecutionInfo: " +
                    $"{e.OperationId}");

                await _executionInfoRepository.SaveAsync(executionInfo);
            }
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
        public async Task <RfqPauseErrorCode> AddAsync(string operationId, PauseSource source, Initiator initiator)
        {
            if (string.IsNullOrEmpty(operationId))
            {
                throw new ArgumentNullException(nameof(operationId));
            }

            var locker = _lock.GetOrAdd(operationId, new SemaphoreSlim(1, 1));

            await locker.WaitAsync();

            try
            {
                var existingPause = (await _pauseRepository.FindAsync(
                                         operationId,
                                         SpecialLiquidationSaga.OperationName,
                                         NotCancelledPredicate))
                                    .SingleOrDefault();

                if (existingPause != null)
                {
                    await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(AddAsync), $"There is already pause with state [{existingPause.State}] for operation with id [{operationId}]");

                    return(RfqPauseErrorCode.AlreadyExists);
                }

                var executionInfo = await _executionInfoRepository
                                    .GetAsync <SpecialLiquidationOperationData>(SpecialLiquidationSaga.OperationName, operationId);

                if (executionInfo == null)
                {
                    return(RfqPauseErrorCode.NotFound);
                }

                if (!AllowedOperationStatesToPauseIn.Contains(executionInfo.Data.State))
                {
                    await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(AddAsync),
                                                 $"There was an attempt to pause special liquidation with id {operationId} and state {executionInfo.Data.State}. Pause is possible in [{string.Join(',', AllowedOperationStatesToPauseIn)}] states only");

                    return(RfqPauseErrorCode.InvalidOperationState);
                }

                var pause = Pause.Create(
                    operationId,
                    SpecialLiquidationSaga.OperationName,
                    source,
                    initiator,
                    _dateService.Now());

                await _pauseRepository.AddAsync(pause);

                // todo: add audit log
            }
            finally
            {
                locker.Release();
            }

            return(RfqPauseErrorCode.None);
        }
예제 #4
0
        private async Task Handle(WithdrawalStartedInternalEvent e, ICommandSender sender)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <WithdrawalDepositData>(OperationName, e.OperationId);

            if (executionInfo == null)
            {
                return;
            }

            if (SwitchState(executionInfo.Data, WithdrawalState.Created, WithdrawalState.FreezingAmount))
            {
                sender.SendCommand(
                    new FreezeAmountForWithdrawalCommand(
                        executionInfo.Id,
                        _systemClock.UtcNow.UtcDateTime,
                        executionInfo.Data.AccountId,
                        executionInfo.Data.Amount,
                        executionInfo.Data.Comment),
                    _contextNames.TradingEngine);

                _chaosKitty.Meow(e.OperationId);

                await _executionInfoRepository.SaveAsync(executionInfo);
            }
        }
        private async Task <CommandHandlingResult> Handle(GetPriceForSpecialLiquidationTimeoutInternalCommand command,
                                                          IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <SpecialLiquidationOperationData>(
                operationName : SpecialLiquidationSaga.OperationName,
                id : command.OperationId);

            if (executionInfo?.Data != null)
            {
                if (executionInfo.Data.State > SpecialLiquidationOperationState.PriceRequested ||
                    executionInfo.Data.RequestNumber > command.RequestNumber)
                {
                    return(CommandHandlingResult.Ok());
                }

                if (_dateService.Now() >= command.CreationTime.AddSeconds(command.TimeoutSeconds))
                {
                    publisher.PublishEvent(new SpecialLiquidationFailedEvent
                    {
                        OperationId          = command.OperationId,
                        CreationTime         = _dateService.Now(),
                        Reason               = $"Timeout of {command.TimeoutSeconds} seconds from {command.CreationTime:s}",
                        CanRetryPriceRequest = true
                    });

                    return(CommandHandlingResult.Ok());
                }
            }

            return(CommandHandlingResult.Fail(_marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutCheckPeriod));
        }
예제 #6
0
        public async Task Handle(FailLiquidationInternalCommand command,
                                 IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>(
                operationName : LiquidationSaga.OperationName,
                id : command.OperationId);

            if (executionInfo?.Data == null)
            {
                return;
            }

            _accountUpdateService.RemoveLiquidationStateIfNeeded(executionInfo.Data.AccountId,
                                                                 $"Liquidation [{command.OperationId}] failed ({command.Reason})", command.OperationId,
                                                                 executionInfo.Data.LiquidationType);

            _chaosKitty.Meow(
                $"{nameof(FailLiquidationInternalCommand)}:" +
                $"Publish_LiquidationFailedInternalEvent:" +
                $"{command.OperationId}");

            publisher.PublishEvent(new LiquidationFailedEvent
            {
                OperationId                     = command.OperationId,
                CreationTime                    = _dateService.Now(),
                Reason                          = command.Reason,
                LiquidationType                 = command.LiquidationType.ToType <LiquidationTypeContract>(),
                AccountId                       = executionInfo.Data.AccountId,
                AssetPairId                     = executionInfo.Data.AssetPairId,
                Direction                       = executionInfo.Data.Direction?.ToType <PositionDirectionContract>(),
                QuoteInfo                       = executionInfo.Data.QuoteInfo,
                ProcessedPositionIds            = executionInfo.Data.ProcessedPositionIds,
                LiquidatedPositionIds           = executionInfo.Data.LiquidatedPositionIds,
                OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count,
                CurrentTotalCapital             = _accountsCache.Get(executionInfo.Data.AccountId).GetTotalCapital(),
            });

            _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs
            {
                OperationId           = command.OperationId,
                CreationTime          = _dateService.Now(),
                AccountId             = executionInfo.Data.AccountId,
                LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds,
                FailReason            = command.Reason,
            });
        }
        private async Task Handle(FailWithdrawalInternalCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <WithdrawalDepositData>(
                OperationName,
                command.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            var account = await _accountsRepository.GetAsync(executionInfo.Data.AccountId);

            publisher.PublishEvent(new WithdrawalFailedEvent(
                                       command.OperationId,
                                       _systemClock.UtcNow.UtcDateTime,
                                       command.Reason,
                                       executionInfo.Data.AccountId,
                                       account?.ClientId,
                                       executionInfo.Data.Amount));
        }
        public async Task Handle(RevokeTemporaryCapitalStartedInternalEvent e, ICommandSender sender)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <RevokeTemporaryCapitalData>(
                OperationName,
                e.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            if (executionInfo.Data.SwitchState(TemporaryCapitalState.Initiated, TemporaryCapitalState.Started))
            {
                executionInfo.Data.RevokedTemporaryCapital = e.RevokedTemporaryCapital;

                sender.SendCommand(
                    new UpdateBalanceInternalCommand(
                        e.OperationId,
                        executionInfo.Data.AccountId,
                        -executionInfo.Data.RevokedTemporaryCapital.Sum(x => x.Amount),
                        $"{executionInfo.Data.Comment}  ***  Revoked eventSourceIds: [{string.Join(",", executionInfo.Data.RevokedTemporaryCapital.Select(x => x.Id))}]",
                        executionInfo.Data.AdditionalInfo,
                        OperationName,
                        AccountBalanceChangeReasonType.TemporaryCashAdjustment,
                        executionInfo.Data.EventSourceId,
                        string.Empty,
                        _systemClock.UtcNow.UtcDateTime),
                    _contextNames.AccountsManagement);

                _chaosKitty.Meow(
                    $"{nameof(RevokeTemporaryCapitalStartedInternalEvent)}: " +
                    "Save_OperationExecutionInfo:" +
                    $"{e.OperationId}");

                await _executionInfoRepository.SaveAsync(executionInfo);
            }
        }
        private async Task Handle(DepositStartedInternalEvent e, ICommandSender sender)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <WithdrawalDepositData>(
                OperationName,
                e.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            if (SwitchState(executionInfo.Data, WithdrawalState.Created, WithdrawalState.FreezingAmount))
            {
                sender.SendCommand(
                    new FreezeAmountForDepositInternalCommand(e.OperationId),
                    _contextNames.AccountsManagement);

                _chaosKitty.Meow(e.OperationId);

                await _executionInfoRepository.SaveAsync(executionInfo);
            }
        }
예제 #10
0
        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);
            }
        }
예제 #11
0
        public async Task Handle(FinishGiveTemporaryCapitalInternalCommand c, IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <GiveTemporaryCapitalData>(OperationName, c.OperationId);

            if (executionInfo == null)
            {
                return;
            }

            if (!new[] { TemporaryCapitalState.ChargedOnAccount, TemporaryCapitalState.Failing }
                .Contains(executionInfo.Data.State))
            {
                throw new Exception($"{nameof(FinishGiveTemporaryCapitalInternalCommand)} have state {executionInfo.Data.State.ToString()}, but one of [{TemporaryCapitalState.ChargedOnAccount}, {TemporaryCapitalState.Failing}] was expected. Throwing to retry in {(long) _settings.Cqrs.RetryDelay.TotalMilliseconds}ms.");
            }

            if (executionInfo.Data.State == TemporaryCapitalState.ChargedOnAccount)
            {
                publisher.PublishEvent(new GiveTemporaryCapitalSucceededEvent(
                                           c.OperationId,
                                           _systemClock.UtcNow.UtcDateTime,
                                           executionInfo.Data.OperationId,
                                           executionInfo.Data.AccountId,
                                           executionInfo.Data.Amount,
                                           executionInfo.Data.Reason,
                                           executionInfo.Data.Comment,
                                           executionInfo.Data.AdditionalInfo));
            }
            else if (executionInfo.Data.State == TemporaryCapitalState.Failing)
            {
                //rollback account. if exception is thrown here, it will be retried until success
                await _accountsRepository.UpdateAccountTemporaryCapitalAsync(executionInfo.Data.AccountId,
                                                                             AccountManagementService.UpdateTemporaryCapital,
                                                                             new InternalModels.TemporaryCapital
                {
                    Id     = executionInfo.Data.OperationId,
                    Amount = executionInfo.Data.Amount,
                },
                                                                             false);

                _chaosKitty.Meow($"{nameof(FinishGiveTemporaryCapitalInternalCommand)}: " +
                                 "publisher.PublishEvent: " + c.OperationId);

                publisher.PublishEvent(new GiveTemporaryCapitalFailedEvent(c.OperationId,
                                                                           _systemClock.UtcNow.UtcDateTime, executionInfo.Data.FailReason));
            }
        }
예제 #12
0
        private async Task Handle(MtCoreFinishAccountsDeletionCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <DeleteAccountsOperationData>(
                operationName : OperationName,
                id : command.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            if (executionInfo.Data.SwitchState(OperationState.Started, OperationState.Finished))
            {
                foreach (var failedAccountId in command.FailedAccountIds)
                {
                    try
                    {
                        var account = _accountsCacheService.Get(failedAccountId);

                        await UpdateAccount(account, false, r => { }, command.Timestamp);
                    }
                    catch (Exception exception)
                    {
                        await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler),
                                                   nameof(MtCoreFinishAccountsDeletionCommand), exception);
                    }
                }

                foreach (var accountId in command.AccountIds)
                {
                    _accountsCacheService.Remove(accountId);
                }

                publisher.PublishEvent(new MtCoreDeleteAccountsFinishedEvent(command.OperationId, _dateService.Now()));

                _chaosKitty.Meow($"{nameof(MtCoreFinishAccountsDeletionCommand)}: " +
                                 "Save_OperationExecutionInfo: " +
                                 $"{command.OperationId}");

                await _operationExecutionInfoRepository.Save(executionInfo);
            }
        }
예제 #13
0
        private async Task Handle(UnfreezeMarginOnFailWithdrawalCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <WithdrawalFreezeOperationData>(
                operationName : OperationName,
                id : command.OperationId
                );

            if (executionInfo == null)
            {
                return;
            }

            if (executionInfo.Data.SwitchState(OperationState.Started, OperationState.Finished))
            {
                await _accountUpdateService.UnfreezeWithdrawalMargin(executionInfo.Data.AccountId, command.OperationId);

                publisher.PublishEvent(new UnfreezeMarginOnFailSucceededWithdrawalEvent(command.OperationId,
                                                                                        _dateService.Now(), executionInfo.Data.AccountId, executionInfo.Data.Amount));

                _chaosKitty.Meow(command.OperationId);

                await _operationExecutionInfoRepository.Save(executionInfo);
            }
        }
예제 #14
0
        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);
            }
        }
예제 #15
0
        public async Task ExecuteAsync(IEventPublisher failurePublisher, string accountId, string operationId, string reason)
        {
            if (failurePublisher == null)
            {
                throw new ArgumentNullException(nameof(failurePublisher));
            }

            if (string.IsNullOrEmpty(accountId))
            {
                throw new ArgumentNullException(nameof(accountId));
            }

            if (string.IsNullOrEmpty(operationId))
            {
                throw new ArgumentNullException(nameof(operationId));
            }

            var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>(
                LiquidationSaga.OperationName,
                operationId);

            if (executionInfo?.Data == null)
            {
                await _log.WriteWarningAsync(nameof(LiquidationFailureExecutor),
                                             nameof(ExecuteAsync),
                                             new { operationId, accountId }.ToJson(),
                                             $"Unable to execute failure. Liquidation execution info was not found.");

                return;
            }

            _accountUpdateService.RemoveLiquidationStateIfNeeded(accountId, reason, operationId, executionInfo.Data.LiquidationType);

            var account = _accountsCache.Get(accountId);

            failurePublisher.PublishEvent(new LiquidationFailedEvent
            {
                OperationId                     = operationId,
                CreationTime                    = _dateService.Now(),
                Reason                          = reason,
                LiquidationType                 = executionInfo.Data.LiquidationType.ToType <LiquidationTypeContract>(),
                AccountId                       = executionInfo.Data.AccountId,
                AssetPairId                     = executionInfo.Data.AssetPairId,
                Direction                       = executionInfo.Data.Direction?.ToType <PositionDirectionContract>(),
                QuoteInfo                       = executionInfo.Data.QuoteInfo,
                ProcessedPositionIds            = executionInfo.Data.ProcessedPositionIds,
                LiquidatedPositionIds           = executionInfo.Data.LiquidatedPositionIds,
                OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count,
                CurrentTotalCapital             = account.GetTotalCapital(),
            });

            await _log.WriteInfoAsync(nameof(LiquidationFailureExecutor),
                                      nameof(ExecuteAsync),
                                      new { account, operationId, reason }.ToJson(),
                                      $"Successfully published {nameof(LiquidationFailedEvent)} event");

            _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs
            {
                OperationId           = operationId,
                CreationTime          = _dateService.Now(),
                AccountId             = executionInfo.Data.AccountId,
                LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds,
                FailReason            = reason,
            });

            await _log.WriteInfoAsync(nameof(LiquidationFailureExecutor),
                                      nameof(ExecuteAsync),
                                      new { account, operationId, reason }.ToJson(),
                                      $"Successfully published {nameof(LiquidationEndEventArgs)} event");
        }
        private async Task Handle(MarkAccountsAsDeletedInternalCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetAsync <DeleteAccountsData>(OperationName,
                                                                                             command.OperationId);

            if (executionInfo == null || executionInfo.Data.State > DeleteAccountsState.MtCoreAccountsBlocked)
            {
                return;
            }
            if (executionInfo.Data.State < DeleteAccountsState.MtCoreAccountsBlocked)
            {
                throw new Exception($"{nameof(MarkAccountsAsDeletedInternalCommand)} have state {executionInfo.Data.State.ToString()}, but [{DeleteAccountsState.MtCoreAccountsBlocked}] was expected. Throwing to retry in {(long) _settings.Cqrs.RetryDelay.TotalMilliseconds}ms.");
            }

            var validationFailedAccountIds = await ValidateAccountsAsync(executionInfo.Data.AccountIds);

            foreach (var accountToDelete in executionInfo.Data.GetAccountsToDelete()
                     .Except(validationFailedAccountIds.Keys))
            {
                try
                {
                    var account = await _accountsRepository.DeleteAsync(accountToDelete);

                    publisher.PublishEvent(
                        new AccountChangedEvent(
                            account.ModificationTimestamp,
                            OperationName,
                            _convertService.Convert <IAccount, AccountContract>(account),
                            AccountChangedEventTypeContract.Deleted,
                            null,
                            command.OperationId,
                            null));
                }
                catch (Exception exception)
                {
                    await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(MarkAccountsAsDeletedInternalCommand),
                                               $"OperationId: [{command.OperationId}]", exception);

                    validationFailedAccountIds.Add(accountToDelete, exception.Message);
                }
            }

            foreach (var failedAccountId in executionInfo.Data.FailedAccountIds.Keys
                     .Concat(validationFailedAccountIds.Keys))
            {
                try
                {
                    var account = (await _accountsRepository.GetAsync(failedAccountId))
                                  .RequiredNotNull(nameof(failedAccountId), $"Account {failedAccountId} does not exist.");

                    var result = await _accountsRepository.UpdateAccountAsync(
                        failedAccountId,
                        false,
                        false);

                    _eventSender.SendAccountChangedEvent(
                        nameof(MarkAccountsAsDeletedInternalCommand),
                        result,
                        AccountChangedEventTypeContract.Updated,
                        $"{command.OperationId}_{failedAccountId}",
                        previousSnapshot: account);
                }
                catch (Exception exception)
                {
                    await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler),
                                               nameof(MarkAccountsAsDeletedInternalCommand), exception);
                }
            }

            _chaosKitty.Meow($"{nameof(MarkAccountsAsDeletedInternalCommand)}: " +
                             "AccountsMarkedAsDeletedEvent: " +
                             $"{command.OperationId}");

            publisher.PublishEvent(new AccountsMarkedAsDeletedEvent(
                                       command.OperationId,
                                       _systemClock.UtcNow.UtcDateTime,
                                       validationFailedAccountIds
                                       ));
        }