예제 #1
0
        public MarginTradingAccount GetAccountById(string accountId)
        {
            if (string.IsNullOrWhiteSpace(accountId))
            {
                throw new ArgumentNullException(nameof(accountId));
            }

            if (_draftSnapshotKeeper.Initialized())
            {
                _log.WriteInfoAsync(nameof(AccountsProvider),
                                    nameof(GetAccountById),
                                    _draftSnapshotKeeper.TradingDay.ToJson(),
                                    "Draft snapshot keeper initialized and will be used as accounts provider");

                var accounts = _draftSnapshotKeeper
                               .GetAccountsAsync()
                               .GetAwaiter()
                               .GetResult();

                return(accounts
                       .SingleOrDefault(a => a.Id == accountId));
            }

            return(_accountsCacheService.TryGet(accountId));
        }
예제 #2
0
        public async Task <MtBackendResponse <MarginTradingAccountModel> > SetTradingCondition(
            [FromBody] SetTradingConditionModel model)
        {
            if (!_tradingConditionsCacheService.IsTradingConditionExists(model.TradingConditionId))
            {
                return(MtBackendResponse <MarginTradingAccountModel> .Error(
                           $"No trading condition {model.TradingConditionId} found in cache"));
            }

            var tradingCondition = _tradingConditionsCacheService.GetTradingCondition(model.TradingConditionId);

            IMarginTradingAccount account = _accountsCacheService.TryGet(model.ClientId, model.AccountId);

            if (account == null)
            {
                return(MtBackendResponse <MarginTradingAccountModel> .Error(
                           $"Account for client [{model.ClientId}] with id [{model.AccountId}] was not found"));
            }

            if (tradingCondition.LegalEntity != account.LegalEntity)
            {
                return(MtBackendResponse <MarginTradingAccountModel> .Error(
                           $"Account for client [{model.ClientId}] with id [{model.AccountId}] has LegalEntity " +
                           $"[{account.LegalEntity}], but trading condition wit id [{tradingCondition.Id}] has " +
                           $"LegalEntity [{tradingCondition.LegalEntity}]"));
            }

            account =
                await _accountManager.SetTradingCondition(model.ClientId, model.AccountId, model.TradingConditionId);

            return(MtBackendResponse <MarginTradingAccountModel> .Ok(account.ToBackendContract()));
        }
예제 #3
0
        private void ContinueOrFinishLiquidation(string operationId, LiquidationOperationData data, ICommandSender sender)
        {
            void FinishWithReason(string reason) => sender.SendCommand(new FinishLiquidationInternalCommand
            {
                OperationId           = operationId,
                CreationTime          = _dateService.Now(),
                Reason                = reason,
                LiquidationType       = data.LiquidationType,
                ProcessedPositionIds  = data.ProcessedPositionIds,
                LiquidatedPositionIds = data.LiquidatedPositionIds,
            }, _cqrsContextNamesSettings.TradingEngine);

            var account = _accountsCacheService.TryGet(data.AccountId);

            if (account == null)
            {
                sender.SendCommand(new FailLiquidationInternalCommand
                {
                    OperationId     = operationId,
                    CreationTime    = _dateService.Now(),
                    Reason          = "Account does not exist",
                    LiquidationType = data.LiquidationType,
                }, _cqrsContextNamesSettings.TradingEngine);
                return;
            }

            var accountLevel = account.GetAccountLevel();

            if (data.LiquidationType == LiquidationType.Forced)
            {
                if (!_ordersCache.Positions.GetPositionsByAccountIds(data.AccountId)
                    .Any(x => (string.IsNullOrWhiteSpace(data.AssetPairId) || x.AssetPairId == data.AssetPairId) &&
                         x.OpenDate < data.StartedAt &&
                         (data.Direction == null || x.Direction == data.Direction)))
                {
                    FinishWithReason("All positions are closed");
                }
                else
                {
                    LiquidatePositionsIfAnyAvailable(operationId, data, sender);
                }

                return;
            }

            if (accountLevel < AccountLevel.StopOut)
            {
                FinishWithReason($"Account margin level is {accountLevel}");
            }
            else
            {
                LiquidatePositionsIfAnyAvailable(operationId, data, sender);
            }
        }
예제 #4
0
        public void RemoveLiquidationStateIfNeeded(string accountId, string reason,
                                                   string liquidationOperationId = null, LiquidationType liquidationType = LiquidationType.Normal)
        {
            var account = _accountsCacheService.TryGet(accountId);

            if (account == null)
            {
                return;
            }

            if (!string.IsNullOrEmpty(account.LiquidationOperationId) &&
                (liquidationType == LiquidationType.Forced ||
                 account.GetAccountLevel() != AccountLevel.StopOut))
            {
                _accountsCacheService.TryFinishLiquidation(accountId, reason, liquidationOperationId);
            }
        }
