예제 #1
0
        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(WithdrawCommand command, IEventPublisher publisher)
        {
            await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                command.OperationId,
                () => new OperationExecutionInfo <WithdrawalDepositData>(
                    OperationName,
                    command.OperationId,
                    new WithdrawalDepositData
            {
                AccountId = command.AccountId,
                Amount = command.Amount,
                AuditLog = command.AuditLog,
                State = WithdrawalState.Created,
                Comment = command.Comment
            },
                    _systemClock.UtcNow.UtcDateTime));

            var account = await _accountsRepository.GetAsync(command.AccountId);

            if (account == null)
            {
                publisher.PublishEvent(new WithdrawalStartFailedInternalEvent(command.OperationId,
                                                                              _systemClock.UtcNow.UtcDateTime, $"Account {command.AccountId} not found."));
                return;
            }

            var accountCapital = await _accountManagementService.GetAccountCapitalAsync(account.Id, useCache : false);

            if (accountCapital.Disposable < command.Amount)
            {
                publisher.PublishEvent(new WithdrawalStartFailedInternalEvent(command.OperationId,
                                                                              _systemClock.UtcNow.UtcDateTime,
                                                                              $"Account {account.Id} balance {accountCapital.Balance}{accountCapital.AssetId} is not enough to withdraw {command.Amount}{accountCapital.AssetId}. " +
                                                                              $"Taking into account the current state of the trading account: {accountCapital.ToJson()}."));
                return;
            }

            if (account.IsWithdrawalDisabled)
            {
                publisher.PublishEvent(new WithdrawalStartFailedInternalEvent(command.OperationId,
                                                                              _systemClock.UtcNow.UtcDateTime, "Withdrawal is disabled"));

                return;
            }

            _chaosKitty.Meow(command.OperationId);

            publisher.PublishEvent(new WithdrawalStartedInternalEvent(command.OperationId,
                                                                      _systemClock.UtcNow.UtcDateTime));
        }
예제 #3
0
        private async Task Handle(DepositCommand c, IEventPublisher publisher)
        {
            await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                c.OperationId,
                () => new OperationExecutionInfo <WithdrawalDepositData>(
                    OperationName,
                    c.OperationId,
                    new WithdrawalDepositData
            {
                AccountId = c.AccountId,
                Amount = c.Amount,
                AuditLog = c.AuditLog,
                State = WithdrawalState.Created,
                Comment = c.Comment
            },
                    _systemClock.UtcNow.UtcDateTime));

            _chaosKitty.Meow(c.OperationId);

            publisher.PublishEvent(new DepositStartedInternalEvent(c.OperationId, _systemClock.UtcNow.UtcDateTime));
        }
예제 #4
0
        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);
            }
        }
