Exemple #1
0
        public async Task <ExternalLimitOrder> CreateLimitOrderAsync(string exchangeName, string assetPair,
                                                                     decimal price, decimal volume, OrderType orderType)
        {
            IExchange exchange = GetExchange(exchangeName);

            AssetPairModel assetPairModel = _marketInstrumentService.GetAssetPair(assetPair, exchange.Name);

            if (assetPairModel == null)
            {
                throw new FailedOperationException("Asset pair settings does not exist");
            }

            price = price.TruncateDecimalPlaces(assetPairModel.PriceAccuracy, orderType == OrderType.Sell);

            volume = Math.Round(volume, assetPairModel.VolumeAccuracy);

            if (volume < assetPairModel.MinVolume)
            {
                throw new FailedOperationException("The volume is too small");
            }

            string orderId = await exchange.CreateLimitOrderAsync(assetPair, price, volume, orderType);

            var externalLimitOrder =
                new ExternalLimitOrder(orderId, exchange.Name, assetPair, price, volume, orderType);

            return(externalLimitOrder);
        }
        private async Task ProcessMessageAsync(OrderBook orderBook)
        {
            try
            {
                AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPairs()
                                                   .FirstOrDefault(o => o.Exchange == orderBook.Source && o.Id == orderBook.Asset);

                if (assetPairSettings == null)
                {
                    return;
                }

                await _orderBookService.UpdateAsync(assetPairSettings.Exchange, assetPairSettings.Name,
                                                    orderBook.Timestamp,
                                                    orderBook.Asks.Select(orderBookItem => new OrderBookLevel
                {
                    Price = orderBookItem.Price,
                    Volume = orderBookItem.Volume
                }).ToList(),
                                                    orderBook.Bids.Select(orderBookItem => new OrderBookLevel
                {
                    Price = orderBookItem.Price,
                    Volume = orderBookItem.Volume
                }).ToList());
            }
            catch (Exception exception)
            {
                _log.ErrorWithDetails(exception, "An unexpected error occurred while processing external order book",
                                      orderBook);
            }
        }
Exemple #3
0
        public async Task <IAssetPairRate> GetCurrentRateAsync(string baseAssetId, string quotingAssetId)
        {
            if (await _assetPairSettingsService.GetAsync(baseAssetId, quotingAssetId) != null)
            {
                IReadOnlyList <IAssetPairRate> allRates =
                    await _assetPairRateRepository.GetAsync(baseAssetId, quotingAssetId);

                return(allRates
                       .Where(x => x.CreatedOn <= DateTime.UtcNow)
                       .OrderByDescending(x => x.CreatedOn)
                       .FirstOrDefault());
            }

            AssetPair assetPair = await _assetsLocalCache.GetAssetPairAsync(baseAssetId, quotingAssetId);

            if (assetPair == null)
            {
                throw new AssetPairUnknownException(baseAssetId, quotingAssetId);
            }

            AssetPairModel assetPairRate = await InvokeMarketProfileServiceAsync(assetPair.Id);

            return(Mapper.Map <AssetPairRate>(assetPairRate, opt =>
            {
                opt.Items["BaseAssetId"] = baseAssetId;
                opt.Items["QuotingAssetId"] = quotingAssetId;
            }));
        }
Exemple #4
0
        private string GetAssetPairId(string assetPair)
        {
            AssetPairModel assetPairModel = _marketInstrumentService.GetAssetPair(assetPair, Name);

            if (assetPairModel == null)
            {
                throw new FailedOperationException("Asset pair does not exist");
            }

            return(assetPairModel.Id);
        }
Exemple #5
0
        public static AssetPairNoSql Create(IAssetPair source)
        {
            var item = new AssetPairNoSql()
            {
                PartitionKey = GeneratePartitionKey(),
                RowKey       = GenerateRowKey(source.Id),
                AssetPair    = AssetPairModel.Create(source)
            };

            return(item);
        }
        public async Task UpdateAsync(string exchange, string assetPair, DateTime timestamp,
                                      IReadOnlyList <OrderBookLevel> sellLevels, IReadOnlyList <OrderBookLevel> buyLevels)
        {
            ExchangeSettings exchangeSettings = await _exchangeSettingsService.GetByNameAsync(exchange);

            if (exchangeSettings == null)
            {
                throw new FailedOperationException("Exchange setting not found");
            }

            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchange);

            if (assetPairSettings == null)
            {
                throw new FailedOperationException("Asset pair not found");
            }

            var externalOrderBook = new ExternalOrderBook
            {
                Exchange   = exchange,
                AssetPair  = assetPair,
                Timestamp  = timestamp,
                SellLevels = sellLevels.Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 + exchangeSettings.MarketFee + exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                             .OrderBy(o => o.Price)
                             .ToList(),
                BuyLevels = buyLevels.Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 - exchangeSettings.MarketFee - exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                            .OrderByDescending(o => o.Price)
                            .ToList()
            };

            lock (_sync)
            {
                if (!_orderBooks.ContainsKey(exchange))
                {
                    _orderBooks[exchange] = new Dictionary <string, ExternalOrderBook>();
                }

                _orderBooks[exchange][assetPair] = externalOrderBook;
            }
        }
