mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-10-01 01:35:48 -04:00
Add market depth info API call (#190)
This commit is contained in:
parent
e3b9a9962b
commit
5b038697c3
@ -19,6 +19,7 @@ package bisq.core.api;
|
|||||||
|
|
||||||
import bisq.core.api.model.AddressBalanceInfo;
|
import bisq.core.api.model.AddressBalanceInfo;
|
||||||
import bisq.core.api.model.BalancesInfo;
|
import bisq.core.api.model.BalancesInfo;
|
||||||
|
import bisq.core.api.model.MarketDepthInfo;
|
||||||
import bisq.core.api.model.MarketPriceInfo;
|
import bisq.core.api.model.MarketPriceInfo;
|
||||||
import bisq.core.api.model.TxFeeRateInfo;
|
import bisq.core.api.model.TxFeeRateInfo;
|
||||||
import bisq.core.app.AppStartupState;
|
import bisq.core.app.AppStartupState;
|
||||||
@ -31,7 +32,6 @@ import bisq.core.payment.payload.PaymentMethod;
|
|||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.statistics.TradeStatistics3;
|
import bisq.core.trade.statistics.TradeStatistics3;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.crypto.IncorrectPasswordException;
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
@ -461,6 +461,10 @@ public class CoreApi {
|
|||||||
return corePriceService.getMarketPrices();
|
return corePriceService.getMarketPrices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MarketDepthInfo getMarketDepth(String currencyCode) throws ExecutionException, InterruptedException, TimeoutException {
|
||||||
|
return corePriceService.getMarketDepth(currencyCode);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Trades
|
// Trades
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -17,29 +17,41 @@
|
|||||||
|
|
||||||
package bisq.core.api;
|
package bisq.core.api;
|
||||||
|
|
||||||
|
import bisq.core.api.model.MarketDepthInfo;
|
||||||
import bisq.core.api.model.MarketPriceInfo;
|
import bisq.core.api.model.MarketPriceInfo;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
|
import bisq.core.monetary.Price;
|
||||||
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OfferBookService;
|
||||||
|
import bisq.core.offer.OfferPayload.Direction;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import com.google.common.math.LongMath;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Slf4j
|
@Slf4j
|
||||||
class CorePriceService {
|
class CorePriceService {
|
||||||
|
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
|
private final OfferBookService offerBookService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CorePriceService(PriceFeedService priceFeedService) {
|
public CorePriceService(PriceFeedService priceFeedService, OfferBookService offerBookService) {
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
|
this.offerBookService = offerBookService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,6 +77,71 @@ class CorePriceService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Data for market depth chart
|
||||||
|
*/
|
||||||
|
public MarketDepthInfo getMarketDepth(String currencyCode) throws ExecutionException, InterruptedException, TimeoutException, IllegalArgumentException {
|
||||||
|
if (priceFeedService.requestAllPrices().get(currencyCode.toUpperCase()) == null) throw new IllegalArgumentException("Currency not found: " + currencyCode) ;
|
||||||
|
|
||||||
|
// Offer price can be null (if price feed unavailable), thus a null-tolerant comparator is used.
|
||||||
|
Comparator<Offer> offerPriceComparator = Comparator.comparing(Offer::getPrice, Comparator.nullsLast(Comparator.naturalOrder()));
|
||||||
|
|
||||||
|
// Trading btc-fiat is considered as buying/selling BTC, but trading btc-altcoin is
|
||||||
|
// considered as buying/selling Altcoin. Because of this, when viewing a btc-altcoin pair,
|
||||||
|
// the buy column is actually the sell column and vice versa. To maintain the expected
|
||||||
|
// ordering, we have to reverse the price comparator.
|
||||||
|
boolean isCrypto = CurrencyUtil.isCryptoCurrency(currencyCode);
|
||||||
|
if (isCrypto) offerPriceComparator = offerPriceComparator.reversed();
|
||||||
|
|
||||||
|
// Offer amounts are used for the secondary sort. They are sorted from high to low.
|
||||||
|
Comparator<Offer> offerAmountComparator = Comparator.comparing(Offer::getAmount).reversed();
|
||||||
|
|
||||||
|
var buyOfferSortComparator =
|
||||||
|
offerPriceComparator.reversed() // Buy offers, as opposed to sell offers, are primarily sorted from high price to low.
|
||||||
|
.thenComparing(offerAmountComparator);
|
||||||
|
var sellOfferSortComparator =
|
||||||
|
offerPriceComparator
|
||||||
|
.thenComparing(offerAmountComparator);
|
||||||
|
List<Offer> buyOffers = offerBookService.getOffersByCurrency(Direction.BUY.name(), currencyCode).stream().sorted(buyOfferSortComparator).collect(Collectors.toList());
|
||||||
|
List<Offer> sellOffers = offerBookService.getOffersByCurrency(Direction.SELL.name(), currencyCode).stream().sorted(sellOfferSortComparator).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Create buyer hashmap {key:price, value:count}, uses LinkedHashMap to maintain insertion order
|
||||||
|
double accumulatedAmount = 0;
|
||||||
|
LinkedHashMap<Double,Double> buyTM = new LinkedHashMap<Double,Double>();
|
||||||
|
for(Offer offer: buyOffers) {
|
||||||
|
Price price = offer.getPrice();
|
||||||
|
if (price != null) {
|
||||||
|
double amount = (double) offer.getAmount().value / LongMath.pow(10, offer.getAmount().smallestUnitExponent());
|
||||||
|
accumulatedAmount += amount;
|
||||||
|
double priceAsDouble = (double) price.getValue() / LongMath.pow(10, price.smallestUnitExponent());
|
||||||
|
buyTM.put(mapPriceFeedServicePrice(priceAsDouble, currencyCode), accumulatedAmount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create buyer hashmap {key:price, value:count}, uses TreeMap to sort by key (asc)
|
||||||
|
accumulatedAmount = 0;
|
||||||
|
LinkedHashMap<Double,Double> sellTM = new LinkedHashMap<Double,Double>();
|
||||||
|
for(Offer offer: sellOffers){
|
||||||
|
Price price = offer.getPrice();
|
||||||
|
if (price != null) {
|
||||||
|
double amount = (double) offer.getAmount().value / LongMath.pow(10, offer.getAmount().smallestUnitExponent());
|
||||||
|
accumulatedAmount += amount;
|
||||||
|
double priceAsDouble = (double) price.getValue() / LongMath.pow(10, price.smallestUnitExponent());
|
||||||
|
sellTM.put(mapPriceFeedServicePrice(priceAsDouble, currencyCode), accumulatedAmount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make array of buyPrices and buyDepth
|
||||||
|
Double[] buyDepth = buyTM.values().toArray(new Double[buyTM.size()]);
|
||||||
|
Double[] buyPrices = buyTM.keySet().toArray(new Double[buyTM.size()]);
|
||||||
|
|
||||||
|
// Make array of sellPrices and sellDepth
|
||||||
|
Double[] sellDepth = sellTM.values().toArray(new Double[sellTM.size()]);
|
||||||
|
Double[] sellPrices = sellTM.keySet().toArray(new Double[sellTM.size()]);
|
||||||
|
|
||||||
|
return new MarketDepthInfo(currencyCode, buyPrices, buyDepth, sellPrices, sellDepth);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PriceProvider returns different values for crypto and fiat,
|
* PriceProvider returns different values for crypto and fiat,
|
||||||
* e.g. 1 XMR = X USD
|
* e.g. 1 XMR = X USD
|
||||||
@ -80,3 +157,4 @@ class CorePriceService {
|
|||||||
// TODO PriceProvider.getAll() could provide these values directly when the original values are not needed for the 'desktop' UI anymore
|
// TODO PriceProvider.getAll() could provide these values directly when the original values are not needed for the 'desktop' UI anymore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
core/src/main/java/bisq/core/api/model/MarketDepthInfo.java
Normal file
49
core/src/main/java/bisq/core/api/model/MarketDepthInfo.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.api.model;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@ToString
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MarketDepthInfo {
|
||||||
|
public final String currencyCode;
|
||||||
|
public final Double[] buyPrices;
|
||||||
|
public final Double[] buyDepth;
|
||||||
|
public final Double[] sellPrices;
|
||||||
|
public final Double[] sellDepth;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROTO BUFFER
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
public bisq.proto.grpc.MarketDepthInfo toProtoMessage() {
|
||||||
|
return bisq.proto.grpc.MarketDepthInfo.newBuilder()
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.addAllBuyPrices(Arrays.asList(buyPrices))
|
||||||
|
.addAllBuyDepth(Arrays.asList((buyDepth)))
|
||||||
|
.addAllSellPrices(Arrays.asList(sellPrices))
|
||||||
|
.addAllSellDepth(Arrays.asList(sellDepth))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@ package bisq.core.monetary;
|
|||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.util.ParsingUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Monetary;
|
import org.bitcoinj.core.Monetary;
|
||||||
import org.bitcoinj.utils.ExchangeRate;
|
import org.bitcoinj.utils.ExchangeRate;
|
||||||
@ -103,10 +105,18 @@ public class Price extends MonetaryWrapper implements Comparable<Price> {
|
|||||||
return monetary instanceof Altcoin ? ((Altcoin) monetary).getCurrencyCode() : ((Fiat) monetary).getCurrencyCode();
|
return monetary instanceof Altcoin ? ((Altcoin) monetary).getCurrencyCode() : ((Fiat) monetary).getCurrencyCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long getValue() {
|
public long getValue() {
|
||||||
return monetary.getValue();
|
return monetary.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of whole coins or fiat units as double.
|
||||||
|
*/
|
||||||
|
public double getDoubleValue() {
|
||||||
|
return BigDecimal.valueOf(monetary.getValue()).movePointLeft(monetary.smallestUnitExponent()).doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull Price other) {
|
public int compareTo(@NotNull Price other) {
|
||||||
if (!this.getCurrencyCode().equals(other.getCurrencyCode()))
|
if (!this.getCurrencyCode().equals(other.getCurrencyCode()))
|
||||||
|
@ -202,6 +202,12 @@ public class OfferBookService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Offer> getOffersByCurrency(String direction, String currencyCode) {
|
||||||
|
return getOffers().stream()
|
||||||
|
.filter(o -> o.getOfferPayload().getBaseCurrencyCode().equalsIgnoreCase(currencyCode) && o.getDirection().name() == direction)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public void removeOfferAtShutDown(OfferPayload offerPayload) {
|
public void removeOfferAtShutDown(OfferPayload offerPayload) {
|
||||||
removeOffer(offerPayload, null, null);
|
removeOffer(offerPayload, null, null);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ class GrpcOffersService extends OffersImplBase {
|
|||||||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getCreateOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getCreateOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
}}
|
}}
|
||||||
)));
|
)));
|
||||||
|
@ -174,7 +174,7 @@ class GrpcPaymentAccountsService extends PaymentAccountsImplBase {
|
|||||||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||||
new HashMap<>() {{
|
new HashMap<>() {{
|
||||||
put(getCreatePaymentAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getCreatePaymentAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getCreateCryptoCurrencyPaymentAccountMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getCreateCryptoCurrencyPaymentAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getGetPaymentAccountsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetPaymentAccountsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getGetPaymentMethodsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetPaymentMethodsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getGetPaymentAccountFormMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetPaymentAccountFormMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
package bisq.daemon.grpc;
|
package bisq.daemon.grpc;
|
||||||
|
|
||||||
import bisq.core.api.CoreApi;
|
import bisq.core.api.CoreApi;
|
||||||
|
import bisq.core.api.model.MarketDepthInfo;
|
||||||
import bisq.core.api.model.MarketPriceInfo;
|
import bisq.core.api.model.MarketPriceInfo;
|
||||||
|
import bisq.proto.grpc.MarketDepthReply;
|
||||||
|
import bisq.proto.grpc.MarketDepthRequest;
|
||||||
import bisq.proto.grpc.MarketPriceReply;
|
import bisq.proto.grpc.MarketPriceReply;
|
||||||
import bisq.proto.grpc.MarketPriceRequest;
|
import bisq.proto.grpc.MarketPriceRequest;
|
||||||
import bisq.proto.grpc.MarketPricesReply;
|
import bisq.proto.grpc.MarketPricesReply;
|
||||||
@ -81,6 +83,17 @@ class GrpcPriceService extends PriceImplBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getMarketDepth(MarketDepthRequest req,
|
||||||
|
StreamObserver<MarketDepthReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
responseObserver.onNext(mapMarketDepthReply(coreApi.getMarketDepth(req.getCurrencyCode())));
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MarketPricesReply mapMarketPricesReply(List<MarketPriceInfo> marketPrices) {
|
private MarketPricesReply mapMarketPricesReply(List<MarketPriceInfo> marketPrices) {
|
||||||
MarketPricesReply.Builder builder = MarketPricesReply.newBuilder();
|
MarketPricesReply.Builder builder = MarketPricesReply.newBuilder();
|
||||||
marketPrices.stream()
|
marketPrices.stream()
|
||||||
@ -89,6 +102,10 @@ class GrpcPriceService extends PriceImplBase {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MarketDepthReply mapMarketDepthReply(MarketDepthInfo marketDepth) {
|
||||||
|
return MarketDepthReply.newBuilder().setMarketDepth(marketDepth.toProtoMessage()).build();
|
||||||
|
}
|
||||||
|
|
||||||
final ServerInterceptor[] interceptors() {
|
final ServerInterceptor[] interceptors() {
|
||||||
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||||
return rateMeteringInterceptor.map(serverInterceptor ->
|
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||||
|
@ -506,6 +506,8 @@ service Price {
|
|||||||
}
|
}
|
||||||
rpc GetMarketPrices (MarketPricesRequest) returns (MarketPricesReply) {
|
rpc GetMarketPrices (MarketPricesRequest) returns (MarketPricesReply) {
|
||||||
}
|
}
|
||||||
|
rpc GetMarketDepth (MarketDepthRequest) returns (MarketDepthReply) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message MarketPriceRequest {
|
message MarketPriceRequest {
|
||||||
@ -528,6 +530,22 @@ message MarketPriceInfo {
|
|||||||
double price = 2;
|
double price = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message MarketDepthRequest {
|
||||||
|
string currency_code = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MarketDepthReply {
|
||||||
|
MarketDepthInfo market_depth = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MarketDepthInfo {
|
||||||
|
string currency_code = 1;
|
||||||
|
repeated double buy_prices = 2;
|
||||||
|
repeated double buy_depth = 3;
|
||||||
|
repeated double sell_prices = 4;
|
||||||
|
repeated double sell_depth = 5;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// GetTradeStatistics
|
// GetTradeStatistics
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Loading…
Reference in New Issue
Block a user