예제 #5
0
        public async Task Handle(StartLiquidationInternalCommand command,
                                 IEventPublisher publisher)
        {
            #region Private Methods

            void PublishFailedEvent(string reason)
            {
                publisher.PublishEvent(new LiquidationFailedEvent
                {
                    OperationId     = command.OperationId,
                    CreationTime    = _dateService.Now(),
                    Reason          = reason,
                    LiquidationType = command.LiquidationType.ToType <LiquidationTypeContract>(),
                    AccountId       = command.AccountId,
                    AssetPairId     = command.AssetPairId,
                    Direction       = command.Direction?.ToType <PositionDirectionContract>(),
                });

                _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs
                {
                    OperationId           = command.OperationId,
                    CreationTime          = _dateService.Now(),
                    AccountId             = command.AccountId,
                    LiquidatedPositionIds = new List <string>(),
                    FailReason            = reason,
                });
            }

            #endregion

            #region Validations

            if (string.IsNullOrEmpty(command.AccountId))
            {
                PublishFailedEvent("AccountId must be specified");
                return;
            }

            if (_accountsCache.TryGet(command.AccountId) == null)
            {
                PublishFailedEvent("Account does not exist");
                return;
            }

            #endregion

            var(executionInfo, _) = await _operationExecutionInfoRepository.GetOrAddAsync(
                operationName : LiquidationSaga.OperationName,
                operationId : command.OperationId,
                factory : () => new OperationExecutionInfo <LiquidationOperationData>(
                    operationName: LiquidationSaga.OperationName,
                    id: command.OperationId,
                    lastModified: _dateService.Now(),
                    data: new LiquidationOperationData
            {
                State                 = LiquidationOperationState.Initiated,
                AccountId             = command.AccountId,
                AssetPairId           = command.AssetPairId,
                QuoteInfo             = command.QuoteInfo,
                Direction             = command.Direction,
                LiquidatedPositionIds = new List <string>(),
                ProcessedPositionIds  = new List <string>(),
                LiquidationType       = command.LiquidationType,
                OriginatorType        = command.OriginatorType,
                AdditionalInfo        = command.AdditionalInfo,
                StartedAt             = command.CreationTime
            }
                    ));

            if (executionInfo.Data.State == LiquidationOperationState.Initiated)
            {
                if (!_accountsCache.TryStartLiquidation(command.AccountId, command.OperationId,
                                                        out var currentOperationId))
                {
                    if (currentOperationId != command.OperationId)
                    {
                        PublishFailedEvent(
                            $"Liquidation is already in progress. Initiated by operation : {currentOperationId}");
                        return;
                    }
                }

                _chaosKitty.Meow(
                    $"{nameof(StartLiquidationInternalCommand)}:" +
                    $"Publish_LiquidationStartedInternalEvent:" +
                    $"{command.OperationId}");

                publisher.PublishEvent(new LiquidationStartedEvent
                {
                    OperationId     = command.OperationId,
                    CreationTime    = _dateService.Now(),
                    AssetPairId     = executionInfo.Data.AssetPairId,
                    AccountId       = executionInfo.Data.AccountId,
                    LiquidationType = executionInfo.Data.LiquidationType.ToType <LiquidationTypeContract>()
                });
            }
        }
        public async Task Handle(StartRevokeTemporaryCapitalInternalCommand c, IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                c.OperationId,
                () => new OperationExecutionInfo <RevokeTemporaryCapitalData>(
                    OperationName,
                    c.OperationId,
                    new RevokeTemporaryCapitalData
            {
                State = TemporaryCapitalState.Initiated,
                OperationId = c.OperationId,
                AccountId = c.AccountId,
                RevokeEventSourceId = c.RevokeEventSourceId,
                Comment = c.Comment,
                AdditionalInfo = c.AdditionalInfo,
            },
                    _systemClock.UtcNow.UtcDateTime));

            if (executionInfo.Data.State != TemporaryCapitalState.Initiated)
            {
                return;
            }

            var account = await _accountsRepository.GetAsync(c.AccountId);

            if (account == null)
            {
                publisher.PublishEvent(new RevokeTemporaryCapitalFailedEvent(c.OperationId,
                                                                             _systemClock.UtcNow.UtcDateTime, $"Account {c.AccountId} not found",
                                                                             c.RevokeEventSourceId));
                return;
            }

            if (!string.IsNullOrEmpty(c.RevokeEventSourceId) &&
                account.TemporaryCapital.All(x => x.Id != c.RevokeEventSourceId))
            {
                publisher.PublishEvent(new RevokeTemporaryCapitalFailedEvent(c.OperationId,
                                                                             _systemClock.UtcNow.UtcDateTime,
                                                                             $"Account {c.AccountId} doesn't contain temporary capital with id {c.RevokeEventSourceId}",
                                                                             c.RevokeEventSourceId));
                return;
            }

            var temporaryCapitalToRevoke = account.TemporaryCapital
                                           .Where(x => string.IsNullOrEmpty(c.RevokeEventSourceId) || x.Id == c.RevokeEventSourceId)
                                           .ToList();

            var accountCapital = await _accountManagementService.GetAccountCapitalAsync(account.Id, useCache : false);

            var amountToRevoke = temporaryCapitalToRevoke.Sum(x => x.Amount);

            if (accountCapital.CanRevokeAmount < amountToRevoke)
            {
                publisher.PublishEvent(new RevokeTemporaryCapitalFailedEvent(c.OperationId,
                                                                             _systemClock.UtcNow.UtcDateTime,
                                                                             $"Account {c.AccountId} balance {account.Balance}{account.BaseAssetId} is not enough to revoke {amountToRevoke}{account.BaseAssetId}. Taking into account the current state of the trading account: {accountCapital.ToJson()}.",
                                                                             c.RevokeEventSourceId));
                return;
            }

            var accountStat = await _accountsApi.GetAccountStats(c.AccountId);

            if (accountStat != null && accountStat.FreeMargin < amountToRevoke)
            {
                publisher.PublishEvent(new RevokeTemporaryCapitalFailedEvent(c.OperationId,
                                                                             _systemClock.UtcNow.UtcDateTime,
                                                                             $"MT Core account {c.AccountId} free margin {accountStat.FreeMargin}{account.BaseAssetId} is not enough to revoke {amountToRevoke}{account.BaseAssetId}.",
                                                                             c.RevokeEventSourceId));
                return;
            }

            try
            {
                await _accountsRepository.UpdateAccountTemporaryCapitalAsync(c.AccountId,
                                                                             AccountManagementService.UpdateTemporaryCapital,
                                                                             string.IsNullOrEmpty(c.RevokeEventSourceId)
                                                                             ?null
                                                                             : new TemporaryCapital {
                    Id = c.RevokeEventSourceId
                },
                                                                             false);
            }
            catch (Exception exception)
            {
                await _log.WriteErrorAsync(nameof(RevokeTemporaryCapitalCommandsHandler),
                                           nameof(StartRevokeTemporaryCapitalInternalCommand), exception);

                publisher.PublishEvent(new RevokeTemporaryCapitalFailedEvent(c.OperationId,
                                                                             _systemClock.UtcNow.UtcDateTime, exception.Message, c.RevokeEventSourceId));

                return;
            }

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

            publisher.PublishEvent(new RevokeTemporaryCapitalStartedInternalEvent(c.OperationId,
                                                                                  _systemClock.UtcNow.UtcDateTime, temporaryCapitalToRevoke));
        }
예제 #7
0
        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);
            }
        }
예제 #8
0
        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);
            }
        }
예제 #9
0
        private async Task Handle(UpdateBalanceInternalCommand command,
            IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                command.OperationId,
                () => new OperationExecutionInfo<OperationData>(
                    OperationName,
                    command.OperationId,
                    new  OperationData { State = OperationState.Created },
                    _systemClock.UtcNow.UtcDateTime));

            if (SwitchState(executionInfo.Data, OperationState.Created, OperationState.Started))
            {
                IAccount account = null;
                try
                {
                    account = await _accountsRepository.UpdateBalanceAsync(
                        command.OperationId,
                        command.AccountId,
                        command.AmountDelta,
                        false);
                }
                catch (ValidationException ex)
                {
                    await _log.WriteWarningAsync(nameof(UpdateBalanceCommandsHandler),
                        nameof(UpdateBalanceInternalCommand), ex.Message);
                    
                    publisher.PublishEvent(new AccountBalanceChangeFailedEvent(command.OperationId,
                        _systemClock.UtcNow.UtcDateTime, ex.Message, command.Source));
                
                    await _executionInfoRepository.SaveAsync(executionInfo);
                    
                    return;
                }

                _chaosKitty.Meow(command.OperationId);

                var change = new AccountBalanceChangeContract(
                    command.OperationId,
                    account.ModificationTimestamp,
                    account.Id,
                    account.ClientId,
                    command.AmountDelta,
                    account.Balance,
                    account.WithdrawTransferLimit,
                    command.Comment,
                    Convert(command.ChangeReasonType),
                    command.EventSourceId,
                    account.LegalEntity,
                    command.AuditLog,
                    command.AssetPairId,
                    command.TradingDay);

                var convertedAccount = Convert(account);

                publisher.PublishEvent(
                    new AccountChangedEvent(
                        change.ChangeTimestamp,
                        command.Source,
                        convertedAccount,
                        AccountChangedEventTypeContract.BalanceUpdated,
                        change,
                        command.OperationId)
                );
                
                await _executionInfoRepository.SaveAsync(executionInfo);
            }
        }
        private async Task Handle(DeleteAccountsCommand command, IEventPublisher publisher)
        {
            if (string.IsNullOrWhiteSpace(command.OperationId))
            {
                command.OperationId = Guid.NewGuid().ToString("N");
            }

            if (command.AccountIds == null || !command.AccountIds.Any())
            {
                publisher.PublishEvent(new AccountsDeletionFinishedEvent(
                                           command.OperationId,
                                           _systemClock.UtcNow.UtcDateTime,
                                           new List <string>(),
                                           new Dictionary <string, string>(),
                                           command.Comment
                                           ));
                return;
            }

            command.AccountIds = command.AccountIds.Distinct().ToList();

            var executionInfo = await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                command.OperationId,
                () => new OperationExecutionInfo <DeleteAccountsData>(
                    OperationName,
                    command.OperationId,
                    new DeleteAccountsData
            {
                State = DeleteAccountsState.Initiated,
                OperationId = command.OperationId,
                AccountIds = command.AccountIds,
                Comment = command.Comment,
            },
                    _systemClock.UtcNow.UtcDateTime));

            if (executionInfo.Data.State != DeleteAccountsState.Initiated)
            {
                return;
            }

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

            foreach (var accountToBlock in command.AccountIds.Except(failedAccounts.Keys))
            {
                try
                {
                    var account = (await _accountsRepository.GetAsync(accountToBlock))
                                  .RequiredNotNull(nameof(accountToBlock), $"Account {accountToBlock} does not exist.");

                    var result = await _accountsRepository.UpdateAccountAsync(
                        accountToBlock,
                        true,
                        true);

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

                    failedAccounts.Add(accountToBlock, exception.Message);
                }
            }

            if (!command.AccountIds.Except(failedAccounts.Keys).Any())
            {
                publisher.PublishEvent(new AccountsDeletionFinishedEvent(
                                           command.OperationId,
                                           _systemClock.UtcNow.UtcDateTime,
                                           new List <string>(),
                                           failedAccounts,
                                           executionInfo.Data.Comment
                                           ));
                return;
            }

            _chaosKitty.Meow($"{nameof(DeleteAccountsCommand)}: " +
                             "DeleteAccountsStartedInternalEvent: " +
                             $"{command.OperationId}");

            publisher.PublishEvent(new DeleteAccountsStartedInternalEvent(
                                       command.OperationId,
                                       _systemClock.UtcNow.UtcDateTime,
                                       failedAccounts
                                       ));
        }