Exemple #7
0
        private string GetAssetPairName(string assetPairId)
        {
            AssetPairModel assetPairModel = _marketInstrumentService.GetAssetPairs()
                                            .SingleOrDefault(o => o.Exchange == Name && o.Id == assetPairId);

            if (assetPairModel == null)
            {
                throw new FailedOperationException("Asset pair does not exist");
            }

            return(assetPairModel.Name);
        }
Exemple #8
0
            public static AssetPairModel Create(IAssetPair source)
            {
                var item = new AssetPairModel()
                {
                    AskPrice          = source.AskPrice,
                    AskPriceTimestamp = source.AskPriceTimestamp,
                    BidPrice          = source.BidPrice,
                    BidPriceTimestamp = source.BidPriceTimestamp,
                    AssetPair         = source.AssetPair
                };

                return(item);
            }
        private Task ProcessMessageAsync(TickPrice tickPrice)
        {
            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPairs()
                                               .FirstOrDefault(o => o.Exchange == tickPrice.Source && o.Id == tickPrice.Asset);

            if (assetPairSettings != null)
            {
                _quoteService.Update(new Quote(assetPairSettings.Name, tickPrice.Timestamp, tickPrice.Ask,
                                               tickPrice.Bid, tickPrice.Source));
            }

            return(Task.CompletedTask);
        }
Exemple #10
0
        public void CalculatePrice_CheckCorrectCalculation()
        {
            var assetPairRate = new AssetPairModel
            {
                AssetPair = "BTCCHF",
                BidPrice  = 6838.57154,
                AskPrice  = 6838.57154
            };

            IMarkup merchantMarkup = new Markup
            {
                Percent     = 0,
                Pips        = 0,
                DeltaSpread = 1.4m,
                FixedFee    = 0
            };

            IRequestMarkup requestMarkup = new RequestMarkup
            {
                Pips     = 9,
                Percent  = 9,
                FixedFee = 13
            };

            var assetPair = new AssetPair
            {
                Id               = "BTCCHF",
                Name             = "BTC/CHF",
                Accuracy         = 3,
                BaseAssetId      = "BTC",
                QuotingAssetId   = "CHF",
                InvertedAccuracy = 8,
                IsDisabled       = false,
                Source           = "BTCUSD",
                Source2          = "USDCHF"
            };

            decimal chfAmount = 10;

            _logMock.Setup(o => o.WriteInfoAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <DateTime>())).Verifiable();

            var rate = _service.CalculatePrice(assetPairRate.AskPrice, assetPairRate.BidPrice, assetPair.Accuracy,
                                               BtcAccuracy, requestMarkup.Percent, requestMarkup.Pips, PriceCalculationMethod.ByBid, merchantMarkup);

            var btcAmount = (chfAmount + (decimal)requestMarkup.FixedFee + merchantMarkup.FixedFee) / rate;

            Assert.IsTrue(Math.Abs(btcAmount - (decimal)0.00374839) < BtcAccuracy.GetMinValue());
        }
Exemple #11
0
        public async Task AddAsync([FromBody] AssetPairModel model, string userId)
        {
            try
            {
                var assetPair = Mapper.Map <AssetPair>(model);

                await _instrumentService.AddAssetPairAsync(assetPair, userId);
            }
            catch (EntityAlreadyExistsException)
            {
                throw new ValidationApiException(HttpStatusCode.Conflict, "The asset pair already exists");
            }
            catch (FailedOperationException exception)
            {
                throw new ValidationApiException(HttpStatusCode.BadRequest, exception.Message);
            }
        }
Exemple #12
0
        public async Task UpdateAsync([FromBody] AssetPairModel model, string userId)
        {
            try
            {
                var assetPair = Mapper.Map <AssetPair>(model);

                await _instrumentService.UpdateAssetPairAsync(assetPair, userId);
            }
            catch (EntityNotFoundException)
            {
                throw new ValidationApiException(HttpStatusCode.NotFound, "The asset pair does not exist");
            }
            catch (FailedOperationException exception)
            {
                throw new ValidationApiException(HttpStatusCode.BadRequest, exception.Message);
            }
        }