예제 #5
0
        public async Task TestAccountCreation()
        {
            var account = Accounts[1];
            var time    = DateService.Now();

            var accountsProjection = AssertEnv();

            await accountsProjection.Handle(new AccountChangedEvent(time, "test",
                                                                    account, AccountChangedEventTypeContract.Created));

            var createdAccount = _accountsCacheService.TryGet(account.Id);

            Assert.True(createdAccount != null);
            Assert.AreEqual(account.Id, createdAccount.Id);
            Assert.AreEqual(account.Balance, createdAccount.Balance);
            Assert.AreEqual(account.TradingConditionId, createdAccount.TradingConditionId);
        }
예제 #6
0
        //has to be beyond global lock
        public void Validate(Order order)
        {
            #region Validate input params

            if (_assetDayOffService.IsDayOff(order.Instrument))
            {
                throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available");
            }

            if (order.Volume == 0)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume cannot be 0");
            }

            var asset = _assetPairsCache.TryGetAssetPairById(order.Instrument);

            if (asset == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, "Instrument not found");
            }

            var account = _accountsCacheService.TryGet(order.ClientId, order.AccountId);

            if (account == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidAccount, "Account not found");
            }

            if (!_quoteCashService.TryGetQuoteById(order.Instrument, out var quote))
            {
                throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Quote not found");
            }

            #endregion

            order.AssetAccuracy      = asset.Accuracy;
            order.AccountAssetId     = account.BaseAssetId;
            order.TradingConditionId = account.TradingConditionId;

            //check ExpectedOpenPrice for pending order
            if (order.ExpectedOpenPrice.HasValue)
            {
                if (_assetDayOffService.ArePendingOrdersDisabled(order.Instrument))
                {
                    throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available");
                }

                if (order.ExpectedOpenPrice <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, "Incorrect expected open price");
                }

                order.ExpectedOpenPrice = Math.Round(order.ExpectedOpenPrice ?? 0, order.AssetAccuracy);

                if (order.GetOrderType() == OrderDirection.Buy && order.ExpectedOpenPrice > quote.Ask ||
                    order.GetOrderType() == OrderDirection.Sell && order.ExpectedOpenPrice < quote.Bid)
                {
                    var reasonText = order.GetOrderType() == OrderDirection.Buy
                        ? string.Format(MtMessages.Validation_PriceAboveAsk, order.ExpectedOpenPrice, quote.Ask)
                        : string.Format(MtMessages.Validation_PriceBelowBid, order.ExpectedOpenPrice, quote.Bid);

                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, reasonText, $"{order.Instrument} quote (bid/ask): {quote.Bid}/{quote.Ask}");
                }
            }

            var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument);

            if (accountAsset.DealLimit > 0 && Math.Abs(order.Volume) > accountAsset.DealLimit)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume,
                                                 $"Margin Trading is in beta testing. The volume of a single order is temporarily limited to {accountAsset.DealLimit} {accountAsset.Instrument}. Thank you for using Lykke Margin Trading, the limit will be cancelled soon!");
            }

            //check TP/SL
            if (order.TakeProfit.HasValue)
            {
                order.TakeProfit = Math.Round(order.TakeProfit.Value, order.AssetAccuracy);
            }

            if (order.StopLoss.HasValue)
            {
                order.StopLoss = Math.Round(order.StopLoss.Value, order.AssetAccuracy);
            }

            ValidateOrderStops(order.GetOrderType(), quote, accountAsset.DeltaBid, accountAsset.DeltaAsk, order.TakeProfit, order.StopLoss, order.ExpectedOpenPrice, order.AssetAccuracy);

            ValidateInstrumentPositionVolume(accountAsset, order);

            if (!_accountUpdateService.IsEnoughBalance(order))
            {
                throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance, MtMessages.Validation_NotEnoughBalance, $"Account available balance is {account.GetTotalCapital()}");
            }
        }
예제 #7
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>()
                });
            }
        }
