Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 7d2f75b

Browse files
authored
Merge pull request #516 from agonyforge/liquidity-error-handling
Liquidity error handling
2 parents 8d0e0f7 + 924b4c0 commit 7d2f75b

3 files changed

Lines changed: 40 additions & 30 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.agonyforge.arbitrader.service;
2+
3+
/**
4+
* Thrown when an exchange has too little liquidity for us to place the order we want.
5+
*/
6+
public class LiquidityException extends RuntimeException {
7+
public LiquidityException(String message) {
8+
super(message);
9+
}
10+
}

src/main/java/com/agonyforge/arbitrader/service/TradingService.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ private void enterPosition(Spread spread) {
226226
try {
227227
longLimitPrice = getLimitPrice(spread.getLongExchange(), spread.getCurrencyPair(), tradeVolume.getLongVolume(), Order.OrderType.ASK);
228228
shortLimitPrice = getLimitPrice(spread.getShortExchange(), spread.getCurrencyPair(), tradeVolume.getShortVolume(), Order.OrderType.BID);
229-
} catch (ExchangeException e) {
230-
LOGGER.warn("Failed to fetch order books for {}/{} and currency {}/{} to compute entry prices: {}",
229+
} catch (IOException | ExchangeException e) {
230+
LOGGER.error("Failed to fetch order books for {}/{} and currency {}/{} to compute entry prices: {}",
231231
longExchangeName,
232232
spread.getShortExchange().getDefaultExchangeSpecification().getExchangeName(),
233233
spread.getCurrencyPair().base,
@@ -456,7 +456,7 @@ private void exitPosition(Spread spread) {
456456
try {
457457
longLimitPrice = getLimitPrice(spread.getLongExchange(), spread.getCurrencyPair(), tradeVolume.getLongVolume(), Order.OrderType.BID);
458458
shortLimitPrice = getLimitPrice(spread.getShortExchange(), spread.getCurrencyPair(), tradeVolume.getShortVolume(), Order.OrderType.ASK);
459-
} catch (ExchangeException e) {
459+
} catch (IOException | ExchangeException e) {
460460
LOGGER.warn("Failed to fetch order books (on active position) for {}/{} and currency {}/{} to compute entry prices: {}",
461461
longExchangeName,
462462
spread.getShortExchange().getDefaultExchangeSpecification().getExchangeName(),
@@ -941,35 +941,35 @@ BigDecimal getVolumeForOrder(Exchange exchange, CurrencyPair currencyPair, Strin
941941
* @param orderType Are we buying or selling? Use the bid or ask price?
942942
* @return The more accurate price for this order.
943943
*/
944-
BigDecimal getLimitPrice(Exchange exchange, CurrencyPair rawCurrencyPair, BigDecimal allowedVolume, Order.OrderType orderType) {
944+
BigDecimal getLimitPrice(Exchange exchange, CurrencyPair rawCurrencyPair, BigDecimal allowedVolume, Order.OrderType orderType) throws IOException {
945945
CurrencyPair currencyPair = exchangeService.convertExchangePair(exchange, rawCurrencyPair);
946+
OrderBook orderBook = exchange.getMarketDataService().getOrderBook(currencyPair);
947+
List<LimitOrder> orders = orderType.equals(Order.OrderType.ASK) ? orderBook.getAsks() : orderBook.getBids();
948+
BigDecimal price;
949+
BigDecimal volume = BigDecimal.ZERO;
946950

947-
try {
948-
OrderBook orderBook = exchange.getMarketDataService().getOrderBook(currencyPair);
949-
List<LimitOrder> orders = orderType.equals(Order.OrderType.ASK) ? orderBook.getAsks() : orderBook.getBids();
950-
BigDecimal price;
951-
BigDecimal volume = BigDecimal.ZERO;
952-
953-
// Walk through orders, ordered by price, until we satisfy all the volume we need.
954-
// Return the price of the last order we see.
955-
//
956-
// If we set our limit order at this price (without waiting too long) it is very likely to fill
957-
// because we know the exchange has enough currency available to fill it at this or a better price.
958-
for (LimitOrder order : orders) {
959-
price = order.getLimitPrice();
951+
// Walk through orders, ordered by price, until we satisfy all the volume we need.
952+
// Return the price of the last order we see.
953+
//
954+
// If we set our limit order at this price (without waiting too long) it is very likely to fill
955+
// because we know the exchange has enough currency available to fill it at this or a better price.
956+
for (LimitOrder order : orders) {
957+
price = order.getLimitPrice();
958+
959+
if (order.getRemainingAmount() == null || BigDecimal.ZERO.compareTo(order.getRemainingAmount()) == 0) {
960+
volume = volume.add(order.getOriginalAmount());
961+
} else{
960962
volume = volume.add(order.getRemainingAmount());
963+
}
961964

962-
if (volume.compareTo(allowedVolume) > 0) {
963-
int scale = computePriceScale(exchange, currencyPair);
965+
if (volume.compareTo(allowedVolume) > 0) {
966+
int scale = computePriceScale(exchange, currencyPair);
964967

965-
return price.setScale(scale, RoundingMode.HALF_EVEN);
966-
}
968+
return price.setScale(scale, RoundingMode.HALF_EVEN);
967969
}
968-
} catch (IOException e) {
969-
LOGGER.error("IOE fetching {} {} order volume", exchange.getExchangeSpecification().getExchangeName(), currencyPair, e);
970970
}
971971

972-
throw new RuntimeException("Not enough liquidity on exchange to fulfill required volume!");
972+
throw new LiquidityException("Not enough liquidity on exchange to fulfill required volume!");
973973
}
974974

975975
/**

src/test/java/com/agonyforge/arbitrader/service/TradingServiceTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public void testGetVolumeFallbackToDefaultIOException() throws IOException {
201201

202202
// the best price point has enough volume to fill my order
203203
@Test
204-
public void testLimitPriceLongSufficientVolume() {
204+
public void testLimitPriceLongSufficientVolume() throws IOException {
205205
when(exchangeService.convertExchangePair(any(Exchange.class), any(CurrencyPair.class)))
206206
.thenReturn(currencyPair);
207207

@@ -213,7 +213,7 @@ public void testLimitPriceLongSufficientVolume() {
213213

214214
// the best price point has enough volume to fill my order
215215
@Test
216-
public void testLimitPriceShortSufficientVolume() {
216+
public void testLimitPriceShortSufficientVolume() throws IOException {
217217
when(exchangeService.convertExchangePair(any(Exchange.class), any(CurrencyPair.class)))
218218
.thenReturn(currencyPair);
219219

@@ -225,7 +225,7 @@ public void testLimitPriceShortSufficientVolume() {
225225

226226
// the best price point isn't big enough to fill my order alone, so the price will slip
227227
@Test
228-
public void testLimitPriceLongInsufficientVolume() {
228+
public void testLimitPriceLongInsufficientVolume() throws IOException {
229229
when(exchangeService.convertExchangePair(any(Exchange.class), any(CurrencyPair.class)))
230230
.thenReturn(currencyPair);
231231
BigDecimal allowedVolume = new BigDecimal("11.00");
@@ -236,7 +236,7 @@ public void testLimitPriceLongInsufficientVolume() {
236236

237237
// the best price point isn't big enough to fill my order alone, so the price will slip
238238
@Test
239-
public void testLimitPriceShortInsufficientVolume() {
239+
public void testLimitPriceShortInsufficientVolume() throws IOException {
240240
when(exchangeService.convertExchangePair(any(Exchange.class), any(CurrencyPair.class)))
241241
.thenReturn(currencyPair);
242242
BigDecimal allowedVolume = new BigDecimal("11.00");
@@ -247,15 +247,15 @@ public void testLimitPriceShortInsufficientVolume() {
247247

248248
// the exchange doesn't have enough volume to fill my gigantic order
249249
@Test(expected = RuntimeException.class)
250-
public void testLimitPriceLongInsufficientLiquidity() {
250+
public void testLimitPriceLongInsufficientLiquidity() throws IOException {
251251
BigDecimal allowedVolume = new BigDecimal(10001);
252252

253253
tradingService.getLimitPrice(longExchange, currencyPair, allowedVolume, Order.OrderType.ASK);
254254
}
255255

256256
// the exchange doesn't have enough volume to fill my gigantic order
257257
@Test(expected = RuntimeException.class)
258-
public void testLimitPriceShortInsufficientLiquidity() {
258+
public void testLimitPriceShortInsufficientLiquidity() throws IOException {
259259
BigDecimal allowedVolume = new BigDecimal(10001);
260260

261261
tradingService.getLimitPrice(longExchange, currencyPair, allowedVolume, Order.OrderType.BID);

0 commit comments

Comments
 (0)