Exemple #13
0
            public static AssetPairModel Create(IAssetPair source)
            {
                var item = new AssetPairModel()
                {
                    Id                = source.Id,
                    Name              = source.Name,
                    Accuracy          = source.Accuracy,
                    IsDisabled        = source.IsDisabled,
                    BaseAssetId       = source.BaseAssetId,
                    ExchangeId        = source.ExchangeId,
                    InvertedAccuracy  = source.InvertedAccuracy,
                    MinInvertedVolume = source.MinInvertedVolume,
                    MinVolume         = source.MinVolume,
                    QuotingAssetId    = source.QuotingAssetId,
                    Source            = source.Source,
                    Source2           = source.Source2
                };

                return(item);
            }
        private async Task <bool> CanCompleteMarketOrderAsync(MarketOrder marketOrder)
        {
            IReadOnlyList <ExternalLimitOrder> externalLimitOrders =
                await _externalLimitOrderService.GetByParentIdAsync(marketOrder.Id);

            if (externalLimitOrders.Any(o => o.Status == ExternalLimitOrderStatus.Active))
            {
                return(false);
            }

            decimal executedVolume = externalLimitOrders.Sum(o => o.ExecutedVolume ?? 0);

            string exchange = _settingsService.GetExchangeName();

            AssetPairModel assetPairModel = _marketInstrumentService.GetAssetPair(marketOrder.AssetPair, exchange);

            decimal remainingVolume = Math.Round(marketOrder.Volume - executedVolume, assetPairModel.VolumeAccuracy);

            return(remainingVolume == 0 || remainingVolume < assetPairModel.MinVolume);
        }
        public async Task CreateAsync(string clientId, string assetPair, decimal volume, OrderType orderType)
        {
            string exchangeName = _settingsService.GetExchangeName();

            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchangeName);

            if (assetPairSettings == null)
            {
                throw new FailedOperationException("Asset pair not supported");
            }

            if (volume < assetPairSettings.MinVolume)
            {
                throw new FailedOperationException("Volume is too small");
            }

            var marketOrder = new MarketOrder(clientId, assetPair, orderType, volume);

            await _marketOrderRepository.InsertAsync(marketOrder);

            _log.InfoWithDetails("Market order created", marketOrder);
        }
        public Task UpdateAsync(string assetPair)
        {
            string exchangeName = _settingsService.GetExchangeName();

            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchangeName);

            if (assetPairSettings == null)
            {
                throw new FailedOperationException("Asset pair not found");
            }

            IReadOnlyList <ExternalOrderBook> externalOrderBooks = _externalOrderBookService.GetByAssetPair(assetPair);

            var sellLevels = new Dictionary <decimal, decimal>();

            decimal ask = decimal.MaxValue;

            foreach (ExternalOrderBook externalOrderBook in externalOrderBooks)
            {
                foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.SellLevels)
                {
                    decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true);

                    if (!sellLevels.ContainsKey(price))
                    {
                        sellLevels[price] = 0;
                    }

                    sellLevels[price] += priceLevel.Volume;

                    if (ask > price)
                    {
                        ask = price;
                    }
                }
            }

            var buyLevels = new Dictionary <decimal, decimal>();

            decimal bid = decimal.MinValue;

            foreach (ExternalOrderBook externalOrderBook in externalOrderBooks)
            {
                foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.BuyLevels)
                {
                    decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy);

                    if (!buyLevels.ContainsKey(price))
                    {
                        buyLevels[price] = 0;
                    }

                    buyLevels[price] += priceLevel.Volume;

                    if (bid < price)
                    {
                        bid = price;
                    }
                }
            }

            if (ask <= bid)
            {
                decimal mid = Math.Round((ask + bid) / 2, assetPairSettings.PriceAccuracy);

                var basisPoint = (decimal)Math.Pow(.1, assetPairSettings.PriceAccuracy);

                ask = mid + basisPoint;

                bid = mid - basisPoint;
            }

            var sellOrderBookLevels = new List <OrderBookLevel>();

            var bestSellLevel = new OrderBookLevel {
                Price = ask
            };

            foreach (var(price, volume) in sellLevels)
            {
                if (price <= ask)
                {
                    bestSellLevel.Volume += volume;
                }
                else
                {
                    sellOrderBookLevels.Add(new OrderBookLevel {
                        Price = price, Volume = volume
                    });
                }
            }

            if (bestSellLevel.Volume > 0)
            {
                sellOrderBookLevels.Add(bestSellLevel);
            }

            var buyOrderBookLevels = new List <OrderBookLevel>();

            var bestBuyLevel = new OrderBookLevel {
                Price = bid
            };

            foreach (var(price, volume) in buyLevels)
            {
                if (price >= bid)
                {
                    bestBuyLevel.Volume += volume;
                }
                else
                {
                    buyOrderBookLevels.Add(new OrderBookLevel {
                        Price = price, Volume = volume
                    });
                }
            }

            if (bestBuyLevel.Volume > 0)
            {
                buyOrderBookLevels.Add(bestBuyLevel);
            }

            var internalOrderBook = new InternalOrderBook
            {
                AssetPair  = assetPairSettings.Name,
                Timestamp  = DateTime.UtcNow,
                SellLevels = sellOrderBookLevels
                             .OrderBy(o => o.Price)
                             .ToList(),
                BuyLevels = buyOrderBookLevels
                            .OrderByDescending(o => o.Price)
                            .ToList()
            };

            _orderBooks.AddOrUpdate(assetPair, internalOrderBook, (key, value) => internalOrderBook);

            return(Task.CompletedTask);
        }
