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())); }
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); }
/// <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 }); }
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(); } }
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); } }
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); } }
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}].")); } } }
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}].")); } } }
/// <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(); } }