Used BitfinexExchange as a template to create generic XChangeExchange
Implemented ItBitExchange via XChangeExchange
This commit is contained in:
parent
7c75fcc02d
commit
44c565cf0f
|
@ -3,3 +3,4 @@
|
||||||
dist/
|
dist/
|
||||||
bin/
|
bin/
|
||||||
build/
|
build/
|
||||||
|
.idea
|
||||||
|
|
|
@ -20,6 +20,7 @@ package com.generalbytes.batm.server.extensions;
|
||||||
|
|
||||||
public interface ICurrencies {
|
public interface ICurrencies {
|
||||||
public static final String BTC = "BTC";
|
public static final String BTC = "BTC";
|
||||||
|
public static final String XBT = "XBT"; //alternate BTC code
|
||||||
public static final String LTC = "LTC";
|
public static final String LTC = "LTC";
|
||||||
public static final String DOGE = "DOGE";
|
public static final String DOGE = "DOGE";
|
||||||
public static final String MAX = "MAX";
|
public static final String MAX = "MAX";
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -27,6 +27,9 @@
|
||||||
<ratesource prefix="bitfinex" name ="Bitfinex.com Exchange" >
|
<ratesource prefix="bitfinex" name ="Bitfinex.com Exchange" >
|
||||||
<cryptocurrency>BTC</cryptocurrency>
|
<cryptocurrency>BTC</cryptocurrency>
|
||||||
</ratesource>
|
</ratesource>
|
||||||
|
<ratesource prefix="itbit" name ="itBit.com Exchange" >
|
||||||
|
<cryptocurrency>BTC</cryptocurrency>
|
||||||
|
</ratesource>
|
||||||
<paymentprocessor prefix="bitcoinpay" name="BitcoinPay.com" >
|
<paymentprocessor prefix="bitcoinpay" name="BitcoinPay.com" >
|
||||||
<param name="apikey" />
|
<param name="apikey" />
|
||||||
<cryptocurrency>BTC</cryptocurrency>
|
<cryptocurrency>BTC</cryptocurrency>
|
||||||
|
@ -42,6 +45,13 @@
|
||||||
<param name="apisecret" />
|
<param name="apisecret" />
|
||||||
<cryptocurrency>BTC</cryptocurrency>
|
<cryptocurrency>BTC</cryptocurrency>
|
||||||
</exchange>
|
</exchange>
|
||||||
|
<exchange prefix="itbit" name="itBit.com Exchange">
|
||||||
|
<param name="userId" />
|
||||||
|
<param name="walletId" />
|
||||||
|
<param name="apikey" />
|
||||||
|
<param name="apisecret" />
|
||||||
|
<cryptocurrency>BTC</cryptocurrency>
|
||||||
|
</exchange>
|
||||||
<cryptologo cryptocurrency="BTC" file="btc.png"/>
|
<cryptologo cryptocurrency="BTC" file="btc.png"/>
|
||||||
</extension>
|
</extension>
|
||||||
<extension class="com.generalbytes.batm.server.extensions.extra.dogecoin.DogecoinExtension">
|
<extension class="com.generalbytes.batm.server.extensions.extra.dogecoin.DogecoinExtension">
|
||||||
|
|
|
@ -19,6 +19,7 @@ package com.generalbytes.batm.server.extensions.extra.bitcoin;
|
||||||
|
|
||||||
import com.generalbytes.batm.server.extensions.*;
|
import com.generalbytes.batm.server.extensions.*;
|
||||||
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitfinex.BitfinexExchange;
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitfinex.BitfinexExchange;
|
||||||
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.itbit.ItBitExchange;
|
||||||
import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.bitcoinpay.BitcoinPayPP;
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.bitcoinpay.BitcoinPayPP;
|
||||||
import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.coinofsale.CoinOfSalePP;
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.coinofsale.CoinOfSalePP;
|
||||||
import com.generalbytes.batm.server.extensions.extra.bitcoin.sources.BitcoinAverageRateSource;
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.sources.BitcoinAverageRateSource;
|
||||||
|
@ -47,6 +48,12 @@ public class BitcoinExtension implements IExtension{
|
||||||
String apiKey = paramTokenizer.nextToken();
|
String apiKey = paramTokenizer.nextToken();
|
||||||
String apiSecret = paramTokenizer.nextToken();
|
String apiSecret = paramTokenizer.nextToken();
|
||||||
return new BitfinexExchange(apiKey, apiSecret);
|
return new BitfinexExchange(apiKey, apiSecret);
|
||||||
|
} else if ("itbit".equalsIgnoreCase(prefix)) {
|
||||||
|
String userId = paramTokenizer.nextToken();
|
||||||
|
String walletId = paramTokenizer.nextToken();
|
||||||
|
String clientKey = paramTokenizer.nextToken();
|
||||||
|
String clientSecret = paramTokenizer.nextToken();
|
||||||
|
return new ItBitExchange(userId, walletId, clientKey, clientSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -144,6 +151,8 @@ public class BitcoinExtension implements IExtension{
|
||||||
return new FixPriceRateSource(rate,preferedFiatCurrency);
|
return new FixPriceRateSource(rate,preferedFiatCurrency);
|
||||||
}else if ("bitfinex".equalsIgnoreCase(exchangeType)) {
|
}else if ("bitfinex".equalsIgnoreCase(exchangeType)) {
|
||||||
return new BitfinexExchange("**","**");
|
return new BitfinexExchange("**","**");
|
||||||
|
}else if ("itbit".equalsIgnoreCase(exchangeType)) {
|
||||||
|
return new ItBitExchange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,723 @@
|
||||||
|
/*************************************************************************************
|
||||||
|
* Copyright (C) 2014 GENERAL BYTES s.r.o. All rights reserved.
|
||||||
|
* <p/>
|
||||||
|
* This software may be distributed and modified under the terms of the GNU
|
||||||
|
* General Public License version 2 (GPL2) as published by the Free Software
|
||||||
|
* Foundation and appearing in the file GPL2.TXT included in the packaging of
|
||||||
|
* this file. Please note that GPL2 Section 2[b] requires that all works based
|
||||||
|
* on this software must also be made publicly available under the terms of
|
||||||
|
* the GPL2 ("Copyleft").
|
||||||
|
* <p/>
|
||||||
|
* Contact information
|
||||||
|
* -------------------
|
||||||
|
* <p/>
|
||||||
|
* GENERAL BYTES s.r.o.
|
||||||
|
* Web : http://www.generalbytes.com
|
||||||
|
* <p/>
|
||||||
|
* Other information:
|
||||||
|
* <p/>
|
||||||
|
* This implementation was created in cooperation with Orillia BVBA
|
||||||
|
************************************************************************************/
|
||||||
|
package com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges;
|
||||||
|
|
||||||
|
import com.generalbytes.batm.server.extensions.IExchangeAdvanced;
|
||||||
|
import com.generalbytes.batm.server.extensions.IRateSourceAdvanced;
|
||||||
|
import com.generalbytes.batm.server.extensions.ITask;
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.xeiam.xchange.Exchange;
|
||||||
|
import com.xeiam.xchange.ExchangeFactory;
|
||||||
|
import com.xeiam.xchange.ExchangeSpecification;
|
||||||
|
import com.xeiam.xchange.currency.CurrencyPair;
|
||||||
|
import com.xeiam.xchange.dto.Order.OrderType;
|
||||||
|
import com.xeiam.xchange.dto.marketdata.Ticker;
|
||||||
|
import com.xeiam.xchange.dto.trade.LimitOrder;
|
||||||
|
import com.xeiam.xchange.dto.trade.OpenOrders;
|
||||||
|
import com.xeiam.xchange.exceptions.ExchangeException;
|
||||||
|
import com.xeiam.xchange.service.polling.account.PollingAccountService;
|
||||||
|
import com.xeiam.xchange.service.polling.marketdata.PollingMarketDataService;
|
||||||
|
import com.xeiam.xchange.service.polling.trade.PollingTradeService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public abstract class XChangeExchange implements IExchangeAdvanced, IRateSource {
|
||||||
|
|
||||||
|
private static final long cacheRefreshSeconds = 30;
|
||||||
|
private static final Cache<String, BigDecimal> balanceCache = createCache();
|
||||||
|
private static final Cache<String, BigDecimal> rateCache = createCache();
|
||||||
|
|
||||||
|
private static Cache<String, BigDecimal> createCache() {
|
||||||
|
return CacheBuilder
|
||||||
|
.newBuilder()
|
||||||
|
.expireAfterWrite(cacheRefreshSeconds, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Exchange exchange;
|
||||||
|
private final String name;
|
||||||
|
private final Logger log;
|
||||||
|
|
||||||
|
public XChangeExchange(ExchangeSpecification specification) {
|
||||||
|
exchange = ExchangeFactory.INSTANCE.createExchange(specification);
|
||||||
|
name = exchange.getExchangeSpecification().getExchangeName();
|
||||||
|
log = LoggerFactory.getLogger("batm.master." + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean isWithdrawSuccessful(String result);
|
||||||
|
protected abstract double getAllowedCallsPerSecond();
|
||||||
|
|
||||||
|
// exchanges that use non standard currency codes can override this
|
||||||
|
protected String mapCurrency(String currency) {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkSupportedCrypto(String currency) {
|
||||||
|
currency = mapCurrency(currency);
|
||||||
|
if (!getCryptoCurrencies().contains(currency)) {
|
||||||
|
log.debug("{} exchange doesn't support cryptocurrency '{}'", name, currency);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkSupportedFiat(String currency) {
|
||||||
|
currency = mapCurrency(currency);
|
||||||
|
if (!getCryptoCurrencies().contains(currency)) {
|
||||||
|
log.debug("{} exchange doesn't support fiat currency '{}'", name, currency);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) {
|
||||||
|
String key = getKey(cryptoCurrency, fiatCurrency);
|
||||||
|
try {
|
||||||
|
BigDecimal result = rateCache.get(key, new RateCaller(key));
|
||||||
|
log.debug("{} exchange rate request: {} = {}", name, key, result);
|
||||||
|
return result;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
log.error("{} exchange rate request: {}", name, key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getKey(String cryptoCurrency, String fiatCurrency) {
|
||||||
|
return String.format("%s:%s", cryptoCurrency, fiatCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getKeyParts(String key) {
|
||||||
|
return key.split(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getCryptoBalance(String cryptoCurrency) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
return cryptoCurrency == null ? BigDecimal.ZERO : getBalance(cryptoCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getFiatBalance(String fiatCurrency) {
|
||||||
|
fiatCurrency = checkSupportedFiat(fiatCurrency);
|
||||||
|
return fiatCurrency == null ? BigDecimal.ZERO : getBalance(fiatCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal getBalance(String currency) {
|
||||||
|
try {
|
||||||
|
BigDecimal balance = balanceCache.get(currency, new BalanceCaller(currency));
|
||||||
|
log.debug("{} exchange balance request: {} = {}", name, currency, balance);
|
||||||
|
return balance;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
log.error("{} exchange balance request: {}", name, currency, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency, String description) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
if (cryptoCurrency == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("{} exchange withdrawing {} {} to {}", name, amount, cryptoCurrency, destinationAddress);
|
||||||
|
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
try {
|
||||||
|
String result = accountService.withdrawFunds(cryptoCurrency, amount, destinationAddress);
|
||||||
|
if (isWithdrawSuccessful(result)) {
|
||||||
|
log.debug("{} exchange withdrawal completed with result: {}", name, result);
|
||||||
|
return "success";
|
||||||
|
} else {
|
||||||
|
log.error("{} exchange withdrawal failed with result: '{}'", name, result);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("{} exchange withdrawal failed", name, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String purchaseCoins(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrencyToUse = checkSupportedFiat(fiatCurrencyToUse);
|
||||||
|
if (cryptoCurrency == null || fiatCurrencyToUse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
PollingMarketDataService marketService = exchange.getPollingMarketDataService();
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("AccountInfo as String: {}", accountService.getAccountInfo());
|
||||||
|
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
||||||
|
|
||||||
|
Ticker ticker = marketService.getTicker(currencyPair);
|
||||||
|
|
||||||
|
LimitOrder order = new LimitOrder.Builder(OrderType.BID, currencyPair)
|
||||||
|
.limitPrice(ticker.getAsk())
|
||||||
|
.tradableAmount(amount)
|
||||||
|
.build();
|
||||||
|
log.debug("marketOrder = {}", order);
|
||||||
|
|
||||||
|
String orderId = tradeService.placeLimitOrder(order);
|
||||||
|
log.debug("orderId = {} {}", orderId, order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get open orders
|
||||||
|
log.debug("Open orders:");
|
||||||
|
boolean orderProcessed = false;
|
||||||
|
int numberOfChecks = 0;
|
||||||
|
while (!orderProcessed && numberOfChecks < 10) {
|
||||||
|
boolean orderFound = false;
|
||||||
|
OpenOrders openOrders = tradeService.getOpenOrders();
|
||||||
|
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
||||||
|
log.debug("openOrder = {}", openOrder);
|
||||||
|
if (orderId.equals(openOrder.getId())) {
|
||||||
|
orderFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (orderFound) {
|
||||||
|
log.debug("Waiting for order to be processed.");
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000); //don't get your ip address banned
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
orderProcessed = true;
|
||||||
|
}
|
||||||
|
numberOfChecks++;
|
||||||
|
}
|
||||||
|
if (orderProcessed) {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(String.format("{} exchange purchase coins failed", name), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ITask createPurchaseCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrencyToUse = checkSupportedFiat(fiatCurrencyToUse);
|
||||||
|
if (cryptoCurrency == null || fiatCurrencyToUse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new PurchaseCoinsTask(amount, cryptoCurrency, fiatCurrencyToUse, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDepositAddress(String cryptoCurrency) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
if (cryptoCurrency == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
try {
|
||||||
|
return accountService.requestDepositAddress(cryptoCurrency);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sellCoins(BigDecimal cryptoAmount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrencyToUse = checkSupportedFiat(fiatCurrencyToUse);
|
||||||
|
if (cryptoCurrency == null || fiatCurrencyToUse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Calling {} exchange (sell {} {})", name, cryptoAmount, cryptoCurrency);
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("AccountInfo as String: {}", accountService.getAccountInfo());
|
||||||
|
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
||||||
|
|
||||||
|
MarketOrder order = new MarketOrder(OrderType.ASK, cryptoAmount, currencyPair);
|
||||||
|
log.debug("marketOrder = {}", order);
|
||||||
|
|
||||||
|
String orderId = tradeService.placeMarketOrder(order);
|
||||||
|
log.debug("orderId = {} {}", orderId, order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get open orders
|
||||||
|
log.debug("Open orders:");
|
||||||
|
boolean orderProcessed = false;
|
||||||
|
int numberOfChecks = 0;
|
||||||
|
while (!orderProcessed && numberOfChecks < 10) {
|
||||||
|
boolean orderFound = false;
|
||||||
|
OpenOrders openOrders = tradeService.getOpenOrders();
|
||||||
|
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
||||||
|
log.debug("openOrder = {}", openOrder);
|
||||||
|
if (orderId.equals(openOrder.getId())) {
|
||||||
|
orderFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (orderFound) {
|
||||||
|
log.debug("Waiting for order to be processed.");
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000); //don't get your ip address banned
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
orderProcessed = true;
|
||||||
|
}
|
||||||
|
numberOfChecks++;
|
||||||
|
}
|
||||||
|
if (orderProcessed) {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("{} exchange sell coins failed", name, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ITask createSellCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrencyToUse = checkSupportedFiat(fiatCurrencyToUse);
|
||||||
|
if (cryptoCurrency == null || fiatCurrencyToUse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new SellCoinsTask(amount, cryptoCurrency, fiatCurrencyToUse, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getExchangeRateForBuy(String cryptoCurrency, String fiatCurrency) {
|
||||||
|
BigDecimal result = calculateBuyPrice(cryptoCurrency, fiatCurrency, BigDecimal.TEN);
|
||||||
|
if (result != null) {
|
||||||
|
return result.divide(BigDecimal.TEN, 2, BigDecimal.ROUND_UP);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getExchangeRateForSell(String cryptoCurrency, String fiatCurrency) {
|
||||||
|
BigDecimal result = calculateSellPrice(cryptoCurrency, fiatCurrency, BigDecimal.TEN);
|
||||||
|
if (result != null) {
|
||||||
|
return result.divide(BigDecimal.TEN, 2, BigDecimal.ROUND_DOWN);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal calculateBuyPrice(String cryptoCurrency, String fiatCurrency, BigDecimal cryptoAmount) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrency = checkSupportedFiat(fiatCurrency);
|
||||||
|
if (cryptoCurrency == null || fiatCurrency == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimiter.acquire();
|
||||||
|
PollingMarketDataService marketDataService = exchange.getPollingMarketDataService();
|
||||||
|
try {
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrency);
|
||||||
|
OrderBook orderBook = marketDataService.getOrderBook(currencyPair);
|
||||||
|
List<LimitOrder> asks = orderBook.getAsks();
|
||||||
|
BigDecimal targetAmount = cryptoAmount;
|
||||||
|
BigDecimal asksTotal = BigDecimal.ZERO;
|
||||||
|
BigDecimal tradableLimit = null;
|
||||||
|
Collections.sort(asks, new Comparator<LimitOrder>() {
|
||||||
|
@Override
|
||||||
|
public int compare(LimitOrder lhs, LimitOrder rhs) {
|
||||||
|
return lhs.getLimitPrice().compareTo(rhs.getLimitPrice());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// log.debug("Selected asks:");
|
||||||
|
for (LimitOrder ask : asks) {
|
||||||
|
// log.debug("ask = " + ask);
|
||||||
|
asksTotal = asksTotal.add(ask.getTradableAmount());
|
||||||
|
if (targetAmount.compareTo(asksTotal) <= 0) {
|
||||||
|
tradableLimit = ask.getLimitPrice();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tradableLimit != null) {
|
||||||
|
log.debug("Called {} exchange for BUY rate: {}:{} = {}", name, cryptoCurrency, fiatCurrency, tradableLimit);
|
||||||
|
return tradableLimit.multiply(cryptoAmount);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("{} exchange failed to calculate buy price", name, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal calculateSellPrice(String cryptoCurrency, String fiatCurrency, BigDecimal cryptoAmount) {
|
||||||
|
cryptoCurrency = checkSupportedCrypto(cryptoCurrency);
|
||||||
|
fiatCurrency = checkSupportedFiat(fiatCurrency);
|
||||||
|
if (cryptoCurrency == null || fiatCurrency == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimiter.acquire();
|
||||||
|
PollingMarketDataService marketDataService = exchange.getPollingMarketDataService();
|
||||||
|
try {
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrency);
|
||||||
|
|
||||||
|
OrderBook orderBook = marketDataService.getOrderBook(currencyPair);
|
||||||
|
List<LimitOrder> bids = orderBook.getBids();
|
||||||
|
|
||||||
|
BigDecimal targetAmount = cryptoAmount;
|
||||||
|
BigDecimal bidsTotal = BigDecimal.ZERO;
|
||||||
|
BigDecimal tradableLimit = null;
|
||||||
|
|
||||||
|
Collections.sort(bids, new Comparator<LimitOrder>() {
|
||||||
|
@Override
|
||||||
|
public int compare(LimitOrder lhs, LimitOrder rhs) {
|
||||||
|
return rhs.getLimitPrice().compareTo(lhs.getLimitPrice());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (LimitOrder bid : bids) {
|
||||||
|
bidsTotal = bidsTotal.add(bid.getTradableAmount());
|
||||||
|
if (targetAmount.compareTo(bidsTotal) <= 0) {
|
||||||
|
tradableLimit = bid.getLimitPrice();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tradableLimit != null) {
|
||||||
|
log.debug("Called {} exchange for SELL rate: {}:{} = {}", name, cryptoCurrency, fiatCurrency, tradableLimit);
|
||||||
|
return tradableLimit.multiply(cryptoAmount);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("{} exchange failed to calculate sell price", name, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class RateCaller implements Callable<BigDecimal> {
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
RateCaller(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal call() throws Exception {
|
||||||
|
String[] keyParts = getKeyParts(key);
|
||||||
|
return exchange.getPollingMarketDataService()
|
||||||
|
.getTicker(new CurrencyPair(keyParts[0], keyParts[1]))
|
||||||
|
.getLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BalanceCaller implements Callable<BigDecimal> {
|
||||||
|
private final String currency;
|
||||||
|
|
||||||
|
BalanceCaller(String currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal call() throws Exception {
|
||||||
|
return exchange.getPollingAccountService()
|
||||||
|
.getAccountInfo()
|
||||||
|
.getWallet(currency)
|
||||||
|
.getBalance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PurchaseCoinsTask implements ITask {
|
||||||
|
private long MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH = 5 * 60 * 60 * 1000; //5 hours
|
||||||
|
|
||||||
|
private BigDecimal amount;
|
||||||
|
private String cryptoCurrency;
|
||||||
|
private String fiatCurrencyToUse;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private String orderId;
|
||||||
|
private String result;
|
||||||
|
private boolean finished;
|
||||||
|
|
||||||
|
PurchaseCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
this.amount = amount;
|
||||||
|
this.cryptoCurrency = cryptoCurrency;
|
||||||
|
this.fiatCurrencyToUse = fiatCurrencyToUse;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
log.debug("{} exchange purchase {} {}", name, amount, cryptoCurrency);
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
PollingMarketDataService marketService = exchange.getPollingMarketDataService();
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("AccountInfo as String: {}", accountService.getAccountInfo());
|
||||||
|
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
||||||
|
|
||||||
|
Ticker ticker = marketService.getTicker(currencyPair);
|
||||||
|
LimitOrder order = new LimitOrder.Builder(OrderType.BID, currencyPair)
|
||||||
|
.limitPrice(ticker.getAsk())
|
||||||
|
.tradableAmount(amount)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
log.debug("limitOrder = {}", order);
|
||||||
|
|
||||||
|
orderId = tradeService.placeLimitOrder(order);
|
||||||
|
log.debug("orderId = {} {}", orderId, order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error("{} exchange purchase task failed", name, e);
|
||||||
|
}
|
||||||
|
return (orderId != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDoStep() {
|
||||||
|
if (orderId == null) {
|
||||||
|
log.debug("Giving up on waiting for trade to complete. Because it did not happen");
|
||||||
|
finished = true;
|
||||||
|
result = "Skipped";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
// get open orders
|
||||||
|
boolean orderProcessed = false;
|
||||||
|
long checkTillTime = System.currentTimeMillis() + MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH;
|
||||||
|
if (System.currentTimeMillis() > checkTillTime) {
|
||||||
|
log.debug("Giving up on waiting for trade {} to complete", orderId);
|
||||||
|
finished = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Open orders:");
|
||||||
|
boolean orderFound = false;
|
||||||
|
try {
|
||||||
|
OpenOrders openOrders = tradeService.getOpenOrders();
|
||||||
|
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
||||||
|
log.debug("openOrder = {}", openOrder);
|
||||||
|
if (orderId.equals(openOrder.getId())) {
|
||||||
|
orderFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderFound) {
|
||||||
|
log.debug("Waiting for order to be processed.");
|
||||||
|
} else {
|
||||||
|
orderProcessed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderProcessed) {
|
||||||
|
result = orderId;
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFailed() {
|
||||||
|
return finished && result == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
log.debug("Purchase task finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getShortestTimeForNexStepInvocation() {
|
||||||
|
return 5 * 1000; //it doesn't make sense to run step sooner than after 5 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SellCoinsTask implements ITask {
|
||||||
|
private long MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH = 5 * 60 * 60 * 1000; //5 hours
|
||||||
|
|
||||||
|
private BigDecimal cryptoAmount;
|
||||||
|
private String cryptoCurrency;
|
||||||
|
private String fiatCurrencyToUse;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private String orderId;
|
||||||
|
private String result;
|
||||||
|
private boolean finished;
|
||||||
|
|
||||||
|
SellCoinsTask(BigDecimal cryptoAmount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
||||||
|
this.cryptoAmount = cryptoAmount;
|
||||||
|
this.cryptoCurrency = cryptoCurrency;
|
||||||
|
this.fiatCurrencyToUse = fiatCurrencyToUse;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
log.info("Calling {} exchange (sell {} {})", name, cryptoAmount, cryptoCurrency);
|
||||||
|
PollingAccountService accountService = exchange.getPollingAccountService();
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("AccountInfo as String: {}", accountService.getAccountInfo());
|
||||||
|
|
||||||
|
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
||||||
|
|
||||||
|
MarketOrder order = new MarketOrder(OrderType.ASK, cryptoAmount, currencyPair);
|
||||||
|
log.debug("marketOrder = {}", order);
|
||||||
|
|
||||||
|
orderId = tradeService.placeMarketOrder(order);
|
||||||
|
log.debug("orderId = {} {}", orderId, order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error("{} exchange sell coins task failed", name, e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return (orderId != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDoStep() {
|
||||||
|
if (orderId == null) {
|
||||||
|
log.debug("Giving up on waiting for trade to complete. Because it did not happen");
|
||||||
|
finished = true;
|
||||||
|
result = "Skipped";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PollingTradeService tradeService = exchange.getPollingTradeService();
|
||||||
|
// get open orders
|
||||||
|
boolean orderProcessed = false;
|
||||||
|
long checkTillTime = System.currentTimeMillis() + MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH;
|
||||||
|
if (System.currentTimeMillis() > checkTillTime) {
|
||||||
|
log.debug("Giving up on waiting for trade {} to complete", orderId);
|
||||||
|
finished = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Open orders:");
|
||||||
|
boolean orderFound = false;
|
||||||
|
try {
|
||||||
|
OpenOrders openOrders = tradeService.getOpenOrders();
|
||||||
|
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
||||||
|
log.debug("openOrder = {}", openOrder);
|
||||||
|
if (orderId.equals(openOrder.getId())) {
|
||||||
|
orderFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderFound) {
|
||||||
|
log.debug("Waiting for order to be processed.");
|
||||||
|
}else{
|
||||||
|
orderProcessed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderProcessed) {
|
||||||
|
result = orderId;
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFailed() {
|
||||||
|
return finished && result == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
log.debug("Sell task finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getShortestTimeForNexStepInvocation() {
|
||||||
|
return 5 * 1000; //it doesn't make sense to run step sooner than after 5 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,722 +20,54 @@
|
||||||
************************************************************************************/
|
************************************************************************************/
|
||||||
package com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitfinex;
|
package com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitfinex;
|
||||||
|
|
||||||
import java.io.IOException;
|
import com.generalbytes.batm.server.extensions.ICurrencies;
|
||||||
import java.math.BigDecimal;
|
import com.generalbytes.batm.server.extensions.IExchangeAdvanced;
|
||||||
import java.util.*;
|
import com.generalbytes.batm.server.extensions.IRateSource;
|
||||||
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.XChangeExchange;
|
||||||
import com.generalbytes.batm.server.extensions.*;
|
|
||||||
import com.xeiam.xchange.ExchangeException;
|
|
||||||
import com.xeiam.xchange.dto.marketdata.OrderBook;
|
|
||||||
import com.xeiam.xchange.dto.trade.LimitOrder;
|
|
||||||
import com.xeiam.xchange.dto.trade.OpenOrders;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.xeiam.xchange.Exchange;
|
|
||||||
import com.xeiam.xchange.ExchangeFactory;
|
|
||||||
import com.xeiam.xchange.ExchangeSpecification;
|
import com.xeiam.xchange.ExchangeSpecification;
|
||||||
import com.xeiam.xchange.currency.CurrencyPair;
|
|
||||||
import com.xeiam.xchange.dto.Order.OrderType;
|
|
||||||
import com.xeiam.xchange.dto.marketdata.Ticker;
|
|
||||||
import com.xeiam.xchange.dto.trade.MarketOrder;
|
|
||||||
import com.xeiam.xchange.service.polling.PollingAccountService;
|
|
||||||
import com.xeiam.xchange.service.polling.PollingMarketDataService;
|
|
||||||
import com.xeiam.xchange.service.polling.PollingTradeService;
|
|
||||||
import org.slf4j.spi.LocationAwareLogger;
|
|
||||||
|
|
||||||
public class BitfinexExchange implements IExchangeAdvanced, IRateSourceAdvanced {
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger("batm.master.BitfinexExchange");
|
public class BitfinexExchange extends XChangeExchange implements IExchangeAdvanced, IRateSource {
|
||||||
private Exchange exchange = null;
|
|
||||||
private String apiKey;
|
|
||||||
private String apiSecret;
|
|
||||||
|
|
||||||
private static HashMap<String,BigDecimal> rateAmounts = new HashMap<String, BigDecimal>();
|
private static ExchangeSpecification getSpecification(String apiKey, String apiSecret) {
|
||||||
private static HashMap<String,Long> rateTimes = new HashMap<String, Long>();
|
ExchangeSpecification spec = new com.xeiam.xchange.bitfinex.v1.BitfinexExchange().getDefaultExchangeSpecification();
|
||||||
private static final long MAXIMUM_ALLOWED_TIME_OFFSET = 30 * 1000;
|
spec.setApiKey(apiKey);
|
||||||
|
spec.setSecretKey(apiSecret);
|
||||||
private static volatile long lastCall = -1;
|
return spec;
|
||||||
public static final int CALL_PERIOD_MINIMUM = 2100; //cannot be called more often than once in 2 seconds
|
}
|
||||||
|
|
||||||
public BitfinexExchange(String apiKey, String apiSecret) {
|
public BitfinexExchange(String apiKey, String apiSecret) {
|
||||||
this.apiKey = apiKey;
|
super(getSpecification(apiKey, apiSecret));
|
||||||
this.apiSecret = apiSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized Exchange getExchange() {
|
|
||||||
if (this.exchange == null) {
|
|
||||||
ExchangeSpecification bfxSpec = new com.xeiam.xchange.bitfinex.v1.BitfinexExchange().getDefaultExchangeSpecification();
|
|
||||||
bfxSpec.setApiKey(this.apiKey);
|
|
||||||
bfxSpec.setSecretKey(this.apiSecret);
|
|
||||||
this.exchange = ExchangeFactory.INSTANCE.createExchange(bfxSpec);
|
|
||||||
}
|
|
||||||
return this.exchange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Set<String> getCryptoCurrencies() {
|
public Set<String> getCryptoCurrencies() {
|
||||||
Set<String> cryptoCurrencies = new HashSet<String>();
|
Set<String> cryptoCurrencies = new HashSet<String>();
|
||||||
cryptoCurrencies.add(ICurrencies.BTC);
|
cryptoCurrencies.add(ICurrencies.BTC);
|
||||||
return cryptoCurrencies;
|
return cryptoCurrencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Set<String> getFiatCurrencies() {
|
public Set<String> getFiatCurrencies() {
|
||||||
Set<String> fiatCurrencies = new HashSet<String>();
|
Set<String> fiatCurrencies = new HashSet<String>();
|
||||||
fiatCurrencies.add(ICurrencies.USD);
|
fiatCurrencies.add(ICurrencies.USD);
|
||||||
return fiatCurrencies;
|
return fiatCurrencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getPreferredFiatCurrency() {
|
public String getPreferredFiatCurrency() {
|
||||||
return ICurrencies.USD;
|
return ICurrencies.USD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) {
|
protected boolean isWithdrawSuccessful(String result) {
|
||||||
String key = cryptoCurrency +"_" + fiatCurrency;
|
return "success".equalsIgnoreCase(result);
|
||||||
synchronized (rateAmounts) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
BigDecimal amount = rateAmounts.get(key);
|
|
||||||
if (amount == null) {
|
|
||||||
BigDecimal result = getExchangeRateLastSync(cryptoCurrency, fiatCurrency);
|
|
||||||
log.debug("Called bitfinex exchange for rate: " + key + " = " + result);
|
|
||||||
rateAmounts.put(key,result);
|
|
||||||
rateTimes.put(key,now+MAXIMUM_ALLOWED_TIME_OFFSET);
|
|
||||||
return result;
|
|
||||||
}else {
|
|
||||||
Long expirationTime = rateTimes.get(key);
|
|
||||||
if (expirationTime > now) {
|
|
||||||
return rateAmounts.get(key);
|
|
||||||
}else{
|
|
||||||
//do the job;
|
|
||||||
BigDecimal result = getExchangeRateLastSync(cryptoCurrency, fiatCurrency);
|
|
||||||
log.debug("Called bitfinex exchange for rate: " + key + " = " + result);
|
|
||||||
rateAmounts.put(key,result);
|
|
||||||
rateTimes.put(key,now+MAXIMUM_ALLOWED_TIME_OFFSET);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal getExchangeRateLastSync(String cryptoCurrency, String cashCurrency) {
|
|
||||||
PollingMarketDataService marketDataService = getExchange().getPollingMarketDataService();
|
|
||||||
try {
|
|
||||||
Ticker ticker = marketDataService.getTicker(new CurrencyPair(cryptoCurrency,cashCurrency));
|
|
||||||
return ticker.getLast();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCryptoBalance(String cryptoCurrency) {
|
|
||||||
// [TODO] Can be extended to support LTC and DRK (and other currencies supported by BFX)
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
log.debug("Calling Bitfinex exchange (getBalance)");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return getExchange().getPollingAccountService().getAccountInfo().getBalance(cryptoCurrency);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (getBalance) failed with message: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getFiatBalance(String fiatCurrency) {
|
|
||||||
if (!ICurrencies.USD.equalsIgnoreCase(fiatCurrency)) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
log.debug("Calling Bitfinex exchange (getBalance)");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return getExchange().getPollingAccountService().getAccountInfo().getBalance(fiatCurrency);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (getBalance) failed with message: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency, String description) {
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
log.error("Bitfinex supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Calling bitfinex exchange (withdrawal destination: " + destinationAddress + " amount: " + amount + " " + cryptoCurrency + ")");
|
|
||||||
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
try {
|
|
||||||
String result = accountService.withdrawFunds(cryptoCurrency, amount, destinationAddress);
|
|
||||||
if (result == null) {
|
|
||||||
log.warn("Bitfinex exchange (withdrawFunds) failed with null");
|
|
||||||
return null;
|
|
||||||
}else if ("success".equalsIgnoreCase(result)){
|
|
||||||
log.warn("Bitfinex exchange (withdrawFunds) finished successfully");
|
|
||||||
return "success";
|
|
||||||
}else{
|
|
||||||
log.warn("Bitfinex exchange (withdrawFunds) failed with message: " + result);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (withdrawFunds) failed with message: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String purchaseCoins(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
log.error("Bitfinex implementation supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!ICurrencies.USD.equalsIgnoreCase(fiatCurrencyToUse)) {
|
|
||||||
log.error("Bitfinex supports only " + ICurrencies.USD );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Calling Bitfinex exchange (purchase " + amount + " " + cryptoCurrency + ")");
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("AccountInfo as String: " + accountService.getAccountInfo().toString());
|
|
||||||
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
|
||||||
|
|
||||||
MarketOrder order = new MarketOrder(OrderType.BID, amount, currencyPair);
|
|
||||||
log.debug("marketOrder = " + order);
|
|
||||||
|
|
||||||
String orderId = tradeService.placeMarketOrder(order);
|
|
||||||
log.debug("orderId = " + orderId + " " + order);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get open orders
|
|
||||||
log.debug("Open orders:");
|
|
||||||
boolean orderProcessed = false;
|
|
||||||
int numberOfChecks = 0;
|
|
||||||
while (!orderProcessed && numberOfChecks < 10) {
|
|
||||||
boolean orderFound = false;
|
|
||||||
OpenOrders openOrders = tradeService.getOpenOrders();
|
|
||||||
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
|
||||||
log.debug("openOrder = " + openOrder);
|
|
||||||
if (orderId.equals(openOrder.getId())) {
|
|
||||||
orderFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (orderFound) {
|
|
||||||
log.debug("Waiting for order to be processed.");
|
|
||||||
try {
|
|
||||||
Thread.sleep(3000); //don't get your ip address banned
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
orderProcessed = true;
|
|
||||||
}
|
|
||||||
numberOfChecks++;
|
|
||||||
}
|
|
||||||
if (orderProcessed) {
|
|
||||||
return orderId;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (purchaseCoins) failed with message: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITask createPurchaseCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
protected double getAllowedCallsPerSecond() {
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
return 0.5;
|
||||||
log.error("Bitfinex implementation supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!ICurrencies.USD.equalsIgnoreCase(fiatCurrencyToUse)) {
|
|
||||||
log.error("Bitfinex supports only " + ICurrencies.USD );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new PurchaseCoinsTask(amount,cryptoCurrency,fiatCurrencyToUse,description);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public String getDepositAddress(String cryptoCurrency) {
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
log.error("Bitfinex implementation supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
try {
|
|
||||||
return accountService.requestDepositAddress(cryptoCurrency);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sellCoins(BigDecimal cryptoAmount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
log.error("Bitfinex implementation supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!ICurrencies.USD.equalsIgnoreCase(fiatCurrencyToUse)) {
|
|
||||||
log.error("Bitfinex supports only " + ICurrencies.USD );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Calling Bitfinex exchange (sell " + cryptoAmount + " " + cryptoCurrency + ")");
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("AccountInfo as String: " + accountService.getAccountInfo().toString());
|
|
||||||
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
|
||||||
|
|
||||||
MarketOrder order = new MarketOrder(OrderType.ASK, cryptoAmount, currencyPair);
|
|
||||||
log.debug("marketOrder = " + order);
|
|
||||||
|
|
||||||
String orderId = tradeService.placeMarketOrder(order);
|
|
||||||
log.debug("orderId = " + orderId + " " + order);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get open orders
|
|
||||||
log.debug("Open orders:");
|
|
||||||
boolean orderProcessed = false;
|
|
||||||
int numberOfChecks = 0;
|
|
||||||
while (!orderProcessed && numberOfChecks < 10) {
|
|
||||||
boolean orderFound = false;
|
|
||||||
OpenOrders openOrders = tradeService.getOpenOrders();
|
|
||||||
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
|
||||||
log.debug("openOrder = " + openOrder);
|
|
||||||
if (orderId.equals(openOrder.getId())) {
|
|
||||||
orderFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (orderFound) {
|
|
||||||
log.debug("Waiting for order to be processed.");
|
|
||||||
try {
|
|
||||||
Thread.sleep(3000); //don't get your ip address banned
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
orderProcessed = true;
|
|
||||||
}
|
|
||||||
numberOfChecks++;
|
|
||||||
}
|
|
||||||
if (orderProcessed) {
|
|
||||||
return orderId;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (sellCoins) failed with message: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ITask createSellCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
|
||||||
if (!ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) {
|
|
||||||
log.error("Bitfinex implementation supports only " + ICurrencies.BTC);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!ICurrencies.USD.equalsIgnoreCase(fiatCurrencyToUse)) {
|
|
||||||
log.error("Bitfinex supports only " + ICurrencies.USD );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new SellCoinsTask(amount,cryptoCurrency,fiatCurrencyToUse,description);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PurchaseCoinsTask implements ITask {
|
|
||||||
private long MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH = 5 * 60 * 60 * 1000; //5 hours
|
|
||||||
|
|
||||||
private BigDecimal amount;
|
|
||||||
private String cryptoCurrency;
|
|
||||||
private String fiatCurrencyToUse;
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
private String orderId;
|
|
||||||
private String result;
|
|
||||||
private boolean finished;
|
|
||||||
|
|
||||||
PurchaseCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
|
||||||
this.amount = amount;
|
|
||||||
this.cryptoCurrency = cryptoCurrency;
|
|
||||||
this.fiatCurrencyToUse = fiatCurrencyToUse;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
log.info("Calling Bitfinex exchange (purchase " + amount + " " + cryptoCurrency + ")");
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("AccountInfo as String: " + accountService.getAccountInfo().toString());
|
|
||||||
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
|
||||||
|
|
||||||
MarketOrder order = new MarketOrder(OrderType.BID, amount, currencyPair);
|
|
||||||
log.debug("marketOrder = " + order);
|
|
||||||
|
|
||||||
orderId = tradeService.placeMarketOrder(order);
|
|
||||||
log.debug("orderId = " + orderId + " " + order);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (purchaseCoins) failed with message: " + e.getMessage());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return (orderId != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDoStep() {
|
|
||||||
if (orderId == null) {
|
|
||||||
log.debug("Giving up on waiting for trade to complete. Because it did not happen");
|
|
||||||
finished = true;
|
|
||||||
result = "Skipped";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
// get open orders
|
|
||||||
boolean orderProcessed = false;
|
|
||||||
long checkTillTime = System.currentTimeMillis() + MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH;
|
|
||||||
if (System.currentTimeMillis() > checkTillTime) {
|
|
||||||
log.debug("Giving up on waiting for trade " + orderId + " to complete");
|
|
||||||
finished = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Open orders:");
|
|
||||||
boolean orderFound = false;
|
|
||||||
try {
|
|
||||||
OpenOrders openOrders = tradeService.getOpenOrders();
|
|
||||||
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
|
||||||
log.debug("openOrder = " + openOrder);
|
|
||||||
if (orderId.equals(openOrder.getId())) {
|
|
||||||
orderFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderFound) {
|
|
||||||
log.debug("Waiting for order to be processed.");
|
|
||||||
}else{
|
|
||||||
orderProcessed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderProcessed) {
|
|
||||||
result = orderId;
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFailed() {
|
|
||||||
return finished && result == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish() {
|
|
||||||
log.debug("Purchase task finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getShortestTimeForNexStepInvocation() {
|
|
||||||
return 5 * 1000; //it doesn't make sense to run step sooner than after 5 seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SellCoinsTask implements ITask {
|
|
||||||
private long MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH = 5 * 60 * 60 * 1000; //5 hours
|
|
||||||
|
|
||||||
private BigDecimal cryptoAmount;
|
|
||||||
private String cryptoCurrency;
|
|
||||||
private String fiatCurrencyToUse;
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
private String orderId;
|
|
||||||
private String result;
|
|
||||||
private boolean finished;
|
|
||||||
|
|
||||||
SellCoinsTask(BigDecimal cryptoAmount, String cryptoCurrency, String fiatCurrencyToUse, String description) {
|
|
||||||
this.cryptoAmount = cryptoAmount;
|
|
||||||
this.cryptoCurrency = cryptoCurrency;
|
|
||||||
this.fiatCurrencyToUse = fiatCurrencyToUse;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
log.info("Calling Bitfinex exchange (sell " + cryptoAmount + " " + cryptoCurrency + ")");
|
|
||||||
PollingAccountService accountService = getExchange().getPollingAccountService();
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("AccountInfo as String: " + accountService.getAccountInfo().toString());
|
|
||||||
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrencyToUse);
|
|
||||||
|
|
||||||
MarketOrder order = new MarketOrder(OrderType.ASK, cryptoAmount, currencyPair);
|
|
||||||
log.debug("marketOrder = " + order);
|
|
||||||
|
|
||||||
orderId = tradeService.placeMarketOrder(order);
|
|
||||||
log.debug("orderId = " + orderId + " " + order);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000); //give exchange 2 seconds to reflect open order in order book
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("Bitfinex exchange (sellCoins) failed with message: " + e.getMessage());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return (orderId != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDoStep() {
|
|
||||||
if (orderId == null) {
|
|
||||||
log.debug("Giving up on waiting for trade to complete. Because it did not happen");
|
|
||||||
finished = true;
|
|
||||||
result = "Skipped";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PollingTradeService tradeService = getExchange().getPollingTradeService();
|
|
||||||
// get open orders
|
|
||||||
boolean orderProcessed = false;
|
|
||||||
long checkTillTime = System.currentTimeMillis() + MAXIMUM_TIME_TO_WAIT_FOR_ORDER_TO_FINISH;
|
|
||||||
if (System.currentTimeMillis() > checkTillTime) {
|
|
||||||
log.debug("Giving up on waiting for trade " + orderId + " to complete");
|
|
||||||
finished = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Open orders:");
|
|
||||||
boolean orderFound = false;
|
|
||||||
try {
|
|
||||||
OpenOrders openOrders = tradeService.getOpenOrders();
|
|
||||||
for (LimitOrder openOrder : openOrders.getOpenOrders()) {
|
|
||||||
log.debug("openOrder = " + openOrder);
|
|
||||||
if (orderId.equals(openOrder.getId())) {
|
|
||||||
orderFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderFound) {
|
|
||||||
log.debug("Waiting for order to be processed.");
|
|
||||||
}else{
|
|
||||||
orderProcessed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderProcessed) {
|
|
||||||
result = orderId;
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFailed() {
|
|
||||||
return finished && result == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish() {
|
|
||||||
log.debug("Sell task finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getShortestTimeForNexStepInvocation() {
|
|
||||||
return 5 * 1000; //it doesn't make sense to run step sooner than after 5 seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal getMeasureCryptoAmount() {
|
|
||||||
return new BigDecimal(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal getExchangeRateForBuy(String cryptoCurrency, String fiatCurrency) {
|
|
||||||
BigDecimal result = calculateBuyPrice(cryptoCurrency, fiatCurrency, getMeasureCryptoAmount());
|
|
||||||
if (result != null) {
|
|
||||||
return result.divide(getMeasureCryptoAmount(), 2, BigDecimal.ROUND_UP);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal getExchangeRateForSell(String cryptoCurrency, String fiatCurrency) {
|
|
||||||
BigDecimal result = calculateSellPrice(cryptoCurrency, fiatCurrency, getMeasureCryptoAmount());
|
|
||||||
if (result != null) {
|
|
||||||
return result.divide(getMeasureCryptoAmount(), 2, BigDecimal.ROUND_DOWN);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal calculateBuyPrice(String cryptoCurrency, String fiatCurrency, BigDecimal cryptoAmount) {
|
|
||||||
waitForPossibleCall();
|
|
||||||
PollingMarketDataService marketDataService = getExchange().getPollingMarketDataService();
|
|
||||||
try {
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrency);
|
|
||||||
OrderBook orderBook = marketDataService.getOrderBook(currencyPair);
|
|
||||||
List<LimitOrder> asks = orderBook.getAsks();
|
|
||||||
BigDecimal targetAmount = cryptoAmount;
|
|
||||||
BigDecimal asksTotal = BigDecimal.ZERO;
|
|
||||||
BigDecimal tradableLimit = null;
|
|
||||||
Collections.sort(asks, new Comparator<LimitOrder>() {
|
|
||||||
@Override
|
|
||||||
public int compare(LimitOrder lhs, LimitOrder rhs) {
|
|
||||||
return lhs.getLimitPrice().compareTo(rhs.getLimitPrice());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// log.debug("Selected asks:");
|
|
||||||
for (LimitOrder ask : asks) {
|
|
||||||
// log.debug("ask = " + ask);
|
|
||||||
asksTotal = asksTotal.add(ask.getTradableAmount());
|
|
||||||
if (targetAmount.compareTo(asksTotal) <= 0) {
|
|
||||||
tradableLimit = ask.getLimitPrice();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tradableLimit != null) {
|
|
||||||
log.debug("Called Bitfinex exchange for BUY rate: " + cryptoCurrency + fiatCurrency + " = " + tradableLimit);
|
|
||||||
return tradableLimit.multiply(cryptoAmount);
|
|
||||||
}
|
|
||||||
} catch (ExchangeException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitForPossibleCall() {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (lastCall != -1) {
|
|
||||||
long diff = now - lastCall;
|
|
||||||
if (diff < CALL_PERIOD_MINIMUM) {
|
|
||||||
try {
|
|
||||||
long sleeping = CALL_PERIOD_MINIMUM - diff;
|
|
||||||
Thread.sleep(sleeping);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastCall = now;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal calculateSellPrice(String cryptoCurrency, String fiatCurrency, BigDecimal cryptoAmount) {
|
|
||||||
waitForPossibleCall();
|
|
||||||
PollingMarketDataService marketDataService = getExchange().getPollingMarketDataService();
|
|
||||||
try {
|
|
||||||
CurrencyPair currencyPair = new CurrencyPair(cryptoCurrency, fiatCurrency);
|
|
||||||
|
|
||||||
OrderBook orderBook = marketDataService.getOrderBook(currencyPair);
|
|
||||||
List<LimitOrder> bids = orderBook.getBids();
|
|
||||||
|
|
||||||
BigDecimal targetAmount = cryptoAmount;
|
|
||||||
BigDecimal bidsTotal = BigDecimal.ZERO;
|
|
||||||
BigDecimal tradableLimit = null;
|
|
||||||
|
|
||||||
Collections.sort(bids, new Comparator<LimitOrder>() {
|
|
||||||
@Override
|
|
||||||
public int compare(LimitOrder lhs, LimitOrder rhs) {
|
|
||||||
return rhs.getLimitPrice().compareTo(lhs.getLimitPrice());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (LimitOrder bid : bids) {
|
|
||||||
bidsTotal = bidsTotal.add(bid.getTradableAmount());
|
|
||||||
if (targetAmount.compareTo(bidsTotal) <= 0) {
|
|
||||||
tradableLimit = bid.getLimitPrice();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tradableLimit != null) {
|
|
||||||
log.debug("Called Bitfinex exchange for SELL rate: " + cryptoCurrency + fiatCurrency + " = " + tradableLimit);
|
|
||||||
return tradableLimit.multiply(cryptoAmount);
|
|
||||||
}
|
|
||||||
} catch (ExchangeException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*************************************************************************************
|
||||||
|
* Copyright (C) 2014 GENERAL BYTES s.r.o. All rights reserved.
|
||||||
|
* <p/>
|
||||||
|
* This software may be distributed and modified under the terms of the GNU
|
||||||
|
* General Public License version 2 (GPL2) as published by the Free Software
|
||||||
|
* Foundation and appearing in the file GPL2.TXT included in the packaging of
|
||||||
|
* this file. Please note that GPL2 Section 2[b] requires that all works based
|
||||||
|
* on this software must also be made publicly available under the terms of
|
||||||
|
* the GPL2 ("Copyleft").
|
||||||
|
* <p/>
|
||||||
|
* Contact information
|
||||||
|
* -------------------
|
||||||
|
* <p/>
|
||||||
|
* GENERAL BYTES s.r.o.
|
||||||
|
* Web : http://www.generalbytes.com
|
||||||
|
* <p/>
|
||||||
|
* Other information:
|
||||||
|
* <p/>
|
||||||
|
* This implementation was created in cooperation with Orillia BVBA
|
||||||
|
************************************************************************************/
|
||||||
|
package com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.itbit;
|
||||||
|
|
||||||
|
import com.generalbytes.batm.server.extensions.ICurrencies;
|
||||||
|
import com.generalbytes.batm.server.extensions.IExchangeAdvanced;
|
||||||
|
import com.generalbytes.batm.server.extensions.IRateSource;
|
||||||
|
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.XChangeExchange;
|
||||||
|
import com.xeiam.xchange.ExchangeSpecification;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ItBitExchange extends XChangeExchange implements IExchangeAdvanced, IRateSource {
|
||||||
|
|
||||||
|
private static ExchangeSpecification getDefaultSpecification() {
|
||||||
|
return new com.xeiam.xchange.itbit.v1.ItBitExchange().getDefaultExchangeSpecification();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExchangeSpecification getSpecification(String userId, String walletId, String clientKey, String clientSecret) {
|
||||||
|
ExchangeSpecification spec = getDefaultSpecification();
|
||||||
|
spec.setExchangeSpecificParametersItem("userId", userId);
|
||||||
|
spec.setExchangeSpecificParametersItem("walletId", walletId);
|
||||||
|
spec.setApiKey(clientKey);
|
||||||
|
spec.setSecretKey(clientSecret);
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItBitExchange() {
|
||||||
|
super(getDefaultSpecification());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItBitExchange(String userId, String walletId, String clientKey, String clientSecret) {
|
||||||
|
super(getSpecification(userId, walletId, clientKey, clientSecret));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getCryptoCurrencies() {
|
||||||
|
Set<String> cryptoCurrencies = new HashSet<String>();
|
||||||
|
cryptoCurrencies.add(ICurrencies.XBT);
|
||||||
|
return cryptoCurrencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getFiatCurrencies() {
|
||||||
|
Set<String> fiatCurrencies = new HashSet<String>();
|
||||||
|
fiatCurrencies.add(ICurrencies.USD);
|
||||||
|
fiatCurrencies.add(ICurrencies.EUR);
|
||||||
|
return fiatCurrencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPreferredFiatCurrency() {
|
||||||
|
return ICurrencies.USD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isWithdrawSuccessful(String result) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double getAllowedCallsPerSecond() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapCurrency(String currency) {
|
||||||
|
return currency.equals(ICurrencies.BTC) ? ICurrencies.XBT : currency;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue