Skip to content

Commit

Permalink
feat: Added latest market price API call (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
gazbert committed Nov 24, 2024
1 parent 9f4f1ba commit 363fc33
Show file tree
Hide file tree
Showing 4 changed files with 1,384 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.gazbert.bxbot.exchange.api.AuthenticationConfig;
Expand All @@ -48,7 +49,7 @@
*/
public class CoinbaseIT {

private static final String MARKET_ID = "BTC-GBP";
private static final String MARKET_ID = "BTC-USD";

private static final String PASSPHRASE = "lePassPhrase";
private static final String KEY = "key123";
Expand Down Expand Up @@ -96,6 +97,8 @@ public void testPublicApiCalls() throws Exception {
assertEquals(100, orderBook.getBuyOrders().size());
assertEquals(100, orderBook.getSellOrders().size());

assertNotNull(exchangeAdapter.getLatestMarketPrice(MARKET_ID));

verify(authenticationConfig, networkConfig, otherConfig, exchangeConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.gazbert.bxbot.trading.api.MarketOrderBook;
import com.gazbert.bxbot.trading.api.OpenOrder;
import com.gazbert.bxbot.trading.api.OrderType;
import com.gazbert.bxbot.trading.api.Ticker;
import com.gazbert.bxbot.trading.api.TradingApiException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand Down Expand Up @@ -64,8 +65,7 @@
* @since 1.0
*/
@Log4j2
public class CoinbaseExchangeAdapter extends AbstractExchangeAdapter
implements ExchangeAdapter {
public class CoinbaseExchangeAdapter extends AbstractExchangeAdapter implements ExchangeAdapter {

private static final String EXCHANGE_ADAPTER_NAME = "Coinbase Advanced Trade REST API v3";

Expand All @@ -76,6 +76,11 @@ public class CoinbaseExchangeAdapter extends AbstractExchangeAdapter
private static final String PRODUCT_BOOK_LIMIT_PARAM = "limit";
private static final int PRODUCT_BOOK_LIMIT_VALUE = 100;

private static final String MARKET_PRODUCTS = "/market/products/";
private static final String TICKER = "/ticker";
private static final String TICKER_LIMIT_PARAM = "limit";
private static final int TICKER_LIMIT_VALUE = 1;

private static final String UNEXPECTED_ERROR_MSG =
"Unexpected error has occurred in Coinbase Advanced Trade Exchange Adapter. ";
private static final String UNEXPECTED_IO_ERROR_MSG =
Expand Down Expand Up @@ -131,12 +136,11 @@ public MarketOrderBook getMarketOrders(String marketId)
log.info("Market Orders response: {}", response);

if (response.getStatusCode() == HttpURLConnection.HTTP_OK) {
final CoinbaseAdvancedTradeProductBookWrapper productBookWrapper =
gson.fromJson(response.getPayload(), CoinbaseAdvancedTradeProductBookWrapper.class);
final CoinbaseProductBookWrapper productBookWrapper =
gson.fromJson(response.getPayload(), CoinbaseProductBookWrapper.class);

final List<MarketOrder> buyOrders = new ArrayList<>();
for (CoinbaseAdvancedTradePriceBookOrder coinbaseProBuyOrder :
productBookWrapper.pricebook.bids) {
for (CoinbasePriceBookOrder coinbaseProBuyOrder : productBookWrapper.pricebook.bids) {
final MarketOrder buyOrder =
new MarketOrderImpl(
OrderType.BUY,
Expand All @@ -147,8 +151,7 @@ public MarketOrderBook getMarketOrders(String marketId)
}

final List<MarketOrder> sellOrders = new ArrayList<>();
for (CoinbaseAdvancedTradePriceBookOrder coinbaseProSellOrder :
productBookWrapper.pricebook.asks) {
for (CoinbasePriceBookOrder coinbaseProSellOrder : productBookWrapper.pricebook.asks) {
final MarketOrder sellOrder =
new MarketOrderImpl(
OrderType.SELL,
Expand Down Expand Up @@ -178,11 +181,42 @@ public MarketOrderBook getMarketOrders(String marketId)
@Override
public BigDecimal getLatestMarketPrice(String marketId)
throws ExchangeNetworkException, TradingApiException {
throw new UnsupportedOperationException("TODO: This method not developed yet!");

try {
final Map<String, String> params = createRequestParamMap();
params.put(TICKER_LIMIT_PARAM, String.valueOf(TICKER_LIMIT_VALUE));

final ExchangeHttpResponse response =
sendPublicRequestToExchange(MARKET_PRODUCTS + marketId + TICKER, params);

log.debug("Latest Market Price response: {}", response);

if (response.getStatusCode() == HttpURLConnection.HTTP_OK) {
final CoinbaseExchangeAdapter.CoinbaseTickerWrapper coinbaseTickerWrapper =
gson.fromJson(
response.getPayload(), CoinbaseExchangeAdapter.CoinbaseTickerWrapper.class);

// Grab the first and only trade entry in the ticker.
final CoinbaseTrade lastTrade = coinbaseTickerWrapper.trades.get(0);
return lastTrade.price;

} else {
final String errorMsg =
"Failed to get latest market price from exchange. Details: " + response;
log.error(errorMsg);
throw new TradingApiException(errorMsg);
}
} catch (ExchangeNetworkException | TradingApiException e) {
throw e;

} catch (Exception e) {
log.error(UNEXPECTED_ERROR_MSG, e);
throw new TradingApiException(UNEXPECTED_ERROR_MSG, e);
}
}

@Override
public BalanceInfo getBalanceInfo() throws ExchangeNetworkException, TradingApiException {
public Ticker getTicker(String marketId) throws ExchangeNetworkException, TradingApiException {
throw new UnsupportedOperationException("TODO: This method not developed yet!");
}

Expand All @@ -196,6 +230,11 @@ public List<OpenOrder> getYourOpenOrders(String marketId)
throw new UnsupportedOperationException("TODO: This method not developed yet!");
}

@Override
public BalanceInfo getBalanceInfo() throws ExchangeNetworkException, TradingApiException {
throw new UnsupportedOperationException("TODO: This method not developed yet!");
}

@Override
public String createOrder(
String marketId, OrderType orderType, BigDecimal quantity, BigDecimal price)
Expand Down Expand Up @@ -234,8 +273,8 @@ public BigDecimal getPercentageOfSellOrderTakenForExchangeFee(String marketId)
* Book API response.
*/
@ToString
private static class CoinbaseAdvancedTradeProductBookWrapper {
CoinbaseAdvancedTradePriceBook pricebook;
private static class CoinbaseProductBookWrapper {
CoinbasePriceBook pricebook;
BigDecimal last;

@SerializedName("mid_market")
Expand All @@ -248,22 +287,57 @@ private static class CoinbaseAdvancedTradeProductBookWrapper {
BigDecimal spreadAbsolute;
}

/** GSON class for Coinbase Advanced Trade Price Book. */
/** GSON class for a Coinbase Advanced Trade Price Book. */
@ToString
private static class CoinbaseAdvancedTradePriceBook {
private static class CoinbasePriceBook {
@SerializedName("product_id")
String productId;

List<CoinbaseAdvancedTradePriceBookOrder> bids;
List<CoinbaseAdvancedTradePriceBookOrder> asks;
List<CoinbasePriceBookOrder> bids;
List<CoinbasePriceBookOrder> asks;
String time;
}

/** GSON class for Coinbase Advanced Trade Price Book Order. */
/** GSON class for a Coinbase Advanced Trade Price Book Order. */
@ToString
private static class CoinbasePriceBookOrder {
BigDecimal price;
BigDecimal size;
}

/**
* GSON class for Coinbase Advanced Trade '/market/products/{product_id}/ticker' API call
* response.
*/
@ToString
private static class CoinbaseAdvancedTradePriceBookOrder {
private static class CoinbaseTickerWrapper {

List<CoinbaseTrade> trades;

@SerializedName("best_bid")
BigDecimal bestBid;

@SerializedName("best_ask")
BigDecimal bestAsk;
}

/** GSON class for a Coinbase Advanced Trade "Trade". */
@ToString
private static class CoinbaseTrade {

@SerializedName("trade_id")
Long tradeId;

@SerializedName("product_id")
String productId;

BigDecimal price;
BigDecimal size;
String time; // e.g. "2024-11-24T16:46:09.736169Z"
String side; // "BUY" | "SELL"
String bid; // always blank i.e. ""
String ask; // always blank i.e. ""
String exchange; // coinbase
}

// --------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 363fc33

Please sign in to comment.