public async Task <IActionResult> GetCurrentRate(string baseAssetId, string quotingAssetId) { try { string lykkeBaseAssetId = await _lykkeAssetsResolver.GetLykkeId(Uri.UnescapeDataString(baseAssetId)); if (await _assetsLocalCache.GetAssetByIdAsync(lykkeBaseAssetId) == null) { return(NotFound(ErrorResponse.Create("Base asset not found"))); } string lykkeQuotingAssetId = await _lykkeAssetsResolver.GetLykkeId(Uri.UnescapeDataString(quotingAssetId)); if (await _assetsLocalCache.GetAssetByIdAsync(lykkeQuotingAssetId) == null) { return(NotFound(ErrorResponse.Create("Quoting asset not found"))); } IAssetPairRate rate = await _assetRatesService.GetCurrentRateAsync(lykkeBaseAssetId, lykkeQuotingAssetId); if (rate == null) { return(NotFound(ErrorResponse.Create("Rate not found"))); } return(Ok(Mapper.Map <AssetRateResponse>(rate, opt => { opt.Items["BaseAssetId"] = baseAssetId; opt.Items["QuotingAssetId"] = quotingAssetId; }))); } catch (AssetUnknownException e) { _log.ErrorWithDetails(e, new { e.Asset }); return(NotFound(ErrorResponse.Create($"Asset not found [{e.Asset}]"))); } catch (AssetPairUnknownException e) { _log.ErrorWithDetails(e, new { e.BaseAssetId, e.QuotingAssetId }); return(NotFound( ErrorResponse.Create($"Asset pair not found for [{e.BaseAssetId}, {e.QuotingAssetId}]"))); } catch (UnrecognizedApiResponse e) { _log.ErrorWithDetails(e, new { baseAssetId, quotingAssetId }); return(StatusCode((int)HttpStatusCode.BadGateway, ErrorResponse.Create(e.Message))); } }
public async Task <ExchangeResult> PreExchangeAsync(PreExchangeCommand cmd) { BlockchainType network = await _assetSettingsService.GetNetworkAsync(cmd.SourceAssetId); if (await _assetSettingsService.GetNetworkAsync(cmd.DestAssetId) != network) { throw new ExchangeOperationNotSupportedException("Assets are being served by different blockchains"); } string sourceWalletAddress = (await _merchantWalletService.GetDefaultAsync( cmd.MerchantId, cmd.SourceAssetId, PaymentDirection.Outgoing)).WalletAddress; await _walletBalanceValidator.ValidateTransfer(sourceWalletAddress, cmd.SourceAssetId, cmd.SourceAmount); string hotwallet = _bcnSettingsResolver.GetExchangeHotWallet(network); IAssetPairRate rate = await _assetRatesService.GetCurrentRateAsync(cmd.SourceAssetId, cmd.DestAssetId); decimal exchangeAmount = cmd.SourceAmount * rate.BidPrice; await _walletBalanceValidator.ValidateTransfer(hotwallet, cmd.DestAssetId, exchangeAmount); return(new ExchangeResult { SourceAssetId = cmd.SourceAssetId, SourceAmount = cmd.SourceAmount, DestAssetId = cmd.DestAssetId, DestAmount = exchangeAmount, Rate = rate.BidPrice }); }
public async Task <IAssetPairRate> AddAsync(IAssetPairRate src) { AssetPairRateEntity entity = AssetPairRateEntity.ByDate.Create(src); await _tableStorage.InsertAsync(entity); return(Mapper.Map <AssetPairRate>(entity)); }
public async Task <ExchangeResult> ExecuteAsync(ExchangeCommand cmd) { BlockchainType network = await _assetSettingsService.GetNetworkAsync(cmd.SourceAssetId); if (await _assetSettingsService.GetNetworkAsync(cmd.DestAssetId) != network) { throw new ExchangeOperationNotSupportedException("Assets are being served by different blockchains"); } IAssetPairRate rate = await _assetRatesService.GetCurrentRateAsync(cmd.SourceAssetId, cmd.DestAssetId); if (cmd.ExpectedRate != null && rate.BidPrice != cmd.ExpectedRate) { throw new ExchangeRateChangedException(rate.BidPrice); } string hotwallet = _bcnSettingsResolver.GetExchangeHotWallet(network); string sourceAddress = await GetSourceAddressAsync(cmd); decimal exchangeAmount = cmd.SourceAmount * rate.BidPrice; await _walletBalanceValidator.ValidateTransfer(sourceAddress, cmd.SourceAssetId, cmd.SourceAmount); await _walletBalanceValidator.ValidateTransfer(hotwallet, cmd.DestAssetId, exchangeAmount); TransferResult toHotWallet = await _retryPolicy .ExecuteAsync(() => _transferService.ExchangeThrowFail( cmd.SourceAssetId, sourceAddress, hotwallet, cmd.SourceAmount)); await RegisterTransferTxsAsync(toHotWallet); string destAddress = await GetDestAddressAsync(cmd); TransferResult fromHotWallet = await _retryPolicy .ExecuteAsync(() => _transferService.ExchangeThrowFail( cmd.DestAssetId, hotwallet, destAddress, exchangeAmount)); await RegisterTransferTxsAsync(fromHotWallet, false); return(new ExchangeResult { SourceAssetId = cmd.SourceAssetId, SourceAmount = cmd.SourceAmount, DestAssetId = cmd.DestAssetId, DestAmount = exchangeAmount, Rate = rate.BidPrice }); }
public async Task GetCurrentRateAsync_LocalRates_ReturnsLatest() { const decimal currentBid = 20; const decimal currentAsk = 40; const string btcAsset = "BTC"; const string lkkAsset = "LKK"; var rates = new List <IAssetPairRate> { new AssetPairRate { CreatedOn = DateTime.UtcNow.AddDays(-10), BaseAssetId = btcAsset, QuotingAssetId = lkkAsset, BidPrice = 10, AskPrice = 20 }, new AssetPairRate { CreatedOn = DateTime.UtcNow.AddDays(-5), BaseAssetId = btcAsset, QuotingAssetId = lkkAsset, BidPrice = currentBid, AskPrice = currentAsk } }; _assetPairSettingsServiceMock.Setup(o => o.GetAsync(It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync(() => new AssetPairSetting()); _assetPairRateRepositoryMock.Setup(o => o.GetAsync(It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync((string baseAssetId, string quotingAssetId) => rates .Where(x => x.BaseAssetId == baseAssetId && x.QuotingAssetId == quotingAssetId).ToList()); IAssetPairRate currentRate = await _assetRatesService.GetCurrentRateAsync(btcAsset, lkkAsset); IAssetPairRate failedCurrentRate = await _assetRatesService.GetCurrentRateAsync(lkkAsset, btcAsset); Assert.IsNotNull(currentRate); Assert.IsNull(failedCurrentRate); Assert.AreEqual(currentRate.BaseAssetId, btcAsset); Assert.AreEqual(currentRate.QuotingAssetId, lkkAsset); Assert.AreEqual(currentRate.BidPrice, currentBid); Assert.AreEqual(currentRate.AskPrice, currentAsk); }
public async Task <IActionResult> AddRate([FromBody] AddAssetRateModel request) { try { string lykkeBaseAssetId = await _lykkeAssetsResolver.GetLykkeId(request.BaseAssetId); if (await _assetsLocalCache.GetAssetByIdAsync(lykkeBaseAssetId) == null) { return(NotFound(ErrorResponse.Create("Base asset not found"))); } string lykkeQuotingAssetId = await _lykkeAssetsResolver.GetLykkeId(request.QuotingAssetId); if (await _assetsLocalCache.GetAssetByIdAsync(lykkeQuotingAssetId) == null) { return(NotFound(ErrorResponse.Create("Quoting asset not found"))); } IAssetPairRate newRate = await _assetRatesService.AddAsync(Mapper.Map <AddAssetPairRateCommand>(request, opt => { opt.Items["BaseAssetId"] = lykkeBaseAssetId; opt.Items["QuotingAssetId"] = lykkeQuotingAssetId; })); return(Ok(Mapper.Map <AssetRateResponse>(newRate, opt => { opt.Items["BaseAssetId"] = request.BaseAssetId; opt.Items["QuotingAssetId"] = request.QuotingAssetId; }))); } catch (AssetUnknownException e) { _log.ErrorWithDetails(e, new { e.Asset }); return(NotFound(ErrorResponse.Create($"Asset not found [{e.Asset}]"))); } catch (AssetPairRateStorageNotSupportedException e) { _log.ErrorWithDetails(e, new { e.BaseAssetId, e.QuotingAssetId }); return(StatusCode((int)HttpStatusCode.NotImplemented, ErrorResponse.Create(e.Message))); } }
public async Task <decimal> GetRateAsync( string baseAssetId, string quotingAssetId, decimal markupPercent, int markupPips, IMarkup merchantMarkup) { decimal askPrice, bidPrice; AssetPair priceAssetPair = null, assetPair = null; if (!string.IsNullOrEmpty(merchantMarkup.PriceAssetPairId)) { _log.Info($"Price asset pair will be used: {merchantMarkup.PriceAssetPairId}"); priceAssetPair = await _assetsLocalCache.GetAssetPairByIdAsync(merchantMarkup.PriceAssetPairId); IAssetPairRate assetPairRate = await _assetRatesService.GetCurrentRateAsync(priceAssetPair.BaseAssetId, priceAssetPair.QuotingAssetId); _log.Info($"Price method: {merchantMarkup.PriceMethod.ToString()}"); 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); try { IAssetPairRate assetPairRate = await _assetRatesService.GetCurrentRateAsync(baseAssetId, quotingAssetId); askPrice = assetPairRate.AskPrice; bidPrice = assetPairRate.BidPrice; } catch (Exception) { askPrice = bidPrice = 1; } } _log.Info($"Market rate that will be used for calculation, askPrice = {askPrice}, bidPrice = {bidPrice}"); 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)); }