mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 11:39:29 +00:00
Add market depth info API call (#190)
This commit is contained in:
parent
e3b9a9962b
commit
5b038697c3
9 changed files with 187 additions and 5 deletions
|
@ -19,6 +19,7 @@ package bisq.core.api;
|
|||
|
||||
import bisq.core.api.model.AddressBalanceInfo;
|
||||
import bisq.core.api.model.BalancesInfo;
|
||||
import bisq.core.api.model.MarketDepthInfo;
|
||||
import bisq.core.api.model.MarketPriceInfo;
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
import bisq.core.app.AppStartupState;
|
||||
|
@ -31,7 +32,6 @@ import bisq.core.payment.payload.PaymentMethod;
|
|||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.IncorrectPasswordException;
|
||||
|
@ -461,6 +461,10 @@ public class CoreApi {
|
|||
return corePriceService.getMarketPrices();
|
||||
}
|
||||
|
||||
public MarketDepthInfo getMarketDepth(String currencyCode) throws ExecutionException, InterruptedException, TimeoutException {
|
||||
return corePriceService.getMarketDepth(currencyCode);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Trades
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -17,29 +17,41 @@
|
|||
|
||||
package bisq.core.api;
|
||||
|
||||
import bisq.core.api.model.MarketDepthInfo;
|
||||
import bisq.core.api.model.MarketPriceInfo;
|
||||
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 javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.math.LongMath;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
class CorePriceService {
|
||||
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final OfferBookService offerBookService;
|
||||
|
||||
@Inject
|
||||
public CorePriceService(PriceFeedService priceFeedService) {
|
||||
public CorePriceService(PriceFeedService priceFeedService, OfferBookService offerBookService) {
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.offerBookService = offerBookService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,6 +77,71 @@ class CorePriceService {
|
|||
.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,
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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.util.ParsingUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Monetary;
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long 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
|
||||
public int compareTo(@NotNull Price other) {
|
||||
if (!this.getCurrencyCode().equals(other.getCurrencyCode()))
|
||||
|
|
|
@ -202,6 +202,12 @@ public class OfferBookService {
|
|||
.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) {
|
||||
removeOffer(offerPayload, null, null);
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ class GrpcOffersService extends OffersImplBase {
|
|||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||
put(getGetOffersMethod().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));
|
||||
}}
|
||||
)));
|
||||
|
|
|
@ -174,7 +174,7 @@ class GrpcPaymentAccountsService extends PaymentAccountsImplBase {
|
|||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||
new HashMap<>() {{
|
||||
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(getGetPaymentMethodsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||
put(getGetPaymentAccountFormMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
package bisq.daemon.grpc;
|
||||
|
||||
import bisq.core.api.CoreApi;
|
||||
import bisq.core.api.model.MarketDepthInfo;
|
||||
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.MarketPriceRequest;
|
||||
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) {
|
||||
MarketPricesReply.Builder builder = MarketPricesReply.newBuilder();
|
||||
marketPrices.stream()
|
||||
|
@ -89,6 +102,10 @@ class GrpcPriceService extends PriceImplBase {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
private MarketDepthReply mapMarketDepthReply(MarketDepthInfo marketDepth) {
|
||||
return MarketDepthReply.newBuilder().setMarketDepth(marketDepth.toProtoMessage()).build();
|
||||
}
|
||||
|
||||
final ServerInterceptor[] interceptors() {
|
||||
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||
|
|
|
@ -506,6 +506,8 @@ service Price {
|
|||
}
|
||||
rpc GetMarketPrices (MarketPricesRequest) returns (MarketPricesReply) {
|
||||
}
|
||||
rpc GetMarketDepth (MarketDepthRequest) returns (MarketDepthReply) {
|
||||
}
|
||||
}
|
||||
|
||||
message MarketPriceRequest {
|
||||
|
@ -528,6 +530,22 @@ message MarketPriceInfo {
|
|||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Reference in a new issue