예제 #11
0
        public async Task Handle(StartGiveTemporaryCapitalInternalCommand c, IEventPublisher publisher)
        {
            var executionInfo = await _executionInfoRepository.GetOrAddAsync(
                OperationName,
                c.OperationId,
                () => new OperationExecutionInfo <GiveTemporaryCapitalData>(
                    OperationName,
                    c.OperationId,
                    new GiveTemporaryCapitalData
            {
                State = TemporaryCapitalState.Initiated,
                OperationId = c.OperationId,
                AccountId = c.AccountId,
                Amount = c.Amount,
                Reason = c.Reason,
                Comment = c.Comment,
                AdditionalInfo = c.AdditionalInfo,
            },
                    _systemClock.UtcNow.UtcDateTime));

            if (executionInfo.Data.State != TemporaryCapitalState.Initiated)
            {
                return;
            }

            _chaosKitty.Meow(c.OperationId);

            var account = await _accountsRepository.GetAsync(c.AccountId);

            if (account == null)
            {
                publisher.PublishEvent(new GiveTemporaryCapitalFailedEvent(c.OperationId,
                                                                           _systemClock.UtcNow.UtcDateTime, $"Account {c.AccountId} not found"));
                return;
            }

            if (account.TemporaryCapital.Any(x => x.Id == c.OperationId))
            {
                publisher.PublishEvent(new GiveTemporaryCapitalStartedInternalEvent(c.OperationId,
                                                                                    _systemClock.UtcNow.UtcDateTime));
                return;
            }

            try
            {
                await _accountsRepository.UpdateAccountTemporaryCapitalAsync(c.AccountId,
                                                                             AccountManagementService.UpdateTemporaryCapital,
                                                                             new InternalModels.TemporaryCapital
                {
                    Id     = c.OperationId,
                    Amount = c.Amount,
                },
                                                                             true);
            }
            catch (Exception exception)
            {
                await _log.WriteErrorAsync(nameof(GiveTemporaryCapitalCommandsHandler),
                                           nameof(StartGiveTemporaryCapitalInternalCommand), exception);

                publisher.PublishEvent(new GiveTemporaryCapitalFailedEvent(c.OperationId,
                                                                           _systemClock.UtcNow.UtcDateTime, exception.Message));

                return;
            }

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

            publisher.PublishEvent(new GiveTemporaryCapitalStartedInternalEvent(c.OperationId,
                                                                                _systemClock.UtcNow.UtcDateTime));
        }