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