예제 #8
0
        public async Task <(Order order, List <Order> relatedOrders)> ValidateRequestAndCreateOrders(
            OrderPlaceRequest request)
        {
            #region Validate properties

            if (request.Volume == 0)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume can not be 0");
            }

            var assetPair = GetAssetPairIfAvailableForTrading(request.InstrumentId, request.Type.ToType <OrderType>(),
                                                              request.ForceOpen, false);

            if (request.Type != OrderTypeContract.Market || request.AdditionalInfo.IsCancellationTrade(out _))
            {
                if (!request.Price.HasValue)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                     $"Price is required for {request.Type} order");
                }
                else
                {
                    request.Price = Math.Round(request.Price.Value, assetPair.Accuracy);

                    if (request.Price <= 0)
                    {
                        throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                         $"Price should be more than 0");
                    }
                }
            }
            else
            {
                //always ignore price for market orders
                request.Price = null;
            }

            var account = _accountsCacheService.TryGet(request.AccountId);

            if (account == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidAccount, "Account not found");
            }

            ValidateValidity(request.Validity, request.Type.ToType <OrderType>());

            ITradingInstrument tradingInstrument;
            try
            {
                tradingInstrument =
                    _tradingInstrumentsCache.GetTradingInstrument(account.TradingConditionId, assetPair.Id);
            }
            catch
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 $"Instrument {assetPair.Id} is not available for trading on selected account");
            }

            if (tradingInstrument.DealMinLimit > 0 && Math.Abs(request.Volume) < tradingInstrument.DealMinLimit)
            {
                throw new ValidateOrderException(OrderRejectReason.MinOrderSizeLimit,
                                                 $"The minimum volume of a single order is limited to {tradingInstrument.DealMinLimit} {tradingInstrument.Instrument}.");
            }

            #endregion

            var equivalentSettings = GetReportingEquivalentPricesSettings(account.LegalEntity);

            if (request.Type == OrderTypeContract.StopLoss ||
                request.Type == OrderTypeContract.TakeProfit ||
                request.Type == OrderTypeContract.TrailingStop)
            {
                var order = await ValidateAndGetSlOrTpOrder(request, request.Type, request.Price, equivalentSettings,
                                                            null);

                return(order, new List <Order>());
            }

            var initialParameters = await GetOrderInitialParameters(request.InstrumentId, account.LegalEntity,
                                                                    equivalentSettings, account.BaseAssetId);

            var volume = request.Direction == OrderDirectionContract.Sell ? -Math.Abs(request.Volume)
                : request.Direction == OrderDirectionContract.Buy ? Math.Abs(request.Volume)
                : request.Volume;

            var originator = GetOriginator(request.Originator);

            var baseOrder = new Order(initialParameters.Id, initialParameters.Code, request.InstrumentId, volume,
                                      initialParameters.Now, initialParameters.Now, request.Validity, account.Id, account.TradingConditionId,
                                      account.BaseAssetId, request.Price, equivalentSettings.EquivalentAsset, OrderFillType.FillOrKill,
                                      string.Empty, account.LegalEntity, request.ForceOpen, request.Type.ToType <OrderType>(),
                                      request.ParentOrderId, null, originator, initialParameters.EquivalentPrice,
                                      initialParameters.FxPrice, initialParameters.FxAssetPairId, initialParameters.FxToAssetPairDirection,
                                      OrderStatus.Placed, request.AdditionalInfo);

            ValidateBaseOrderPrice(baseOrder, baseOrder.Price);

            var relatedOrders = new List <Order>();

            if (request.StopLoss.HasValue)
            {
                request.StopLoss = Math.Round(request.StopLoss.Value, assetPair.Accuracy);

                if (request.StopLoss <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidStoploss,
                                                     $"StopLoss should be more then 0");
                }

                var orderType = request.UseTrailingStop ? OrderTypeContract.TrailingStop : OrderTypeContract.StopLoss;

                var sl = await ValidateAndGetSlOrTpOrder(request, orderType, request.StopLoss,
                                                         equivalentSettings, baseOrder);

                if (sl != null)
                {
                    relatedOrders.Add(sl);
                }
            }

            if (request.TakeProfit.HasValue)
            {
                request.TakeProfit = Math.Round(request.TakeProfit.Value, assetPair.Accuracy);

                if (request.TakeProfit <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit,
                                                     $"TakeProfit should be more then 0");
                }

                var tp = await ValidateAndGetSlOrTpOrder(request, OrderTypeContract.TakeProfit, request.TakeProfit,
                                                         equivalentSettings, baseOrder);

                if (tp != null)
                {
                    relatedOrders.Add(tp);
                }
            }

            await ValidateProductComplexityConfirmation(request, account);

            return(baseOrder, relatedOrders);
        }
예제 #9
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);
            }
        }
예제 #10
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);
            }
        }
        public bool?IsMarginTradingEnabledByAccountId(string accountId)
        {
            var acc = _accountsCacheService.TryGet(accountId);

            return(!acc?.IsDisabled); // todo review and fix?
        }