Beispiel #1
0
        public Task <List <string> > GetAllAccountIdsFiltered([FromBody] ActiveAccountsRequest request)
        {
            if (request.IsAndClauseApplied != null &&
                (request.ActiveOrderAssetPairIds == null || request.ActivePositionAssetPairIds == null))
            {
                throw new ArgumentException("isAndClauseApplied might be set only if both filters are set",
                                            nameof(request.IsAndClauseApplied));
            }

            List <string> accountIdsByOrders = null;

            if (request.ActiveOrderAssetPairIds != null)
            {
                accountIdsByOrders = _orderReader.GetPending()
                                     .Where(x => request.ActiveOrderAssetPairIds.Count == 0 ||
                                            request.ActiveOrderAssetPairIds.Contains(x.AssetPairId))
                                     .Select(x => x.AccountId)
                                     .Distinct()
                                     .ToList();
            }

            List <string> accountIdsByPositions = null;

            if (request.ActivePositionAssetPairIds != null)
            {
                accountIdsByPositions = _orderReader.GetPositions()
                                        .Where(x => request.ActivePositionAssetPairIds.Count == 0 ||
                                               request.ActivePositionAssetPairIds.Contains(x.AssetPairId))
                                        .Select(x => x.AccountId)
                                        .Distinct()
                                        .ToList();
            }

            if (accountIdsByOrders == null && accountIdsByPositions != null)
            {
                return(Task.FromResult(accountIdsByPositions.OrderBy(x => x).ToList()));
            }

            if (accountIdsByOrders != null && accountIdsByPositions == null)
            {
                return(Task.FromResult(accountIdsByOrders.OrderBy(x => x).ToList()));
            }

            if (accountIdsByOrders == null && accountIdsByPositions == null)
            {
                return(Task.FromResult(_accountsCacheService.GetAll().Select(x => x.Id).OrderBy(x => x).ToList()));
            }

            if (request.IsAndClauseApplied ?? false)
            {
                return(Task.FromResult(accountIdsByOrders.Intersect(accountIdsByPositions)
                                       .OrderBy(x => x).ToList()));
            }

            return(Task.FromResult(accountIdsByOrders.Concat(accountIdsByPositions)
                                   .Distinct().OrderBy(x => x).ToList()));
        }
Beispiel #2
0
        private decimal GetNetPositionCloseVolume(ICollection <string> positionIds, string accountId)
        {
            var netPositionVolume = _orderReader.GetPositions()
                                    .Where(x => positionIds.Contains(x.Id) &&
                                           (string.IsNullOrEmpty(accountId) || x.AccountId == accountId))
                                    .Sum(x => x.Volume);

            return(-netPositionVolume);
        }
Beispiel #3
0
        /// <inheritdoc/>
        public async Task <SnapshotValidationResult> ValidateCurrentStateAsync()
        {
            await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                      $"Snapshot validation started: {DateTime.UtcNow}");

            var currentOrders    = _orderCache.GetAllOrders();
            var currentPositions = _orderCache.GetPositions();

            var tradingEngineSnapshot = await _tradingEngineSnapshotsRepository.GetLastAsync();

            await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                      $"Last snapshot correlationId {tradingEngineSnapshot.CorrelationId}, tradingDay {tradingEngineSnapshot.TradingDay}, timestamp {tradingEngineSnapshot.Timestamp}");

            var lastOrders    = GetOrders(tradingEngineSnapshot);
            var lastPositions = GetPositions(tradingEngineSnapshot);

            var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp);

            var positionsHistory = await _positionsHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp);

            var restoredOrders    = RestoreOrdersCurrentStateFromHistory(lastOrders, ordersHistory);
            var restoredPositions = RestorePositionsCurrentStateFromHistory(lastPositions, positionsHistory);

            var ordersValidationResult    = CompareOrders(currentOrders, restoredOrders);
            var positionsValidationResult = ComparePositions(currentPositions, restoredPositions);

            if (ordersValidationResult.IsValid)
            {
                await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                          $"Orders validation result is valid");
            }
            else
            {
                await _log.WriteWarningAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                             $"Orders validation result is NOT valid. Extra: {ordersValidationResult.Extra.Count}, missed: {ordersValidationResult.Missed.Count}, inconsistent: {ordersValidationResult.Inconsistent.Count}");
            }

            if (positionsValidationResult.IsValid)
            {
                await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                          $"Positions validation result is valid");
            }
            else
            {
                await _log.WriteWarningAsync(nameof(SnapshotValidationService), nameof(ValidateCurrentStateAsync),
                                             $"Positions validation result is NOT valid. Extra: {positionsValidationResult.Extra.Count}, missed: {positionsValidationResult.Missed.Count}, inconsistent: {positionsValidationResult.Inconsistent.Count}");
            }

            return(new SnapshotValidationResult
            {
                Orders = ordersValidationResult,
                Positions = positionsValidationResult,
                PreviousSnapshotCorrelationId = tradingEngineSnapshot.CorrelationId
            });
        }
