-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from charvam/master
Added Coinbase API connector.
- Loading branch information
Showing
5 changed files
with
243 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
plugin-base/src/main/java/io/everytrade/server/plugin/impl/everytrade/CoinbaseConnector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package io.everytrade.server.plugin.impl.everytrade; | ||
|
||
import io.everytrade.server.model.SupportedExchange; | ||
import io.everytrade.server.plugin.api.IPlugin; | ||
import io.everytrade.server.plugin.api.connector.ConnectorDescriptor; | ||
import io.everytrade.server.plugin.api.connector.ConnectorParameterDescriptor; | ||
import io.everytrade.server.plugin.api.connector.ConnectorParameterType; | ||
import io.everytrade.server.plugin.api.connector.DownloadResult; | ||
import io.everytrade.server.plugin.api.connector.IConnector; | ||
import io.everytrade.server.plugin.api.parser.ParseResult; | ||
import org.knowm.xchange.Exchange; | ||
import org.knowm.xchange.ExchangeFactory; | ||
import org.knowm.xchange.ExchangeSpecification; | ||
import org.knowm.xchange.coinbase.v2.CoinbaseExchange; | ||
import org.knowm.xchange.coinbase.v2.service.CoinbaseTradeService; | ||
import org.knowm.xchange.dto.trade.UserTrade; | ||
import org.knowm.xchange.service.account.AccountService; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
public class CoinbaseConnector implements IConnector { | ||
private static final String ID = EveryTradePlugin.ID + IPlugin.PLUGIN_PATH_SEPARATOR + "coinbaseApiConnector"; | ||
private static final SupportedExchange SUPPORTED_EXCHANGE = SupportedExchange.COINBASE; | ||
|
||
private static final ConnectorParameterDescriptor PARAMETER_API_SECRET = | ||
new ConnectorParameterDescriptor( | ||
"apiSecret", | ||
ConnectorParameterType.SECRET, | ||
"API Secret", | ||
"" | ||
); | ||
|
||
private static final ConnectorParameterDescriptor PARAMETER_API_KEY = | ||
new ConnectorParameterDescriptor( | ||
"apiKey", | ||
ConnectorParameterType.STRING, | ||
"API Key", | ||
"" | ||
); | ||
|
||
public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor( | ||
ID, | ||
"Coinbase Connector", | ||
"", | ||
SUPPORTED_EXCHANGE.getInternalId(), | ||
List.of(PARAMETER_API_KEY, PARAMETER_API_SECRET) | ||
); | ||
private static final int REAL_WALLET_ID_LENGTH = 36; | ||
private final String apiKey; | ||
private final String apiSecret; | ||
|
||
public CoinbaseConnector(Map<String, String> parameters) { | ||
Objects.requireNonNull(this.apiKey = parameters.get(PARAMETER_API_KEY.getId())); | ||
Objects.requireNonNull(this.apiSecret = parameters.get(PARAMETER_API_SECRET.getId())); | ||
} | ||
|
||
@Override | ||
public String getId() { | ||
return ID; | ||
} | ||
|
||
@Override | ||
public DownloadResult getTransactions(String lastTransactionId) { | ||
final ExchangeSpecification exSpec = new CoinbaseExchange().getDefaultExchangeSpecification(); | ||
exSpec.setApiKey(apiKey); | ||
exSpec.setSecretKey(apiSecret); | ||
final Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exSpec); | ||
final CoinbaseTradeService tradeService = (CoinbaseTradeService) exchange.getTradeService(); | ||
final AccountService accountService = exchange.getAccountService(); | ||
final Set<String> walletIds; | ||
try { | ||
walletIds = filterWalletIds(accountService.getAccountInfo().getWallets().keySet()); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Wallets download failed.", e); | ||
} | ||
final CoinbaseDownloader coinbaseDownloader | ||
= new CoinbaseDownloader(tradeService, lastTransactionId, walletIds); | ||
final List<UserTrade> userTrades = coinbaseDownloader.download(); | ||
final ParseResult parseResult = XChangeConnectorParser.getParseResult(userTrades, SUPPORTED_EXCHANGE); | ||
|
||
return new DownloadResult(parseResult, coinbaseDownloader.getLastTransactionId()); | ||
} | ||
|
||
private Set<String> filterWalletIds(Set<String> walletsIds) { | ||
return walletsIds | ||
.stream() | ||
.filter(s -> s.length() == REAL_WALLET_ID_LENGTH) | ||
.collect(Collectors.toSet()); | ||
} | ||
} |
140 changes: 140 additions & 0 deletions
140
...in-base/src/main/java/io/everytrade/server/plugin/impl/everytrade/CoinbaseDownloader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package io.everytrade.server.plugin.impl.everytrade; | ||
|
||
import org.knowm.xchange.coinbase.v2.service.CoinbaseTradeHistoryParams; | ||
import org.knowm.xchange.coinbase.v2.service.CoinbaseTradeService; | ||
import org.knowm.xchange.dto.trade.UserTrade; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.log; | ||
|
||
public class CoinbaseDownloader { | ||
//https://developers.coinbase.com/api/v2#rate-limiting 10.000 / API-KEY / hour ---> 2500 / 15 min | ||
private static final int MAX_REQUEST_COUNT = 2500; | ||
private static final String DASH_SYMBOL = "-"; | ||
private static final String COLON_SYMBOL = ":"; | ||
private static final String PIPE_SYMBOL = "|"; | ||
private static final int TRANSACTIONS_PER_REQUEST_LIMIT = 100; | ||
private final CoinbaseTradeService tradeService; | ||
private final Map<String, WalletState> actualWalletStates = new HashMap<>(); | ||
|
||
public CoinbaseDownloader( | ||
CoinbaseTradeService tradeService, | ||
String lastTransactionId, | ||
Set<String> actualWalletIds | ||
) { | ||
Objects.requireNonNull(this.tradeService = tradeService); | ||
Map<String, WalletState> previousWalletStates; | ||
if (lastTransactionId == null) { | ||
previousWalletStates = new HashMap<>(); | ||
} else { | ||
previousWalletStates = Arrays.stream(lastTransactionId.split("\\" + PIPE_SYMBOL)) | ||
.map(entry -> entry.split(COLON_SYMBOL)) | ||
.collect(Collectors.toMap(entry -> entry[0], entry -> new WalletState(entry[1], entry[2]))); | ||
} | ||
|
||
for (String actualWalletId : actualWalletIds) { | ||
final WalletState walletState = previousWalletStates.get(actualWalletId); | ||
actualWalletStates.put(actualWalletId, Objects.requireNonNullElseGet( | ||
walletState, | ||
() -> new WalletState(null, null)) | ||
); | ||
} | ||
} | ||
|
||
public List<UserTrade> download() { | ||
final List<UserTrade> userTrades = new ArrayList<>(); | ||
int sentRequests = 0; | ||
final CoinbaseTradeHistoryParams tradeHistoryParams | ||
= (CoinbaseTradeHistoryParams) tradeService.createTradeHistoryParams(); | ||
tradeHistoryParams.setLimit(TRANSACTIONS_PER_REQUEST_LIMIT); | ||
|
||
for (Map.Entry<String, WalletState> entry : actualWalletStates.entrySet()) { | ||
String lastBuyId = entry.getValue().lastBuyId; | ||
String lastSellId = entry.getValue().lastSellId; | ||
final String walletId = entry.getKey(); | ||
|
||
while (sentRequests < MAX_REQUEST_COUNT) { | ||
++sentRequests; | ||
tradeHistoryParams.setStartId(lastBuyId); | ||
final List<UserTrade> buysTradeHistoryBlock; | ||
try { | ||
buysTradeHistoryBlock | ||
= tradeService.getBuyTradeHistory(tradeHistoryParams, walletId).getUserTrades(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Download buys history failed.", e); | ||
} | ||
|
||
if (buysTradeHistoryBlock.isEmpty()) { | ||
break; | ||
} | ||
|
||
userTrades.addAll(buysTradeHistoryBlock); | ||
lastBuyId = buysTradeHistoryBlock.get(0).getId(); | ||
|
||
} | ||
|
||
while (sentRequests < MAX_REQUEST_COUNT) { | ||
++sentRequests; | ||
tradeHistoryParams.setStartId(lastSellId); | ||
final List<UserTrade> sellsTradeHistoryBlock; | ||
try { | ||
sellsTradeHistoryBlock | ||
= tradeService.getSellTradeHistory(tradeHistoryParams, walletId).getUserTrades(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Download sells history failed.", e); | ||
} | ||
|
||
if (sellsTradeHistoryBlock.isEmpty()) { | ||
break; | ||
} | ||
|
||
userTrades.addAll(sellsTradeHistoryBlock); | ||
lastSellId = sellsTradeHistoryBlock.get(0).getId(); | ||
|
||
} | ||
if (sentRequests == MAX_REQUEST_COUNT) { | ||
log.info("Max request count {} has been achieved.", MAX_REQUEST_COUNT); | ||
} | ||
|
||
final WalletState walletState = actualWalletStates.get(walletId); | ||
walletState.lastBuyId = lastBuyId; | ||
walletState.lastSellId = lastSellId; | ||
|
||
} | ||
return userTrades; | ||
} | ||
|
||
public String getLastTransactionId() { | ||
final String result = actualWalletStates.entrySet().stream() | ||
.filter(entry -> entry.getValue().lastBuyId != null || entry.getValue().lastSellId != null) | ||
.map( | ||
entry -> entry.getKey() | ||
+ COLON_SYMBOL | ||
+ Objects.requireNonNullElse(entry.getValue().lastBuyId, DASH_SYMBOL) | ||
+ COLON_SYMBOL | ||
+ Objects.requireNonNullElse(entry.getValue().lastSellId, DASH_SYMBOL) | ||
) | ||
.collect(Collectors.joining(PIPE_SYMBOL)); | ||
|
||
return result; | ||
} | ||
|
||
private static class WalletState { | ||
private String lastBuyId; | ||
private String lastSellId; | ||
|
||
public WalletState(String lastBuyId, String lastSellId) { | ||
this.lastBuyId = DASH_SYMBOL.equals(lastBuyId) ? null : lastBuyId; | ||
this.lastSellId = DASH_SYMBOL.equals(lastSellId) ? null : lastSellId; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters