Пример #1
0
        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
            });
        }
Пример #5
0
        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);
        }
Пример #6
0
        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));
        }