Exemple #17
0
        public async Task <decimal> GetRateAsync(
            string baseAssetId,
            string quotingAssetId,
            double markupPercent,
            int markupPips,
            IMarkup merchantMarkup)
        {
            double askPrice, bidPrice;

            AssetPair priceAssetPair = null, assetPair = null;

            if (!string.IsNullOrEmpty(merchantMarkup.PriceAssetPairId))
            {
                await _log.WriteInfoAsync(nameof(CalculationService), nameof(GetRateAsync),
                                          new { merchantMarkup.PriceAssetPairId }.ToJson(), "Price asset pair will be used");

                priceAssetPair = await _assetsLocalCache.GetAssetPairByIdAsync(merchantMarkup.PriceAssetPairId);

                AssetPairModel assetPairRate = await InvokeMarketProfileServiceAsync(priceAssetPair.Id);

                await _log.WriteInfoAsync(nameof(CalculationService), nameof(GetRateAsync),
                                          new { PriceMethod = merchantMarkup.PriceMethod.ToString() }.ToJson(), "Price method");

                switch (merchantMarkup.PriceMethod)
                {
                case PriceMethod.None:
                case PriceMethod.Direct:
                    askPrice = assetPairRate.AskPrice;
                    bidPrice = assetPairRate.BidPrice;
                    break;

                case PriceMethod.Reverse:
                    askPrice = Math.Abs(assetPairRate.AskPrice) > 0
                            ? 1 / assetPairRate.AskPrice
                            : throw new MarketPriceZeroException("ask");
                    bidPrice = Math.Abs(assetPairRate.BidPrice) > 0
                            ? 1 / assetPairRate.BidPrice
                            : throw new MarketPriceZeroException("bid");
                    break;

                default:
                    throw new UnexpectedAssetPairPriceMethodException(merchantMarkup.PriceMethod);
                }
            }
            else
            {
                assetPair = await _assetsLocalCache.GetAssetPairAsync(baseAssetId, quotingAssetId);

                if (assetPair != null)
                {
                    await _log.WriteInfoAsync(nameof(CalculationService), nameof(GetRateAsync),
                                              new { AssetPairId = assetPair.Id }.ToJson(), "Asset pair will be used");

                    AssetPairModel assetPairRate = await InvokeMarketProfileServiceAsync(assetPair.Id);

                    askPrice = assetPairRate.AskPrice;

                    bidPrice = assetPairRate.BidPrice;
                }
                else
                {
                    askPrice = bidPrice = 1D;
                }
            }

            await _log.WriteInfoAsync(nameof(CalculationService), nameof(GetRateAsync),
                                      new { askPrice, bidPrice }.ToJson(), "Market rate that will be used for calculation");

            Asset baseAsset = await _assetsLocalCache.GetAssetByIdAsync(baseAssetId);

            int pairAccuracy = priceAssetPair?.Accuracy ?? assetPair?.Accuracy ?? baseAsset.Accuracy;

            return(CalculatePrice(askPrice, bidPrice, pairAccuracy, baseAsset.Accuracy, markupPercent, markupPips,
                                  PriceCalculationMethod.ByBid, merchantMarkup));
        }
 private static string GetKey(AssetPairModel assetPair)
 => GetAssetPairKey(assetPair.Name, assetPair.Exchange);
