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 {