com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.XChangeExchange.java Source code

Java tutorial

Introduction

Here is the source code for com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.XChangeExchange.java

Source

/*************************************************************************************
 * Copyright (C) 2015-2016 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 Sumbits http://www.getsumbits.com/
 ************************************************************************************/
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.google.common.util.concurrent.RateLimiter;
import org.knowm.xchange.Exchange;
import org.knowm.xchange.ExchangeFactory;
import org.knowm.xchange.ExchangeSpecification;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.account.AccountInfo;
import org.knowm.xchange.dto.account.Wallet;
import org.knowm.xchange.dto.marketdata.OrderBook;
import org.knowm.xchange.dto.marketdata.Ticker;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.dto.trade.MarketOrder;
import org.knowm.xchange.dto.trade.OpenOrders;
import org.knowm.xchange.exceptions.ExchangeException;
import org.knowm.xchange.exceptions.NotAvailableFromExchangeException;
import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException;

import org.knowm.xchange.service.account.AccountService;
import org.knowm.xchange.service.marketdata.MarketDataService;
import org.knowm.xchange.service.trade.TradeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import si.mazi.rescu.HttpStatusIOException;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public abstract class XChangeExchange implements IExchangeAdvanced, IRateSourceAdvanced {

    private String preferredFiatCurrency;
    private static final long cacheRefreshSeconds = 30;
    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;
    private final RateLimiter rateLimiter;

    public XChangeExchange(ExchangeSpecification specification, String preferredFiatCurrency) {
        exchange = ExchangeFactory.INSTANCE.createExchange(specification);
        name = exchange.getExchangeSpecification().getExchangeName();
        log = LoggerFactory.getLogger("batm.master.exchange." + name);
        rateLimiter = RateLimiter.create(getAllowedCallsPerSecond());
        this.preferredFiatCurrency = preferredFiatCurrency;
    }

    protected abstract boolean isWithdrawSuccessful(String result);

    protected abstract double getAllowedCallsPerSecond();

    private boolean isCryptoCurrencySupported(String currency) {
        if (!getCryptoCurrencies().contains(currency)) {
            log.debug("{} exchange doesn't support cryptocurrency '{}'", name, currency);
            return false;
        }
        return true;
    }

    private boolean isFiatCurrencySupported(String currency) {
        if (!getFiatCurrencies().contains(currency)) {
            log.debug("{} exchange doesn't support fiat currency '{}'", name, currency);
            return false;
        }
        return true;
    }

    class RateCaller implements Callable<BigDecimal> {
        private final String key;

        RateCaller(String key) {
            this.key = key;
        }

        @Override
        public BigDecimal call() throws Exception {
            String[] keyParts = getCacheKeyParts(key);
            String cryptoCurrency = keyParts[0];
            String fiatCurrency = keyParts[1];

            try {
                return exchange.getMarketDataService()
                        .getTicker(new CurrencyPair(
                                translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency),
                                fiatCurrency))
                        .getLast();
            } catch (ExchangeException e) {
                log.error("Error", e);
            } catch (NotAvailableFromExchangeException e) {
                log.error("Error", e);
            } catch (NotYetImplementedForExchangeException e) {
                log.error("Error", e);
            } catch (IOException e) {
                log.error("Error", e);
            }
            return null;
        }
    }

    @Override
    public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) {
        String key = buildCacheKey(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 static String buildCacheKey(String cryptoCurrency, String fiatCurrency) {
        return String.format("%s:%s", cryptoCurrency, fiatCurrency);
    }

    private static String[] getCacheKeyParts(String key) {
        return key.split(":");
    }

    @Override
    public BigDecimal getCryptoBalance(String cryptoCurrency) {
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return BigDecimal.ZERO;
        }
        try {
            AccountInfo accountInfo = exchange.getAccountService().getAccountInfo();
            Wallet wallet = getWallet(accountInfo, cryptoCurrency);
            BigDecimal balance = wallet.getBalance(Currency.getInstance(cryptoCurrency)).getAvailable();
            log.debug("{} exchange balance request: {} = {}", name, cryptoCurrency, balance);
            return balance;
        } catch (IOException e) {
            log.error("Error", e);
            log.error("{} exchange balance request: {}", name, cryptoCurrency, e);
        }
        return null;
    }

    @Override
    public BigDecimal getFiatBalance(String fiatCurrency) {
        if (!isFiatCurrencySupported(fiatCurrency)) {
            return BigDecimal.ZERO;
        }
        try {
            AccountInfo accountInfo = exchange.getAccountService().getAccountInfo();
            Wallet wallet = getWallet(accountInfo, fiatCurrency);
            BigDecimal balance = wallet.getBalance(Currency.getInstance(fiatCurrency)).getAvailable();
            log.debug("{} exchange balance request: {} = {}", name, fiatCurrency, balance);
            return balance;
        } catch (IOException e) {
            log.error("Error", e);
            log.error("{} exchange balance request: {}", name, fiatCurrency, e);
        }
        return null;
    }

    public Wallet getWallet(AccountInfo accountInfo, String currency) {
        return accountInfo.getWallet(translateCryptoCurrencySymbolToExchangeSpecificSymbol(currency));
    }

    public final String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency,
            String description) {
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }

        log.info("{} exchange withdrawing {} {} to {}", name, amount, cryptoCurrency, destinationAddress);

        AccountService accountService = exchange.getAccountService();
        try {
            String result = accountService.withdrawFunds(
                    Currency.getInstance(translateCryptoCurrencySymbolToExchangeSpecificSymbol(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 (HttpStatusIOException e) {
            log.info("{} exchange withdrawal failed; HTTP status: {}, body: {}", name, e.getHttpStatusCode(),
                    e.getHttpBody(), e);
        } catch (IOException e) {
            log.error("{} exchange withdrawal failed", name, e);
        }
        return null;
    }

    public String purchaseCoins(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse,
            String description) {
        if (cryptoCurrency == null || fiatCurrencyToUse == null) {
            return null;
        }
        if (!isFiatCurrencySupported(fiatCurrencyToUse)) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }

        AccountService accountService = exchange.getAccountService();
        MarketDataService marketService = exchange.getMarketDataService();
        TradeService tradeService = exchange.getTradeService();

        try {
            log.debug("AccountInfo as String: {}", accountService.getAccountInfo());

            CurrencyPair currencyPair = new CurrencyPair(
                    translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency), fiatCurrencyToUse);

            Ticker ticker = marketService.getTicker(currencyPair);

            LimitOrder order = new LimitOrder.Builder(Order.OrderType.BID, currencyPair).limitPrice(ticker.getAsk())
                    .originalAmount(amount).build();
            log.debug("limitOrder = {}", 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) {
                log.error("Error", e);
            }

            // 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) {
                        log.error("Error", e);
                    }
                } else {
                    orderProcessed = true;
                }
                numberOfChecks++;
            }
            if (orderProcessed) {
                return orderId;
            }
        } catch (IOException e) {
            log.error(String.format("%s exchange purchase coins failed", name), e);
        }
        return null;
    }

    @Override
    public ITask createPurchaseCoinsTask(BigDecimal amount, String cryptoCurrency, String fiatCurrencyToUse,
            String description) {
        if (cryptoCurrency == null || fiatCurrencyToUse == null) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }
        if (!isFiatCurrencySupported(fiatCurrencyToUse)) {
            return null;
        }
        return new PurchaseCoinsTask(amount, cryptoCurrency, fiatCurrencyToUse, description);
    }

    @Override
    public String getDepositAddress(String cryptoCurrency) {
        if (cryptoCurrency == null) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }

        AccountService accountService = exchange.getAccountService();
        try {
            return accountService.requestDepositAddress(
                    Currency.getInstance(translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency)));
        } catch (IOException e) {
            log.error("Error", e);
        }
        return null;
    }

    @Override
    public String sellCoins(BigDecimal cryptoAmount, String cryptoCurrency, String fiatCurrencyToUse,
            String description) {
        if (cryptoCurrency == null || fiatCurrencyToUse == null) {
            return null;
        }

        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }

        if (!isFiatCurrencySupported(fiatCurrencyToUse)) {
            return null;
        }

        log.info("Calling {} exchange (sell {} {})", name, cryptoAmount, cryptoCurrency);
        AccountService accountService = exchange.getAccountService();
        TradeService tradeService = exchange.getTradeService();

        try {
            log.debug("AccountInfo as String: {}", accountService.getAccountInfo());

            CurrencyPair currencyPair = new CurrencyPair(
                    translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency), fiatCurrencyToUse);

            MarketOrder order = new MarketOrder(Order.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) {
                log.error("Error", e);
            }

            // 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) {
                        log.error("Error", e);
                    }
                } 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) {
        if (cryptoCurrency == null || fiatCurrencyToUse == null) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }
        if (!isFiatCurrencySupported(fiatCurrencyToUse)) {
            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) {
        if (cryptoCurrency == null || fiatCurrency == null) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }
        if (!isFiatCurrencySupported(fiatCurrency)) {
            return null;
        }

        rateLimiter.acquire();
        MarketDataService marketDataService = exchange.getMarketDataService();
        try {
            CurrencyPair currencyPair = new CurrencyPair(
                    translateCryptoCurrencySymbolToExchangeSpecificSymbol(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.getOriginalAmount());
                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) {
        if (cryptoCurrency == null || fiatCurrency == null) {
            return null;
        }
        if (!isCryptoCurrencySupported(cryptoCurrency)) {
            return null;
        }
        if (!isFiatCurrencySupported(fiatCurrency)) {
            return null;
        }

        rateLimiter.acquire();
        MarketDataService marketDataService = exchange.getMarketDataService();
        try {
            CurrencyPair currencyPair = new CurrencyPair(
                    translateCryptoCurrencySymbolToExchangeSpecificSymbol(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.getOriginalAmount());
                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 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);
            AccountService accountService = exchange.getAccountService();
            MarketDataService marketService = exchange.getMarketDataService();
            TradeService tradeService = exchange.getTradeService();

            try {
                log.debug("AccountInfo as String: {}", accountService.getAccountInfo());

                CurrencyPair currencyPair = new CurrencyPair(
                        translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency), fiatCurrencyToUse);

                Ticker ticker = marketService.getTicker(currencyPair);
                LimitOrder order = new LimitOrder.Builder(Order.OrderType.BID, currencyPair)
                        .limitPrice(ticker.getAsk()).originalAmount(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) {
                    log.error("Error", e);
                }
            } catch (IOException e) {
                log.error("Error", e);
                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;
            }
            TradeService tradeService = exchange.getTradeService();
            // 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) {
                log.error("Error", e);
            }

            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);
            AccountService accountService = exchange.getAccountService();
            TradeService tradeService = exchange.getTradeService();

            try {
                log.debug("AccountInfo as String: {}", accountService.getAccountInfo());

                CurrencyPair currencyPair = new CurrencyPair(
                        translateCryptoCurrencySymbolToExchangeSpecificSymbol(cryptoCurrency), fiatCurrencyToUse);

                MarketOrder order = new MarketOrder(Order.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) {
                    log.error("Error", e);
                }
            } catch (IOException e) {
                log.error("Error", e);
                log.error("{} exchange sell coins task failed", name, e);
            } catch (Throwable e) {
                log.error("Error", 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;
            }
            TradeService tradeService = exchange.getTradeService();
            // 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) {
                log.error("Error", e);
            }

            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
        }
    }

    @Override
    public String getPreferredFiatCurrency() {
        return preferredFiatCurrency;
    }

    protected String translateCryptoCurrencySymbolToExchangeSpecificSymbol(String from) {
        return from;
    }
}