Exemple #19
0
        public Task UpdateAsync(string assetPair)
        {
            string exchangeName = _settingsService.GetExchangeName();

            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchangeName);

            if (assetPairSettings == null)
            {
                throw new FailedOperationException("Asset pair not found");
            }

            IReadOnlyList <ExternalOrderBook> externalOrderBooks = _externalOrderBookService.GetByAssetPair(assetPair);

            var sellLevels = new Dictionary <decimal, Dictionary <string, ExternalOrderBookLevel> >();

            foreach (ExternalOrderBook externalOrderBook in externalOrderBooks)
            {
                foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.SellLevels)
                {
                    decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true);

                    if (!sellLevels.ContainsKey(price))
                    {
                        sellLevels[price] = new Dictionary <string, ExternalOrderBookLevel>();
                    }

                    sellLevels[price][externalOrderBook.Exchange] = priceLevel;
                }
            }

            var buyLevels = new Dictionary <decimal, Dictionary <string, ExternalOrderBookLevel> >();

            foreach (ExternalOrderBook externalOrderBook in externalOrderBooks)
            {
                foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.BuyLevels)
                {
                    decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy);

                    if (!buyLevels.ContainsKey(price))
                    {
                        buyLevels[price] = new Dictionary <string, ExternalOrderBookLevel>();
                    }

                    buyLevels[price][externalOrderBook.Exchange] = priceLevel;
                }
            }

            var aggregatedOrderBook = new AggregatedOrderBook
            {
                AssetPair  = assetPair,
                Timestamp  = DateTime.UtcNow,
                SellLevels = sellLevels
                             .Select(priceLevel => new AggregatedOrderBookLevel
                {
                    Price           = priceLevel.Key,
                    Volume          = priceLevel.Value.Sum(exchangeVolume => exchangeVolume.Value.Volume),
                    ExchangeVolumes = priceLevel.Value
                                      .Select(o => new AggregatedOrderBookVolume(o.Key, o.Value.OriginalPrice, o.Value.Volume))
                                      .ToList()
                })
                             .OrderBy(o => o.Price)
                             .ToList(),
                BuyLevels = buyLevels
                            .Select(priceLevel => new AggregatedOrderBookLevel
                {
                    Price           = priceLevel.Key,
                    Volume          = priceLevel.Value.Sum(exchangeVolume => exchangeVolume.Value.Volume),
                    ExchangeVolumes = priceLevel.Value
                                      .Select(o => new AggregatedOrderBookVolume(o.Key, o.Value.OriginalPrice, o.Value.Volume))
                                      .ToList()
                })
                            .OrderByDescending(o => o.Price)
                            .ToList()
            };

            _orderBooks.AddOrUpdate(assetPair, aggregatedOrderBook, (key, value) => aggregatedOrderBook);

            return(Task.CompletedTask);
        }
        public async Task Create_Order_Book()
        {
            // arrange

            string assetPair = "BTCUSD";

            var exchangeSettings = new ExchangeSettings
            {
                Name           = "exchange1",
                MarketFee      = .1m,
                TransactionFee = .01m
            };

            _exchangeSettings.Add(exchangeSettings);

            var assetPairSettings = new AssetPairModel
            {
                Exchange      = exchangeSettings.Name,
                Name          = assetPair,
                PriceAccuracy = 3
            };

            _assetPairs.Add(assetPairSettings);


            var sellLevels = new List <OrderBookLevel>
            {
                new OrderBookLevel {
                    Price = 1.732235m, Volume = 3
                },
                new OrderBookLevel {
                    Price = 0.833000m, Volume = 1
                }
            };

            var buyLevels = new List <OrderBookLevel>
            {
                new OrderBookLevel {
                    Price = 0.712397m, Volume = 1
                },
                new OrderBookLevel {
                    Price = 0.111000m, Volume = 5
                }
            };

            var expectedExternalOrderBook = new ExternalOrderBook
            {
                Exchange   = exchangeSettings.Name,
                Timestamp  = DateTime.UtcNow,
                AssetPair  = assetPair,
                SellLevels = sellLevels
                             .Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 + exchangeSettings.MarketFee + exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                             .ToList(),
                BuyLevels = buyLevels
                            .Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 - exchangeSettings.MarketFee - exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                            .ToList()
            };

            // act

            await _service.UpdateAsync(exchangeSettings.Name, assetPair, DateTime.UtcNow, sellLevels, buyLevels);

            ExternalOrderBook actualExternalOrderBook = _service.GetByAssetPair(assetPair).Single();

            // assert

            Assert.IsTrue(AreEqual(expectedExternalOrderBook, actualExternalOrderBook));
        }