public async Task Getting_enabled_pair_returns_enabled_pair() { // Arrange _assetsServiceMock .Setup(s => s.TryGetAssetPairAsync(It.Is <string>(a => a == "EURUSD"), It.IsAny <CancellationToken>())) .ReturnsAsync((string a, CancellationToken t) => new AssetPair { Id = a, IsDisabled = false }); // Act var pair = await _manager.TryGetEnabledPairAsync("EURUSD"); // Assert Assert.IsNotNull(pair); Assert.AreEqual("EURUSD", pair.Id); }
public LongTaskLaunchResult Filtrate(ICandlesFiltrationRequest request, bool analyzeOnly) { // We should not run filtration multiple times before the first attempt ends. if (Health != null && Health.State == CandlesFiltrationState.InProgress) { return(LongTaskLaunchResult.AlreadyInProgress); } // And also we should check if the specified asset pair is enabled. var storedAssetPair = _assetPairsManager.TryGetEnabledPairAsync(request.AssetPairId).GetAwaiter().GetResult(); if (storedAssetPair == null || !_candlesHistoryRepository.CanStoreAssetPair(request.AssetPairId)) { return(LongTaskLaunchResult.AssetPairNotSupported); } var epsilon = Math.Pow(10, -storedAssetPair.Accuracy); _log.Info(nameof(Filtrate), $"Starting candles with extreme price filtration for {request.AssetPairId}..."); Health = new CandlesFiltrationHealthReport(request.AssetPairId, request.LimitLow, request.LimitHigh, analyzeOnly); var priceTypeTasks = new List <Task>(); if (request.PriceType.HasValue) { priceTypeTasks.Add( DoFiltrateAsync(request.AssetPairId, request.LimitLow, request.LimitHigh, request.PriceType.Value, epsilon, analyzeOnly)); } else { foreach (var priceType in Constants.StoredPriceTypes) { priceTypeTasks.Add( DoFiltrateAsync(request.AssetPairId, request.LimitLow, request.LimitHigh, priceType, epsilon, analyzeOnly)); } } Task.WhenAll(priceTypeTasks.ToArray()).ContinueWith(t => { Health.State = CandlesFiltrationState.Finished; if (analyzeOnly) { _log.Info(nameof(Filtrate), $"Filtration for {request.AssetPairId} finished: analyze only. Total amount of candles to delete: {Health.DeletedCandlesCount.Values.Sum()}, " + $"total amount of candles to replace: {Health.ReplacedCandlesCount.Values.Sum()}. Errors count: {Health.Errors.Count}."); } else { _log.Info(nameof(Filtrate), $"Filtration for {request.AssetPairId} finished. Total amount of deleted Sec candles: {Health.DeletedCandlesCount.Values.Sum()}, " + $"total amount of replaced bigger candles: {Health.ReplacedCandlesCount.Values.Sum()}. Errors count: {Health.Errors.Count}."); } }); return(LongTaskLaunchResult.Started); }
public bool Migrate(DateTime?removeByDate, string[] assetPairIds) { if (!MigrationEnabled) { return(false); } // We should not run migration multiple times before the first attempt ends. if (!_tradesMigrationHealthService.CanStartMigration) { return(false); } // First of all, we will check if we can store the requested asset pairs. Additionally, let's // generate asset search tokens for using it in TradesSqlHistoryRepository (which has no access // to AssetPairsManager). var assetSearchTokens = new List <(string AssetPairId, string SearchToken, string ReverseSearchToken)>(); foreach (var assetPairId in assetPairIds) { var storedAssetPair = _assetPairsManager.TryGetEnabledPairAsync(assetPairId).GetAwaiter().GetResult(); if (storedAssetPair == null) { _log.WriteInfoAsync(nameof(TradesMigrationManager), nameof(Migrate), $"Trades migration: Asset pair {assetPairId} is not currently enabled. Skipping.").GetAwaiter().GetResult(); continue; } assetSearchTokens.Add(( AssetPairId: assetPairId, SearchToken: storedAssetPair.BaseAssetId + storedAssetPair.QuotingAssetId, ReverseSearchToken: storedAssetPair.QuotingAssetId + storedAssetPair.BaseAssetId)); } // Creating a blank health report _tradesMigrationHealthService.Prepare(_sqlQueryBatchSize, removeByDate); // If there is nothing to migrate, just return "success". if (!assetSearchTokens.Any()) { _tradesMigrationHealthService.State = TradesMigrationState.Finished; _log.WriteInfoAsync(nameof(TradesMigrationManager), nameof(Migrate), $"Trades migration: None of the requested asset pairs can be stored. Migration canceled.").GetAwaiter().GetResult(); return(true); } // 1. We do not parallel the migration of different asset pairs consciously. // 2. If we have no upper date-time limit for migration, we migrate everything. // 3. We do not await while the migration process is finished for it may take a lot of time. Immediately return to a caller code instead. _tradesMigrationService.MigrateTradesCandlesAsync(removeByDate, assetSearchTokens); return(true); }
public async Task <LongTaskLaunchResult> RunFixMidPricesAsync(string assetPairId, bool analyzeOnly) { // We should not run filtration multiple times before the first attempt ends. if (Health != null && Health.State == CandlesFiltrationState.InProgress) { return(LongTaskLaunchResult.AlreadyInProgress); } // And also we should check if the specified asset pair is enabled. var storedAssetPair = await _assetPairsManager.TryGetEnabledPairAsync(assetPairId); if (storedAssetPair == null || !_candlesHistoryRepository.CanStoreAssetPair(assetPairId)) { return(LongTaskLaunchResult.AssetPairNotSupported); } _log.Info($"Starting mid candles price fix for {assetPairId}..."); Health = new MidPricesFixHealthReport(assetPairId, analyzeOnly); #pragma warning disable 4014 Task.Run(async() => #pragma warning restore 4014 { await FixMidPricesAsync(assetPairId, storedAssetPair.Accuracy, analyzeOnly); Health.State = CandlesFiltrationState.Finished; if (analyzeOnly) { _log.Info($"Mid candle prices fix for {assetPairId} finished: analyze only. Total amount of corrupted candles: {Health.CorruptedCandlesCount}, " + $" errors count: {Health.Errors.Count}."); } else { _log.Info($"Mid candle prices fix for {assetPairId} finished. Total amount of corrupted candles: {Health.CorruptedCandlesCount}, " + $"errors count: {Health.Errors.Count}."); } }); return(LongTaskLaunchResult.Started); }
private async Task ProcessLimitTradesAsync(LimitOrdersMessage message) { if (message.Orders == null || !message.Orders.Any()) { return; } var limitOrderIds = message.Orders .Select(o => o.Order.Id) .ToHashSet(); foreach (var orderMessage in message.Orders) { if (orderMessage.Trades == null) { continue; } var assetPair = await _assetPairsManager.TryGetEnabledPairAsync(orderMessage.Order.AssetPairId); foreach (var tradeMessage in orderMessage.Trades.OrderBy(t => t.Timestamp).ThenBy(t => t.Index)) { // If both orders of the trade are limit, then both of them should be contained in the single message, // this is by design. var isOppositeOrderIsLimit = limitOrderIds.Contains(tradeMessage.OppositeOrderId); // If opposite order is market order, then unconditionally takes the given limit order. // But if both of orders are limit orders, we should take only one of them. if (isOppositeOrderIsLimit) { var isBuyOrder = orderMessage.Order.Volume > 0; // Takes trade only for the sell limit orders if (isBuyOrder) { continue; } } // Volumes in the asset pair base and quoting assets double baseVolume; double quotingVolume; if (tradeMessage.Asset == assetPair.BaseAssetId) { baseVolume = tradeMessage.Volume; quotingVolume = tradeMessage.OppositeVolume; } else { baseVolume = tradeMessage.OppositeVolume; quotingVolume = tradeMessage.Volume; } // Just discarding trades with negative prices and\or volumes. It's better to do it here instead of // at the first line of foreach 'case we have some additional trade selection logic in the begining. // ReSharper disable once InvertIf if (tradeMessage.Price > 0 && baseVolume > 0 && quotingVolume > 0) { var trade = new Trade( orderMessage.Order.AssetPairId, tradeMessage.Timestamp, baseVolume, quotingVolume, tradeMessage.Price ); await _candlesManager.ProcessTradeAsync(trade); } else { await _log.WriteWarningAsync(nameof(ProcessLimitTradesAsync), tradeMessage.ToJson(), "Got a Spot trade with non-positive price or volume value."); } } } }
public async Task ProcessMtQuoteAsync(MtQuoteDto mtQuote) { if (mtQuote == null) { throw new ArgumentNullException(nameof(mtQuote)); } var assetPair = await _assetPairsManager.TryGetEnabledPairAsync(mtQuote.AssetPair?.Trim()); if (assetPair == null) { return; } var changedUpdates = new ConcurrentBag <CandleUpdateResult>(); var midPriceQuote = _midPriceQuoteGenerator.TryGenerate( mtQuote.AssetPair, mtQuote.Ask, mtQuote.Bid, mtQuote.Timestamp, assetPair.Accuracy); try { // Updates all intervals in parallel var processingTasks = _intervals .Select(timeInterval => Task.Factory.StartNew(() => { ProcessQuoteInterval( mtQuote.AssetPair, mtQuote.Timestamp, mtQuote.Ask, false, timeInterval, midPriceQuote, changedUpdates); })) .Concat(_intervals .Select(timeInterval => Task.Factory.StartNew(() => { ProcessQuoteInterval( mtQuote.AssetPair, mtQuote.Timestamp, mtQuote.Bid, true, timeInterval, null, changedUpdates); }))); await Task.WhenAll(processingTasks); // Publishes updated candles if (!changedUpdates.IsEmpty) { var publisher = await _candlesPublisherProvider.GetForAssetPair(assetPair.Id); await publisher.PublishAsync(changedUpdates); } } catch (Exception) { // Failed to publish one or several candles, so processing should be cancelled foreach (var updateResult in changedUpdates) { _candlesGenerator.Undo(updateResult); } throw; } }