diff --git a/bxbot-exchange-api/src/main/java/com/gazbert/bxbot/exchange/api/PairPrecisionConfig.java b/bxbot-exchange-api/src/main/java/com/gazbert/bxbot/exchange/api/PairPrecisionConfig.java index 6e5b8187f..9189f740d 100644 --- a/bxbot-exchange-api/src/main/java/com/gazbert/bxbot/exchange/api/PairPrecisionConfig.java +++ b/bxbot-exchange-api/src/main/java/com/gazbert/bxbot/exchange/api/PairPrecisionConfig.java @@ -23,11 +23,13 @@ package com.gazbert.bxbot.exchange.api; +import java.math.BigDecimal; + /** - *

Some Exchange Adapters will need custom precision configs when placing orders.

+ * Some Exchange Adapters will need custom precision configs when placing orders. * - *

This interface allows us to have a uniform way to fetch this precision for the - * various Exchange houses.

+ *

This interface allows us to have a uniform way to fetch this precision for the various + * Exchange houses. * * @author maiph * @since 1.2 @@ -35,7 +37,7 @@ public interface PairPrecisionConfig { /** - * Gets the number of decimal places for price precision. The default value id no pair is found + * Gets the number of decimal places for price precision. The default value if no pair is found * will be -1. * * @param pair the coin pair. @@ -44,7 +46,7 @@ public interface PairPrecisionConfig { int getPricePrecision(String pair); /** - * Gets the number of decimal places for volume precision. The default value id no pair is found + * Gets the number of decimal places for volume precision. The default value if no pair is found * will be -1. * * @param pair the coin pair. @@ -52,4 +54,12 @@ public interface PairPrecisionConfig { */ int getVolumePrecision(String pair); + /** + * Gets the minimal amount of order volume for this pair. The default value if no pair is found + * will be null. + * + * @param pair the coin pair. + * @return the minimum amount of order volume. + */ + BigDecimal getMinimalOrderVolume(String pair); } diff --git a/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/KrakenExchangeAdapter.java b/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/KrakenExchangeAdapter.java index f8b47b349..51d70d9da 100644 --- a/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/KrakenExchangeAdapter.java +++ b/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/KrakenExchangeAdapter.java @@ -533,6 +533,11 @@ public BigDecimal getPercentageOfSellOrderTakenForExchangeFee(String marketId) { return sellFeePercentage; } + @Override + public BigDecimal getMinimumOrderVolume(String marketId) { + return pairPrecisionConfig.getMinimalOrderVolume(marketId); + } + @Override public String getImplName() { return "Kraken API v1"; @@ -662,6 +667,7 @@ PairPrecisionConfig loadPrecisionConfig() { Gson gson = new Gson(); Map prices = new HashMap<>(); Map volumes = new HashMap<>(); + Map orderMins = new HashMap<>(); for (Entry entry : this.entrySet()) { JsonElement jsonElement = gson.toJsonTree(entry); @@ -670,12 +676,17 @@ PairPrecisionConfig loadPrecisionConfig() { String name = jsonObject.get("altname").getAsString(); int price = jsonObject.get("pair_decimals").getAsInt(); int volume = jsonObject.get("lot_decimals").getAsInt(); + if (jsonObject.has("ordermin")) { + BigDecimal orderMin = jsonObject.get("ordermin").getAsBigDecimal(); + orderMins.put(name, orderMin); + } + prices.put(name, price); volumes.put(name, volume); } - return new PairPrecisionConfigImpl(prices, volumes); + return new PairPrecisionConfigImpl(prices, volumes, orderMins); } } diff --git a/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/config/PairPrecisionConfigImpl.java b/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/config/PairPrecisionConfigImpl.java index 633a6d501..25e6e35cb 100644 --- a/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/config/PairPrecisionConfigImpl.java +++ b/bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/config/PairPrecisionConfigImpl.java @@ -24,6 +24,7 @@ package com.gazbert.bxbot.exchanges.config; import com.gazbert.bxbot.exchange.api.PairPrecisionConfig; +import java.math.BigDecimal; import java.util.Map; /** @@ -35,10 +36,18 @@ public class PairPrecisionConfigImpl implements PairPrecisionConfig { private final Map prices; private final Map volumes; + private final Map orderMins; - public PairPrecisionConfigImpl(Map prices, Map volumes) { + /** + * Default implementation of {@link PairPrecisionConfig} backed by {@link Map}s. + */ + public PairPrecisionConfigImpl( + Map prices, + Map volumes, + Map orderMins) { this.prices = prices; this.volumes = volumes; + this.orderMins = orderMins; } @Override @@ -50,4 +59,9 @@ public int getPricePrecision(String pair) { public int getVolumePrecision(String pair) { return volumes.getOrDefault(pair, -1); } + + @Override + public BigDecimal getMinimalOrderVolume(String pair) { + return orderMins.getOrDefault(pair, null); + } } diff --git a/bxbot-exchanges/src/test/java/com/gazbert/bxbot/exchanges/TestKrakenExchangeAdapter.java b/bxbot-exchanges/src/test/java/com/gazbert/bxbot/exchanges/TestKrakenExchangeAdapter.java index 0f13141ca..e56cd76d4 100644 --- a/bxbot-exchanges/src/test/java/com/gazbert/bxbot/exchanges/TestKrakenExchangeAdapter.java +++ b/bxbot-exchanges/src/test/java/com/gazbert/bxbot/exchanges/TestKrakenExchangeAdapter.java @@ -1105,6 +1105,32 @@ public void testGettingExchangeBuyingFeeIsAsExpected() throws Exception { PowerMock.verifyAll(); } + @Test + public void testGettingMinOrderVolumeIfAvailable() throws Exception { + PowerMock.replayAll(); + + final ExchangeAdapter exchangeAdapter = new KrakenExchangeAdapter(); + exchangeAdapter.init(exchangeConfig); + + final BigDecimal minimumOrderVolume = exchangeAdapter.getMinimumOrderVolume("XBTUSD"); + assertEquals(0, minimumOrderVolume.compareTo(new BigDecimal("0.0001"))); + + PowerMock.verifyAll(); + } + + @Test + public void testGettingMinOrderVolumeIfNotAvailable() throws Exception { + PowerMock.replayAll(); + + final ExchangeAdapter exchangeAdapter = new KrakenExchangeAdapter(); + exchangeAdapter.init(exchangeConfig); + + final BigDecimal minimumOrderVolume = exchangeAdapter.getMinimumOrderVolume("XBTGBP.d"); + assertNull(minimumOrderVolume); + + PowerMock.verifyAll(); + } + // -------------------------------------------------------------------------- // Initialisation tests // -------------------------------------------------------------------------- diff --git a/bxbot-trading-api/src/main/java/com/gazbert/bxbot/trading/api/TradingApi.java b/bxbot-trading-api/src/main/java/com/gazbert/bxbot/trading/api/TradingApi.java index f08791b1b..2b4b7e2e9 100644 --- a/bxbot-trading-api/src/main/java/com/gazbert/bxbot/trading/api/TradingApi.java +++ b/bxbot-trading-api/src/main/java/com/gazbert/bxbot/trading/api/TradingApi.java @@ -51,7 +51,7 @@ public interface TradingApi { * @since 1.0 */ default String getVersion() { - return "1.1"; + return "1.2"; } /** @@ -215,6 +215,36 @@ BigDecimal getPercentageOfBuyOrderTakenForExchangeFee(String marketId) BigDecimal getPercentageOfSellOrderTakenForExchangeFee(String marketId) throws TradingApiException, ExchangeNetworkException; + /** + * Returns the minimum order volume for a given market id. The returned value is the order volume + * that a SELL or BUY order must at least have as amount of the base currency for the given + * currency pair. + * + *

Not all exchanges provide this information or not fully provide it (only for some + * currency pairs)- you'll need to check the relevant Exchange Adapter code/Javadoc and online + * Exchange API documentation. + * + *

If the exchange does not provide the information at all or for the current market id, + * a null value is returned. + * + * @param marketId the id of the market. + * @return the minimum order volume in base currency that is needed to place a Order as a {@link + * BigDecimal}. + * @throws ExchangeNetworkException if a network error occurred trying to connect to the exchange. + * This is implementation specific for each Exchange Adapter - see the documentation for the + * adapter you are using. You could retry the API call, or exit from your Trading Strategy and + * let the Trading Engine execute your Trading Strategy at the next trade cycle. + * @throws TradingApiException if the API call failed for any reason other than a network error. + * This means something bad as happened; you would probably want to wrap this exception in a + * StrategyException and let the Trading Engine shutdown the bot immediately to prevent + * unexpected losses. + * @since 1.2 + */ + default BigDecimal getMinimumOrderVolume(String marketId) + throws TradingApiException, ExchangeNetworkException { + return null; + } + /** * Returns the exchange Ticker a given market id. * diff --git a/bxbot-trading-api/src/test/java/com/gazbert/bxbot/trading/api/TestTradingApi.java b/bxbot-trading-api/src/test/java/com/gazbert/bxbot/trading/api/TestTradingApi.java index 42123c681..060cf6a42 100644 --- a/bxbot-trading-api/src/test/java/com/gazbert/bxbot/trading/api/TestTradingApi.java +++ b/bxbot-trading-api/src/test/java/com/gazbert/bxbot/trading/api/TestTradingApi.java @@ -41,7 +41,7 @@ public class TestTradingApi { @Test public void testGetVersion() { final MyApiImpl myApi = new MyApiImpl(); - assertEquals("1.1", myApi.getVersion()); + assertEquals("1.2", myApi.getVersion()); } @Test @@ -61,6 +61,13 @@ public void testGetTicker() throws Exception { assertNull(ticker.getTimestamp()); } + @Test + public void testGetMinOrder() throws Exception { + final MyApiImpl myApi = new MyApiImpl(); + final BigDecimal minimumOrderVolume = myApi.getMinimumOrderVolume("market-123"); + assertNull(minimumOrderVolume); + } + /** Test class. */ class MyApiImpl implements TradingApi {