This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 54ee1e9 [XChange] Add initial support for account management 54ee1e9 is described below commit 54ee1e984e49218ad3f6e356da279a7447a06286 Author: Thomas Diesler <tdies...@redhat.com> AuthorDate: Wed Feb 7 12:32:31 2018 +0100 [XChange] Add initial support for account management Resolves CAMEL-12170 --- .../src/main/docs/xchange-component.adoc | 27 +++++-- ...taProducer.java => XChangeAccountProducer.java} | 32 ++++---- .../camel/component/xchange/XChangeComponent.java | 28 +++++-- .../component/xchange/XChangeConfiguration.java | 14 +++- .../camel/component/xchange/XChangeEndpoint.java | 81 ++++++++++++++++++--- .../xchange/XChangeMarketDataProducer.java | 7 +- .../component/xchange/XChangeMetaDataProducer.java | 2 + .../xchange/account/AccountProducerTest.java | 85 ++++++++++++++++++++++ ...nsumerTest.java => MarketDataProducerTest.java} | 13 +++- ...ConsumerTest.java => MetaDataProducerTest.java} | 2 +- parent/pom.xml | 2 +- 11 files changed, 245 insertions(+), 48 deletions(-) diff --git a/components/camel-xchange/src/main/docs/xchange-component.adoc b/components/camel-xchange/src/main/docs/xchange-component.adoc index d351375..8cf5cc3 100644 --- a/components/camel-xchange/src/main/docs/xchange-component.adoc +++ b/components/camel-xchange/src/main/docs/xchange-component.adoc @@ -6,8 +6,7 @@ The *xchange:* component uses the https://knowm.org/open-source/xchange/[XChange] Java library to provide access to 60+ Bitcoin and Altcoin exchanges. It comes with a consistent interface for trading and accessing market data. -Camel can poll for updates of current market data. It can also be used to query historical data based -on the parameters defined on the endpoint. +Camel can get crypto currency market data, query historical data, place market orders and much more. Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -35,8 +34,6 @@ xchange://exchange?options The XChange component has no options. // component options: END - - // endpoint options: START The XChange endpoint is configured using URI syntax: @@ -67,9 +64,25 @@ with the following path and query parameters: |=== // endpoint options: END -### Exchange data format +### Authentication -[TODO] +This component communicates with supported crypto currency exchanges via REST API. Some API requests use simple unauthenticated GET request. +For most of the interesting stuff however, you'd need an account with the exchange and have API access keys enabled. + +These API access keys need to be guarded tightly, especially so when they also allow for the withdraw functionality. +In which case, anyone who can get hold of your API keys can easily transfer funds from your account to some other address i.e. steal your money. + +Your API access keys can be strored in an exchange specific properties file in your SSH directory. +For Binance for example this would be: `~/.ssh/binance-secret.keys` + +---- +## +# This file MUST NEVER be commited to source control. +# It is therefore added to .gitignore. +# +apiKey = GuRW0********* +secretKey = nKLki************ +---- ### Message Headers @@ -81,5 +94,5 @@ In this sample we find the current Bitcoin market price in USDT: [source,java] --------------------------------------------------------------------------------------------- -from("xchange:binance?method=ticker¤cyPair=BTC/USDT").to("jms:queue:btc"); +from("direct:ticker").to("xchange:binance?service=market&method=ticker¤cyPair=BTC/USDT") --------------------------------------------------------------------------------------------- diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeAccountProducer.java similarity index 61% copy from components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java copy to components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeAccountProducer.java index 03e397f..7dd5e86 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeAccountProducer.java @@ -16,22 +16,14 @@ */ package org.apache.camel.component.xchange; -import static org.apache.camel.component.xchange.XChangeConfiguration.HEADER_CURRENCY_PAIR; - import org.apache.camel.Exchange; import org.apache.camel.component.xchange.XChangeConfiguration.XChangeMethod; import org.apache.camel.impl.DefaultProducer; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.marketdata.Ticker; -import org.knowm.xchange.service.marketdata.MarketDataService; -public class XChangeMarketDataProducer extends DefaultProducer { - - private final MarketDataService marketService; +public class XChangeAccountProducer extends DefaultProducer { - public XChangeMarketDataProducer(XChangeEndpoint endpoint) { + public XChangeAccountProducer(XChangeEndpoint endpoint) { super(endpoint); - marketService = endpoint.getXChange().getMarketDataService(); } @Override @@ -45,11 +37,19 @@ public class XChangeMarketDataProducer extends DefaultProducer { XChangeEndpoint endpoint = getEndpoint(); XChangeMethod method = endpoint.getConfiguration().getMethod(); - if (XChangeMethod.ticker == method) { - CurrencyPair pair = exchange.getIn().getHeader(HEADER_CURRENCY_PAIR, CurrencyPair.class); - pair = pair != null ? pair : exchange.getMessage().getBody(CurrencyPair.class); - Ticker ticker = marketService.getTicker(pair); - exchange.getMessage().setBody(ticker); - } + if (XChangeMethod.balances == method) { + Object body = endpoint.getBalances(); + exchange.getMessage().setBody(body); + } + + else if (XChangeMethod.fundingHistory == method) { + Object body = endpoint.getFundingHistory(); + exchange.getMessage().setBody(body); + } + + else if (XChangeMethod.wallets == method) { + Object body = endpoint.getWallets(); + exchange.getMessage().setBody(body); + } } } \ No newline at end of file diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeComponent.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeComponent.java index 13b151a..7143581 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeComponent.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeComponent.java @@ -26,6 +26,8 @@ import org.knowm.xchange.utils.Assert; public class XChangeComponent extends DefaultComponent { + private XChange exchange; + @Override protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { @@ -36,15 +38,29 @@ public class XChangeComponent extends DefaultComponent { // Set the the required name of the exchange configuration.setName(remaining); - // Get the XChange implementation - Class<? extends Exchange> exchangeClass = configuration.getXChangeClass(); - Assert.notNull(exchangeClass, "XChange not supported: " + configuration.getName()); - - // Create the XChange and associated Endpoint - XChange exchange = new XChange(ExchangeFactory.INSTANCE.createExchange(exchangeClass)); + XChange exchange = createXChange(configuration); XChangeEndpoint endpoint = new XChangeEndpoint(uri, this, configuration, exchange); return endpoint; } + public XChange getXChange() { + return exchange; + } + + private synchronized XChange createXChange(XChangeConfiguration configuration) { + + if (exchange == null) { + + // Get the XChange implementation + Class<? extends Exchange> exchangeClass = configuration.getXChangeClass(); + Assert.notNull(exchangeClass, "XChange not supported: " + configuration.getName()); + + // Create the XChange and associated Endpoint + exchange = new XChange(ExchangeFactory.INSTANCE.createExchange(exchangeClass)); + } + + return exchange; + } + } \ No newline at end of file diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeConfiguration.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeConfiguration.java index 3e82d2d..36f9376 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeConfiguration.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeConfiguration.java @@ -33,8 +33,18 @@ import org.knowm.xchange.currency.CurrencyPair; @UriParams public class XChangeConfiguration { - public enum XChangeService { marketdata, metadata } - public enum XChangeMethod { currencies, currencyMetaData, currencyPairs, currencyPairMetaData, ticker } + // Available service + public enum XChangeService { marketdata, metadata, account } + + // Available methods + public enum XChangeMethod { + // Account service methods + balances, fundingHistory, wallets, + // Metadata service methods + currencies, currencyMetaData, currencyPairs, currencyPairMetaData, + // Marketdata service methods + ticker + } public static final String HEADER_CURRENCY = "Currency"; public static final String HEADER_CURRENCY_PAIR = "CurrencyPair"; diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeEndpoint.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeEndpoint.java index 43250fe..88cfe4dd 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeEndpoint.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeEndpoint.java @@ -16,6 +16,9 @@ */ package org.apache.camel.component.xchange; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -28,25 +31,38 @@ import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.account.AccountInfo; +import org.knowm.xchange.dto.account.Balance; +import org.knowm.xchange.dto.account.FundingRecord; +import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.meta.CurrencyMetaData; import org.knowm.xchange.dto.meta.CurrencyPairMetaData; import org.knowm.xchange.dto.meta.ExchangeMetaData; +import org.knowm.xchange.service.account.AccountService; +import org.knowm.xchange.service.marketdata.MarketDataService; +import org.knowm.xchange.service.trade.params.TradeHistoryParams; import org.knowm.xchange.utils.Assert; @UriEndpoint(firstVersion = "2.21.0", scheme = "xchange", title = "XChange", syntax = "xchange:name", producerOnly = true, label = "blockchain") public class XChangeEndpoint extends DefaultEndpoint { @UriParam - private XChangeConfiguration configuration; + private final XChangeConfiguration configuration; private final XChange exchange; - public XChangeEndpoint(String uri, XChangeComponent component, XChangeConfiguration properties, XChange exchange) { + public XChangeEndpoint(String uri, XChangeComponent component, XChangeConfiguration configuration, XChange exchange) { super(uri, component); - this.configuration = properties; + this.configuration = configuration; this.exchange = exchange; } @Override + public XChangeComponent getComponent() { + return (XChangeComponent) super.getComponent(); + } + + @Override public Consumer createConsumer(Processor processor) throws Exception { throw new UnsupportedOperationException(); } @@ -57,10 +73,12 @@ public class XChangeEndpoint extends DefaultEndpoint { Producer producer = null; XChangeService service = getConfiguration().getService(); - if (XChangeService.metadata == service) { - producer = new XChangeMetaDataProducer(this); + if (XChangeService.account == service) { + producer = new XChangeAccountProducer(this); } else if (XChangeService.marketdata == service) { producer = new XChangeMarketDataProducer(this); + } else if (XChangeService.metadata == service) { + producer = new XChangeMetaDataProducer(this); } Assert.notNull(producer, "Unsupported service: " + service); @@ -76,10 +94,6 @@ public class XChangeEndpoint extends DefaultEndpoint { return configuration; } - public XChange getXChange() { - return exchange; - } - public List<Currency> getCurrencies() { ExchangeMetaData metaData = exchange.getExchangeMetaData(); return metaData.getCurrencies().keySet().stream().sorted().collect(Collectors.toList()); @@ -101,4 +115,53 @@ public class XChangeEndpoint extends DefaultEndpoint { ExchangeMetaData metaData = exchange.getExchangeMetaData(); return metaData.getCurrencyPairs().get(pair); } + + public List<Balance> getBalances() throws IOException { + List<Balance> balances = new ArrayList<>(); + getWallets().stream().forEach(w -> { + for (Balance aux : w.getBalances().values()) { + Currency curr = aux.getCurrency(); + CurrencyMetaData metaData = getCurrencyMetaData(curr); + if (metaData != null) { + int scale = metaData.getScale(); + double total = aux.getTotal().doubleValue(); + double scaledTotal = total * Math.pow(10, scale / 2); + if (1 <= scaledTotal) { + balances.add(aux); + } + } + } + }); + return balances.stream().sorted(new Comparator<Balance>() { + public int compare(Balance o1, Balance o2) { + return o1.getCurrency().compareTo(o2.getCurrency()); + } + }).collect(Collectors.toList()); + } + + public List<FundingRecord> getFundingHistory() throws IOException { + AccountService accountService = exchange.getAccountService(); + TradeHistoryParams fundingHistoryParams = accountService.createFundingHistoryParams(); + return accountService.getFundingHistory(fundingHistoryParams).stream().sorted(new Comparator<FundingRecord>() { + public int compare(FundingRecord o1, FundingRecord o2) { + return o1.getDate().compareTo(o2.getDate()); + } + }).collect(Collectors.toList()); + } + + public List<Wallet> getWallets() throws IOException { + AccountService accountService = exchange.getAccountService(); + AccountInfo accountInfo = accountService.getAccountInfo(); + return accountInfo.getWallets().values().stream().sorted(new Comparator<Wallet>() { + public int compare(Wallet o1, Wallet o2) { + return o1.getName().compareTo(o2.getName()); + } + }).collect(Collectors.toList()); + } + + public Ticker getTicker(CurrencyPair pair) throws IOException { + Assert.notNull(pair, "Null currency pair"); + MarketDataService marketService = exchange.getMarketDataService(); + return marketService.getTicker(pair); + } } diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java index 03e397f..ecd74a4 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMarketDataProducer.java @@ -23,15 +23,11 @@ import org.apache.camel.component.xchange.XChangeConfiguration.XChangeMethod; import org.apache.camel.impl.DefaultProducer; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; -import org.knowm.xchange.service.marketdata.MarketDataService; public class XChangeMarketDataProducer extends DefaultProducer { - private final MarketDataService marketService; - public XChangeMarketDataProducer(XChangeEndpoint endpoint) { super(endpoint); - marketService = endpoint.getXChange().getMarketDataService(); } @Override @@ -48,7 +44,8 @@ public class XChangeMarketDataProducer extends DefaultProducer { if (XChangeMethod.ticker == method) { CurrencyPair pair = exchange.getIn().getHeader(HEADER_CURRENCY_PAIR, CurrencyPair.class); pair = pair != null ? pair : exchange.getMessage().getBody(CurrencyPair.class); - Ticker ticker = marketService.getTicker(pair); + pair = pair != null ? pair : endpoint.getConfiguration().getCurrencyPair(); + Ticker ticker = endpoint.getTicker(pair); exchange.getMessage().setBody(ticker); } } diff --git a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMetaDataProducer.java b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMetaDataProducer.java index 8fb4de8..502ba0b 100644 --- a/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMetaDataProducer.java +++ b/components/camel-xchange/src/main/java/org/apache/camel/component/xchange/XChangeMetaDataProducer.java @@ -55,6 +55,7 @@ public class XChangeMetaDataProducer extends DefaultProducer { else if (XChangeMethod.currencyMetaData == method) { Currency curr = exchange.getMessage().getHeader(HEADER_CURRENCY, Currency.class); curr = curr != null ? curr : exchange.getMessage().getBody(Currency.class); + curr = curr != null ? curr : endpoint.getConfiguration().getCurrency(); Object body = endpoint.getCurrencyMetaData(curr); exchange.getMessage().setBody(body); } @@ -62,6 +63,7 @@ public class XChangeMetaDataProducer extends DefaultProducer { else if (XChangeMethod.currencyPairMetaData == method) { CurrencyPair pair = exchange.getIn().getHeader(HEADER_CURRENCY_PAIR, CurrencyPair.class); pair = pair != null ? pair : exchange.getMessage().getBody(CurrencyPair.class); + pair = pair != null ? pair : endpoint.getConfiguration().getCurrencyPair(); Object body = endpoint.getCurrencyPairMetaData(pair); exchange.getMessage().setBody(body); } diff --git a/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/account/AccountProducerTest.java b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/account/AccountProducerTest.java new file mode 100644 index 0000000..c1803f5 --- /dev/null +++ b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/account/AccountProducerTest.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.xchange.account; + +import java.util.List; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.xchange.XChangeComponent; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; +import org.knowm.xchange.dto.account.Balance; +import org.knowm.xchange.dto.account.FundingRecord; +import org.knowm.xchange.dto.account.Wallet; + +public class AccountProducerTest extends CamelTestSupport { + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + + from("direct:balances") + .to("xchange:binance?service=account&method=balances"); + + from("direct:wallets") + .to("xchange:binance?service=account&method=wallets"); + + from("direct:fundingHistory") + .to("xchange:binance?service=account&method=fundingHistory"); + } + }; + } + + @Test + @SuppressWarnings("unchecked") + public void testBalances() throws Exception { + + Assume.assumeTrue(hasAPICredentials()); + + List<Balance> balances = template.requestBody("direct:balances", null, List.class); + Assert.assertNotNull("Balances not null", balances); + } + + @Test + @SuppressWarnings("unchecked") + public void testWallets() throws Exception { + + Assume.assumeTrue(hasAPICredentials()); + + List<Wallet> wallets = template.requestBody("direct:wallets", null, List.class); + Assert.assertNotNull("Wallets not null", wallets); + } + + @Test + @SuppressWarnings("unchecked") + public void testFundingHistory() throws Exception { + + Assume.assumeTrue(hasAPICredentials()); + + List<FundingRecord> records = template.requestBody("direct:fundingHistory", null, List.class); + Assert.assertNotNull("Funding records not null", records); + } + + private boolean hasAPICredentials() { + XChangeComponent component = context().getComponent("xchange", XChangeComponent.class); + return component.getXChange().getExchangeSpecification().getApiKey() != null; + } +} diff --git a/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/TickerConsumerTest.java b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/MarketDataProducerTest.java similarity index 80% rename from components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/TickerConsumerTest.java rename to components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/MarketDataProducerTest.java index 0ad79e2..461737d 100644 --- a/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/TickerConsumerTest.java +++ b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/market/MarketDataProducerTest.java @@ -25,15 +25,19 @@ import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; -public class TickerConsumerTest extends CamelTestSupport { +public class MarketDataProducerTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { + from("direct:ticker") .to("xchange:binance?service=marketdata&method=ticker"); + + from("direct:tickerBTCUSDT") + .to("xchange:binance?service=marketdata&method=ticker¤cyPair=BTC/USDT"); } }; } @@ -47,5 +51,12 @@ public class TickerConsumerTest extends CamelTestSupport { ticker = template.requestBodyAndHeader("direct:ticker", null, HEADER_CURRENCY_PAIR, CurrencyPair.EOS_ETH, Ticker.class); Assert.assertNotNull("Ticker not null", ticker); } + + @Test + public void testTickerBTCUSDT() throws Exception { + + Ticker ticker = template.requestBody("direct:tickerBTCUSDT", null, Ticker.class); + Assert.assertNotNull("Ticker not null", ticker); + } } diff --git a/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataConsumerTest.java b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataProducerTest.java similarity index 98% rename from components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataConsumerTest.java rename to components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataProducerTest.java index a37164d..d99c4c2 100644 --- a/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataConsumerTest.java +++ b/components/camel-xchange/src/test/java/org/apache/camel/component/xchange/metadata/MetaDataProducerTest.java @@ -30,7 +30,7 @@ import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.meta.CurrencyMetaData; import org.knowm.xchange.dto.meta.CurrencyPairMetaData; -public class MetaDataConsumerTest extends CamelTestSupport { +public class MetaDataProducerTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() throws Exception { diff --git a/parent/pom.xml b/parent/pom.xml index ae44aa5..79cc5f0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -721,7 +721,7 @@ <xbean-spring-version>4.5</xbean-spring-version> <!-- xbean-asm4-bundle is used by openjpa --> <xbean-asm4-bundle-version>3.14</xbean-asm4-bundle-version> - <xchange-version>4.3.2</xchange-version> + <xchange-version>4.3.3</xchange-version> <xerces-bundle-version>2.11.0_1</xerces-bundle-version> <xerces-version>2.11.0</xerces-version> <!-- needed to manage the xml-apis version in camel-xmljson --> -- To stop receiving notification emails like this one, please contact davscl...@apache.org.