Beispiel #4
0
        public async Task <string> MakeTradingDataSnapshot(DateTime tradingDay, string correlationId)
        {
            if (!_scheduleSettingsCacheService.TryGetPlatformCurrentDisabledInterval(out var disabledInterval))
            {
                throw new Exception(
                          "Trading should be stopped for whole platform in order to make trading data snapshot.");
            }

            if (disabledInterval.Start.AddDays(-1) > tradingDay.Date || disabledInterval.End < tradingDay.Date)
            {
                throw new Exception(
                          $"{nameof(tradingDay)}'s Date component must be from current disabled interval's Start -1d to End: [{disabledInterval.Start.AddDays(-1)}, {disabledInterval.End}].");
            }

            await _semaphoreSlim.WaitAsync();

            try
            {
                var orders       = _orderReader.GetAllOrders();
                var positions    = _orderReader.GetPositions();
                var accountStats = _accountsCacheService.GetAll();
                var bestFxPrices = _fxRateCacheService.GetAllQuotes();
                var bestPrices   = _quoteCacheService.GetAllQuotes();

                await _tradingEngineSnapshotsRepository.Add(tradingDay,
                                                            correlationId,
                                                            _dateService.Now(),
                                                            orders.Select(x => x.ConvertToContract(_orderReader)).ToJson(),
                                                            positions.Select(x => x.ConvertToContract(_orderReader)).ToJson(),
                                                            accountStats.Select(x => x.ConvertToContract()).ToJson(),
                                                            bestFxPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson(),
                                                            bestPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson());

                return($@"Trading data snapshot was written to the storage. 
Orders: {orders.Length}, positions: {positions.Length}, accounts: {accountStats.Count}, 
best FX prices: {bestFxPrices.Count}, best trading prices: {bestPrices.Count}.");
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
Beispiel #5
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);
            }
        }
Beispiel #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);
            }
        }
