Ejemplo n.º 1
0
        public Task Handle(MarketDataChangedEvent evt)
        {
            if (_memoryCache.TryGetValue(evt.AssetPairId, out _))
            {
                return(Task.CompletedTask);
            }

            _subject.OnNext(evt);
            var assetPairSubject = _realm.Services.GetSubject <MarketDataChangedEvent>($"{MarketDataTopic}.{evt.AssetPairId.ToLower()}");

            assetPairSubject.OnNext(evt);
            _memoryCache.Set(evt.AssetPairId, evt.AssetPairId, _cacheInterval);
            return(Task.CompletedTask);
        }
Ejemplo n.º 2
0
        private async Task ProcessLimitOrdersAsync(LimitOrdersMessage message)
        {
            if (message.Orders == null || !message.Orders.Any())
            {
                return;
            }

            HashSet <string> limitOrderIds = message.Orders
                                             .Select(o => o.Order.Id)
                                             .ToHashSet();

            foreach (var orderMessage in message.Orders)
            {
                if (orderMessage.Trades == null || !orderMessage.Trades.Any())
                {
                    continue;
                }

                string assetPairId = orderMessage.Order.AssetPairId;

                AssetPair assetPair = _assetPairsRepository.TryGet(assetPairId);

                if (assetPair == null)
                {
                    _log.Error($"Asset pair {assetPairId} not found");
                    continue;
                }

                List <LimitOrdersMessage.Trade> allTrades = message.Orders.SelectMany(x => x.Trades).ToList();

                string marketDataKey  = RedisService.GetMarketDataKey(assetPairId);
                string baseVolumeKey  = RedisService.GetMarketDataBaseVolumeKey(assetPairId);
                string quoteVolumeKey = RedisService.GetMarketDataQuoteVolumeKey(assetPairId);
                string priceKey       = RedisService.GetMarketDataPriceKey(assetPairId);

                foreach (var tradeMessage in orderMessage.Trades.OrderBy(t => t.Timestamp).ThenBy(t => t.Index))
                {
                    long maxIndex = allTrades
                                    .Where(x => x.OppositeOrderId == tradeMessage.OppositeOrderId)
                                    .Max(t => t.Index);

                    var    price        = (decimal)tradeMessage.Price;
                    string priceString  = price.ToString(CultureInfo.InvariantCulture);
                    var    nowDate      = tradeMessage.Timestamp;
                    var    nowTradeDate = nowDate.AddMilliseconds(tradeMessage.Index);

                    await Task.WhenAll(
                        _database.HashSetAsync(marketDataKey, nameof(MarketSlice.LastPrice), priceString),
                        _database.SortedSetAddAsync(priceKey, RedisExtensions.SerializeWithTimestamp(priceString, nowTradeDate), nowTradeDate.ToUnixTime())
                        );

                    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;
                        }
                    }

                    decimal baseVolume;
                    decimal quotingVolume;

                    if (tradeMessage.Asset == assetPair.BaseAssetId)
                    {
                        baseVolume    = (decimal)tradeMessage.Volume;
                        quotingVolume = (decimal)tradeMessage.OppositeVolume;
                    }
                    else
                    {
                        baseVolume    = (decimal)tradeMessage.OppositeVolume;
                        quotingVolume = (decimal)tradeMessage.Volume;
                    }

                    if (tradeMessage.Price > 0 && baseVolume > 0 && quotingVolume > 0)
                    {
                        double now  = nowDate.ToUnixTime();
                        double from = (nowDate - _marketDataInterval).ToUnixTime();

                        decimal baseVolumeSum  = baseVolume;
                        decimal quoteVolumeSum = quotingVolume;
                        decimal priceChange    = 0;
                        decimal highValue      = (decimal)tradeMessage.Price;
                        decimal lowValue       = (decimal)tradeMessage.Price;

                        var tasks = new List <Task>();

                        var baseVolumesDataTask  = _database.SortedSetRangeByScoreAsync(baseVolumeKey, from, now);
                        var quoteVolumesDataTask = _database.SortedSetRangeByScoreAsync(quoteVolumeKey, from, now);
                        var priceDataTask        = _database.SortedSetRangeByScoreAsync(priceKey, from, now);

                        await Task.WhenAll(baseVolumesDataTask, quoteVolumesDataTask, priceDataTask);

                        baseVolumeSum += baseVolumesDataTask.Result
                                         .Where(x => x.HasValue)
                                         .Sum(x => RedisExtensions.DeserializeTimestamped <decimal>(x));

                        quoteVolumeSum += quoteVolumesDataTask.Result
                                          .Where(x => x.HasValue)
                                          .Sum(x => RedisExtensions.DeserializeTimestamped <decimal>(x));

                        var currentHigh = priceDataTask.Result.Any(x => x.HasValue)
                            ? priceDataTask.Result
                                          .Where(x => x.HasValue)
                                          .Max(x => RedisExtensions.DeserializeTimestamped <decimal>(x))
                            : (decimal?)null;

                        if (currentHigh.HasValue && currentHigh.Value > highValue)
                        {
                            highValue = currentHigh.Value;
                        }

                        var currentLow = priceDataTask.Result.Any(x => x.HasValue)
                            ? priceDataTask.Result
                                         .Where(x => x.HasValue)
                                         .Min(x => RedisExtensions.DeserializeTimestamped <decimal>(x))
                            : (decimal?)null;

                        if (currentLow.HasValue && currentLow.Value < lowValue)
                        {
                            lowValue = currentLow.Value;
                        }

                        var pricesData = priceDataTask.Result;

                        if (pricesData.Any() && pricesData[0].HasValue)
                        {
                            decimal openPrice = RedisExtensions.DeserializeTimestamped <decimal>(pricesData[0]);

                            if (openPrice > 0)
                            {
                                priceChange = ((decimal)tradeMessage.Price - openPrice) / openPrice;
                            }
                        }

                        tasks.Add(_database.SortedSetAddAsync(baseVolumeKey, RedisExtensions.SerializeWithTimestamp(baseVolume, nowTradeDate), now));
                        tasks.Add(_database.SortedSetAddAsync(quoteVolumeKey, RedisExtensions.SerializeWithTimestamp(quotingVolume, nowTradeDate), now));

                        await Task.WhenAll(tasks);

                        //send event only for the last trade in the order
                        if (tradeMessage.Index == maxIndex)
                        {
                            var evt = new MarketDataChangedEvent
                            {
                                AssetPairId = assetPairId,
                                VolumeBase  = baseVolumeSum,
                                VolumeQuote = quoteVolumeSum,
                                PriceChange = priceChange,
                                LastPrice   = (decimal)tradeMessage.Price,
                                High        = highValue,
                                Low         = lowValue
                            };

                            _cqrsEngine.PublishEvent(evt, MarketDataBoundedContext.Name);

                            try
                            {
                                await _tickerWriter.InsertOrReplaceAsync(new Ticker(assetPairId)
                                {
                                    VolumeBase  = baseVolumeSum,
                                    VolumeQuote = quoteVolumeSum,
                                    PriceChange = priceChange,
                                    LastPrice   = (decimal)tradeMessage.Price,
                                    High        = highValue,
                                    Low         = lowValue,
                                    UpdatedDt   = nowTradeDate
                                });
                            }
                            catch (Exception ex)
                            {
                                _log.Error(ex, "Error sending ticker to MyNySqlServer", context: evt.ToJson());
                            }
                        }
                    }
                }
            }
        }