diff --git a/README.md b/README.md index 38c3ecc2..db545879 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ and released under the [MIT license](http://opensource.org/licenses/MIT). - Strategy API - Trading Strategies implement this for the Trading Engine to execute them. Trading Strategies and Exchange Adapters are injected by the Trading Engine on startup. The bot uses a crude XML based -dependency injection framework to achieve this; the long term goal is for it to run as a [Spring Boot](http://projects.spring.io/spring-boot/) -app in a microservice system. +dependency injection framework to achieve this; the long term goal is to run it as a [Spring Boot](http://projects.spring.io/spring-boot/) +app in a [microservice](http://martinfowler.com/articles/microservices.html) system. The bot was designed to fail hard and fast if any unexpected errors occur in the Exchange Adapters or Trading Strategies: it will log the error, send an email alert (if configured), and then shutdown. @@ -476,8 +476,8 @@ The following features are in the pipeline: - Exchange Adapter for [Gemini](https://gemini.com/). - REST API for configuring the bot. -- Admin app - a microservice for configuring and managing bots in the cloud. -- Web UI (written in [AngularJS](https://angularjs.org/)) for administering bots. +- Web UI (written in [AngularJS](https://angularjs.org/)) for configuring the bot. +- Admin app - a microservice for administering multiple bots in the cloud. - Trade Analysis app - a microservice that will feed off trading events sent by the bots. - Android app for administering bots. diff --git a/config/samples/kraken/engine.xml b/config/samples/kraken/engine.xml index 1f00bbd0..f6db5bd2 100644 Binary files a/config/samples/kraken/engine.xml and b/config/samples/kraken/engine.xml differ diff --git a/src/main/java/com/gazbert/bxbot/core/exchanges/KrakenExchangeAdapter.java b/src/main/java/com/gazbert/bxbot/core/exchanges/KrakenExchangeAdapter.java index 63869f13..d955e29c 100644 --- a/src/main/java/com/gazbert/bxbot/core/exchanges/KrakenExchangeAdapter.java +++ b/src/main/java/com/gazbert/bxbot/core/exchanges/KrakenExchangeAdapter.java @@ -48,6 +48,7 @@ import java.util.*; /** + * TODO 31 July 2016 - Contains PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! *

* Exchange Adapter for integrating with the Kraken exchange. * The Kraken API is documented here. @@ -380,9 +381,20 @@ public List getYourOpenOrders(String marketId) throws TradingApiExcep return openOrders; } else { - final String errorMsg = FAILED_TO_GET_OPEN_ORDERS + response; - LOG.error(errorMsg); - throw new TradingApiException(errorMsg); + + /* + * TODO 31 July 2016 - PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! + */ + if (krakenResponse.error.contains("EAPI:Invalid key")) { + final String errorMsg = "Received another random Invalid API Key response - ignoring... " + + FAILED_TO_GET_OPEN_ORDERS + response; + LOG.error(errorMsg); + throw new ExchangeNetworkException(errorMsg); + } else { + final String errorMsg = FAILED_TO_GET_OPEN_ORDERS + response; + LOG.error(errorMsg); + throw new TradingApiException(errorMsg); + } } } else { @@ -443,9 +455,20 @@ public String createOrder(String marketId, OrderType orderType, BigDecimal quant return krakenAddOrderResult.txid.get(0); } else { - final String errorMsg = FAILED_TO_ADD_ORDER + response; - LOG.error(errorMsg); - throw new TradingApiException(errorMsg); + + /* + * TODO 31 July 2016 - PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! + */ + if (krakenResponse.error.contains("EAPI:Invalid key")) { + final String errorMsg = "Received another random Invalid API Key response - ignoring... " + + FAILED_TO_ADD_ORDER + response; + LOG.error(errorMsg); + throw new ExchangeNetworkException(errorMsg); + } else { + final String errorMsg = FAILED_TO_ADD_ORDER + response; + LOG.error(errorMsg); + throw new TradingApiException(errorMsg); + } } } else { @@ -491,9 +514,20 @@ public boolean cancelOrder(String orderId, String marketIdNotNeeded) throws Trad } } else { - final String errorMsg = FAILED_TO_CANCEL_ORDER + response; - LOG.error(errorMsg); - throw new TradingApiException(errorMsg); + + /* + * TODO 31 July 2016 - PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! + */ + if (krakenResponse.error.contains("EAPI:Invalid key")) { + final String errorMsg = "Received another random Invalid API Key response - ignoring... " + + FAILED_TO_CANCEL_ORDER + response; + LOG.error(errorMsg); + throw new ExchangeNetworkException(errorMsg); + } else { + final String errorMsg = FAILED_TO_CANCEL_ORDER + response; + LOG.error(errorMsg); + throw new TradingApiException(errorMsg); + } } } else { @@ -587,9 +621,20 @@ public BalanceInfo getBalanceInfo() throws TradingApiException, ExchangeNetworkE return new BalanceInfo(balancesAvailable, new HashMap<>()); } else { - final String errorMsg = FAILED_TO_GET_BALANCE + response; - LOG.error(errorMsg); - throw new TradingApiException(errorMsg); + + /* + * TODO 31 July 2016 - PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! + */ + if (krakenResponse.error.contains("EAPI:Invalid key")) { + final String errorMsg = "Received another random Invalid API Key response - ignoring... " + + FAILED_TO_GET_BALANCE + response; + LOG.error(errorMsg); + throw new ExchangeNetworkException(errorMsg); + } else { + final String errorMsg = FAILED_TO_GET_BALANCE + response; + LOG.error(errorMsg); + throw new TradingApiException(errorMsg); + } } } else { diff --git a/src/test/exchange-data/kraken/AddOrder-apiKeyError.json b/src/test/exchange-data/kraken/AddOrder-apiKeyError.json new file mode 100644 index 00000000..cc0e065d --- /dev/null +++ b/src/test/exchange-data/kraken/AddOrder-apiKeyError.json @@ -0,0 +1,6 @@ +{ + "error": [ + "EAPI:Invalid key" + ], + "result": {} +} \ No newline at end of file diff --git a/src/test/java/com/gazbert/bxbot/core/exchanges/TestKrakenExchangeAdapter.java b/src/test/java/com/gazbert/bxbot/core/exchanges/TestKrakenExchangeAdapter.java index 6c1c8c7e..0d40e2c6 100644 --- a/src/test/java/com/gazbert/bxbot/core/exchanges/TestKrakenExchangeAdapter.java +++ b/src/test/java/com/gazbert/bxbot/core/exchanges/TestKrakenExchangeAdapter.java @@ -79,6 +79,7 @@ public class TestKrakenExchangeAdapter { private static final String ADD_ORDER_BUY_JSON_RESPONSE = "./src/test/exchange-data/kraken/AddOrder-buy.json"; private static final String ADD_ORDER_SELL_JSON_RESPONSE = "./src/test/exchange-data/kraken/AddOrder-sell.json"; private static final String ADD_ORDER_ERROR_JSON_RESPONSE = "./src/test/exchange-data/kraken/AddOrder-error.json"; + private static final String ADD_ORDER_API_KEY_ERROR_JSON_RESPONSE = "./src/test/exchange-data/kraken/AddOrder-apiKeyError.json"; private static final String CANCEL_ORDER_JSON_RESPONSE = "./src/test/exchange-data/kraken/CancelOrder.json"; private static final String CANCEL_ORDER_ERROR_JSON_RESPONSE = "./src/test/exchange-data/kraken/CancelOrder-error.json"; @@ -450,6 +451,30 @@ public void testCreateOrderExchangeErrorResponse() throws Exception { PowerMock.verifyAll(); } + /* + * TODO 31 July 2016 - Test for PATCH for occasional 'EAPI:Invalid key' responses. Remove when I get to bottom of it! + */ + @Test(expected = ExchangeNetworkException.class) + public void testCreateOrderExchangeApiKeyErrorResponse() throws Exception { + + // Load the canned response from the exchange + final byte[] encoded = Files.readAllBytes(Paths.get(ADD_ORDER_API_KEY_ERROR_JSON_RESPONSE)); + final AbstractExchangeAdapter.ExchangeHttpResponse exchangeResponse = + new AbstractExchangeAdapter.ExchangeHttpResponse(200, "OK", new String(encoded, StandardCharsets.UTF_8)); + + // Partial mock so we do not send stuff down the wire + final KrakenExchangeAdapter exchangeAdapter = PowerMock.createPartialMockAndInvokeDefaultConstructor( + KrakenExchangeAdapter.class, MOCKED_SEND_AUTHENTICATED_REQUEST_TO_EXCHANGE_METHOD); + PowerMock.expectPrivate(exchangeAdapter, MOCKED_SEND_AUTHENTICATED_REQUEST_TO_EXCHANGE_METHOD, eq(ADD_ORDER), + anyObject(Map.class)).andReturn(exchangeResponse); + + PowerMock.replayAll(); + exchangeAdapter.init(exchangeConfig); + + exchangeAdapter.createOrder(MARKET_ID, OrderType.SELL, SELL_ORDER_QUANTITY, SELL_ORDER_PRICE); + PowerMock.verifyAll(); + } + @Test(expected = ExchangeNetworkException.class) public void testCreateOrderHandlesExchangeNetworkException() throws Exception {