예제 #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);
            }
        }
예제 #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);

                //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);
            }
        }
예제 #3
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);
            }
        }
예제 #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
        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);
            }
        }
예제 #6
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);
            }
        }
예제 #7
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);
            }
        }