Beispiel #7
0
        public async Task Handle(ProductChangedEvent @event)
        {
            switch (@event.ChangeType)
            {
            case ChangeType.Creation:
            case ChangeType.Edition:
                if ([email protected])
                {
                    _log.WriteInfo(nameof(ProductChangedProjection),
                                   nameof(Handle),
                                   $"ProductChangedEvent received for productId: {@event.NewValue.ProductId}, but it was ignored because it has not been started yet.");
                    return;
                }

                break;

            case ChangeType.Deletion:
                if ([email protected])
                {
                    _log.WriteInfo(nameof(ProductChangedProjection),
                                   nameof(Handle),
                                   $"ProductChangedEvent received for productId: {@event.OldValue.ProductId}, but it was ignored because it has not been started yet.");
                    return;
                }

                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if (@event.ChangeType == ChangeType.Deletion)
            {
                CloseAllOrders();

                ValidatePositions(@event.OldValue.ProductId);

                _assetPairsCache.Remove(@event.OldValue.ProductId);
            }
            else
            {
                if (@event.NewValue.IsDiscontinued)
                {
                    CloseAllOrders();
                    RemoveQuoteFromCache();
                }

                await _tradingInstrumentsManager.UpdateTradingInstrumentsCacheAsync();

                var isAdded = _assetPairsCache.AddOrUpdate(AssetPair.CreateFromProduct(@event.NewValue,
                                                                                       _mtSettings.DefaultLegalEntitySettings.DefaultLegalEntity));

                if (@event.NewValue.TradingCurrency != AssetPairConstants.BaseCurrencyId)
                {
                    _assetPairsCache.AddOrUpdate(AssetPair.CreateFromCurrency(@event.NewValue.TradingCurrency,
                                                                              _mtSettings.DefaultLegalEntitySettings.DefaultLegalEntity));
                }

                //only for product
                if (isAdded)
                {
                    await _scheduleSettingsCacheService.UpdateScheduleSettingsAsync();
                }

                if (@event.ChangeType == ChangeType.Edition &&
                    @event.OldValue.IsTradingDisabled != @event.NewValue.IsTradingDisabled)
                {
                    await HandleTradingDisabled(@event.NewValue, @event.Username);
                }
            }

            void RemoveQuoteFromCache()
            {
                var result = _quoteCache.RemoveQuote(@event.OldValue.ProductId);

                if (result != RemoveQuoteErrorCode.None)
                {
                    _log.WriteWarning(nameof(ProductChangedProjection), nameof(RemoveQuoteFromCache), result.Message);
                }
            }

            void CloseAllOrders()
            {
                try
                {
                    foreach (var order in _orderReader.GetPending()
                             .Where(x => x.AssetPairId == @event.OldValue.ProductId))
                    {
                        _tradingEngine.CancelPendingOrder(order.Id, null,
                                                          null, OrderCancellationReason.InstrumentInvalidated);
                    }
                }
                catch (Exception exception)
                {
                    _log.WriteError(nameof(ProductChangedProjection), nameof(CloseAllOrders), exception);
                    throw;
                }
            }

            void ValidatePositions(string assetPairId)
            {
                var positions = _orderReader.GetPositions(assetPairId);

                if (positions.Any())
                {
                    _log.WriteFatalError(nameof(ProductChangedProjection), nameof(ValidatePositions),
                                         new Exception(
                                             $"{positions.Length} positions are opened for [{assetPairId}], first: [{positions.First().Id}]."));
                }
            }
        }
Beispiel #8
0
        public async Task Handle(AssetPairChangedEvent @event)
        {
            //deduplication is not required, it's ok if an object is updated multiple times
            if (@event.AssetPair?.Id == null)
            {
                await _log.WriteWarningAsync(nameof(AssetPairProjection), nameof(Handle),
                                             "AssetPairChangedEvent contained no asset pair id");

                return;
            }

            if (IsDelete(@event))
            {
                CloseAllOrders();

                ValidatePositions(@event.AssetPair.Id);

                _assetPairsCache.Remove(@event.AssetPair.Id);
            }
            else
            {
                if (@event.AssetPair.IsDiscontinued)
                {
                    CloseAllOrders();
                }

                _assetPairsCache.AddOrUpdate(new AssetPair(
                                                 id: @event.AssetPair.Id,
                                                 name: @event.AssetPair.Name,
                                                 baseAssetId: @event.AssetPair.BaseAssetId,
                                                 quoteAssetId: @event.AssetPair.QuoteAssetId,
                                                 accuracy: @event.AssetPair.Accuracy,
                                                 legalEntity: @event.AssetPair.LegalEntity,
                                                 basePairId: @event.AssetPair.BasePairId,
                                                 matchingEngineMode: @event.AssetPair.MatchingEngineMode.ToType <MatchingEngineMode>(),
                                                 stpMultiplierMarkupBid: @event.AssetPair.StpMultiplierMarkupBid,
                                                 stpMultiplierMarkupAsk: @event.AssetPair.StpMultiplierMarkupAsk,
                                                 isSuspended: @event.AssetPair.IsSuspended,
                                                 isFrozen: @event.AssetPair.IsFrozen,
                                                 isDiscontinued: @event.AssetPair.IsDiscontinued
                                                 ));
            }

            await _scheduleSettingsCacheService.UpdateSettingsAsync();

            void CloseAllOrders()
            {
                try
                {
                    foreach (var order in _orderReader.GetPending().Where(x => x.AssetPairId == @event.AssetPair.Id))
                    {
                        _tradingEngine.CancelPendingOrder(order.Id, null, @event.OperationId,
                                                          null, OrderCancellationReason.InstrumentInvalidated);
                    }
                }
                catch (Exception exception)
                {
                    _log.WriteError(nameof(AssetPairProjection), nameof(CloseAllOrders), exception);
                    throw;
                }
            }

            void ValidatePositions(string assetPairId)
            {
                var positions = _orderReader.GetPositions(assetPairId);

                if (positions.Any())
                {
                    _log.WriteFatalError(nameof(AssetPairProjection), nameof(ValidatePositions),
                                         new Exception($"{positions.Length} positions are opened for [{assetPairId}], first: [{positions.First().Id}]."));
                }
            }
        }
Beispiel #9
0
        /// <inheritdoc />
        public async Task <string> MakeTradingDataSnapshot(DateTime tradingDay, string correlationId, SnapshotStatus status = SnapshotStatus.Final)
        {
            if (!_scheduleSettingsCacheService.TryGetPlatformCurrentDisabledInterval(out var disabledInterval))
            {
                //TODO: remove later (if everything will work and we will never go to this branch)
                _scheduleSettingsCacheService.MarketsCacheWarmUp();

                if (!_scheduleSettingsCacheService.TryGetPlatformCurrentDisabledInterval(out disabledInterval))
                {
                    throw new Exception(
                              $"Trading should be stopped for whole platform in order to make trading data snapshot. Current schedule: {_scheduleSettingsCacheService.GetPlatformTradingSchedule()?.ToJson()}");
                }
            }

            if (disabledInterval.Start.AddDays(-1) > tradingDay.Date || disabledInterval.End < tradingDay.Date)
            {
                throw new Exception(
                          $"{nameof(tradingDay)}'s Date component must be from current disabled interval's Start -1d to End: [{disabledInterval.Start.AddDays(-1)}, {disabledInterval.End}].");
            }

            if (Lock.CurrentCount == 0)
            {
                throw new InvalidOperationException("Trading data snapshot creation is already in progress");
            }

            // We must be sure all messages have been processed by history brokers before starting current state validation.
            // If one or more queues contain not delivered messages the snapshot can not be created.
            _queueValidationService.ThrowExceptionIfQueuesNotEmpty(true);

            // Before starting snapshot creation the current state should be validated.
            var validationResult = await _snapshotValidationService.ValidateCurrentStateAsync();

            if (!validationResult.IsValid)
            {
                var ex = new InvalidOperationException(
                    $"The trading data snapshot might be corrupted. The current state of orders and positions is incorrect. Check the dbo.BlobData table for more info: container {LykkeConstants.MtCoreSnapshotBlobContainer}, correlationId {correlationId}");
                await _log.WriteFatalErrorAsync(nameof(SnapshotService),
                                                nameof(MakeTradingDataSnapshot),
                                                validationResult.ToJson(),
                                                ex);

                await _blobRepository.WriteAsync(LykkeConstants.MtCoreSnapshotBlobContainer, correlationId, validationResult);
            }
            else
            {
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          "The current state of orders and positions is correct.");
            }

            await Lock.WaitAsync();

            try
            {
                var orders     = _orderReader.GetAllOrders();
                var ordersJson = orders.Select(o => o.ConvertToSnapshotContract(_orderReader, status)).ToJson();
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Preparing data... {orders.Length} orders prepared.");

                var positions     = _orderReader.GetPositions();
                var positionsJson = positions.Select(p => p.ConvertToSnapshotContract(_orderReader, status)).ToJson();
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Preparing data... {positions.Length} positions prepared.");

                var accountStats = _accountsCacheService.GetAll();
                var accountsJson = accountStats.Select(a => a.ConvertToSnapshotContract(status)).ToJson();
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Preparing data... {accountStats.Count} accounts prepared.");

                var bestFxPrices     = _fxRateCacheService.GetAllQuotes();
                var bestFxPricesData = bestFxPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson();
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Preparing data... {bestFxPrices.Count} best FX prices prepared.");

                var bestPrices     = _quoteCacheService.GetAllQuotes();
                var bestPricesData = bestPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson();
                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Preparing data... {bestPrices.Count} best trading prices prepared.");

                var msg = $"TradingDay: {tradingDay:yyyy-MM-dd}, Orders: {orders.Length}, positions: {positions.Length}, accounts: {accountStats.Count}, best FX prices: {bestFxPrices.Count}, best trading prices: {bestPrices.Count}.";

                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Starting to write trading data snapshot. {msg}");

                var snapshot = new TradingEngineSnapshot(
                    tradingDay,
                    correlationId,
                    _dateService.Now(),
                    ordersJson: ordersJson,
                    positionsJson: positionsJson,
                    accountsJson: accountsJson,
                    bestFxPricesJson: bestFxPricesData,
                    bestTradingPricesJson: bestPricesData,
                    status: status);

                await _tradingEngineSnapshotsRepository.AddAsync(snapshot);

                await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot),
                                          $"Trading data snapshot was written to the storage. {msg}");

                return($"Trading data snapshot was written to the storage. {msg}");
            }
            finally
            {
                Lock.Release();
            }
        }