コード例 #1
0
 def __init__(self, endpoint, key, config):
     self.config = config
     self.api = QtradeAPI(endpoint, key=key)
     self.prev_alloc_profile = None
     self.market_configs = {
         ms: MarketConfig(ms, mkt, default=config['markets'].get('default'))
         for ms, mkt in config['markets'].items() if ms != 'default'
     }
コード例 #2
0
def exchange_credentials(exchange):
    if exchange == 'qTrade':
        while True:
            credentials = {exchange: {'api_key': str(input('Input Your qTrade API Key'+'\n'+'> '))}}
            client = QtradeAPI('https://api.qtrade.io', key=credentials[exchange]['api_key'])
            try:
                client.get("/v1/user/me")
            except:
                print('Invalid Credentials'+'\n')
                continue
            else:
                print('qTrade Credentials Verified'+'\n')
                break
    elif exchange == 'Bybit':
        while True:
            credentials = {exchange: {'api_key': str(input('Input Your Bybit API Key'+'\n'+'> ')),
                                      'api_secret': str(input('Input Your Bybit API Secret'+'\n'+'> '))}}
            client = bybit(test=False,api_key=credentials[exchange]['api_key'],
                           api_secret=credentials[exchange]['api_secret'])
            resp = client.APIkey.APIkey_info().result()[0]['ret_msg'];
            if resp == 'invalid api_key':
                print('Invalid Credentials'+'\n')
                continue
            else:
                print('Bybit Credentials Verified'+'\n')
                break
    elif exchange == 'Bitmex':
        while True:
            credentials = {exchange: {'api_key': str(input('Input Your Bitmex API Key'+'\n'+'> ')),
                                      'api_secret': str(input('Input Your Bitmex API Secret'+'\n'+'> '))}}
            client = bitmex(test=False,api_key=credentials[exchange]['api_key'],
                            api_secret=credentials[exchange]['api_secret']);
            try:
                print('\n'+'Testing Bitmex Credentials'+'\n')
                client.User.User_getWalletHistory().result();
            except bravado.exception.HTTPError:
                print('Invalid Credentials'+'\n')
                continue
            else:
                print('Bitmex Credentials Verified'+'\n')
                break
    return credentials
コード例 #3
0
def load_credentials(exchange, bot):
    credentials = json_read('credentials')[exchange]
    if exchange == 'Bitmex':
        client = bitmex(test=False,api_key=credentials['api_key'],api_secret=credentials['api_secret']) 
    elif exchange == 'Bybit':
        client = bybit(test=False,api_key=credentials['api_key'],api_secret=credentials['api_secret'])
    elif exchange == 'qTrade':
        client = QtradeAPI('https://client.qtrade.io', key=credentials['api_key'])
    if bot == True:
        bot = (credentials['bot_token'], credentials['bot_chatID'])
    return client, bot
コード例 #4
0
ファイル: main.py プロジェクト: Henelik/liquidity-bot-example
def cli(ctx, config, endpoint, keyfile, verbose):
    log_level = "DEBUG" if verbose is True else "INFO"

    root = log.getLogger()
    root.setLevel(log_level)
    handler = log.StreamHandler(sys.stdout)
    handler.setLevel(log_level)
    formatter = log.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    root.addHandler(handler)

    api = QtradeAPI(endpoint, key=keyfile.read().strip())
    config = yaml.load(config)

    ctx.obj['mdc'] = MarketDataCollector(config['market_data_collector'])
    ctx.obj['obm'] = OrderbookManager(api, config['orderbook_manager'])
コード例 #5
0
class QTradeScraper(APIScraper):
    def __init__(self, **kwargs):
        self.api = QtradeAPI("https://api.qtrade.io",
                             key=open("lpbot_hmac.txt", "r").read().strip())
        super().__init__(**kwargs)

    def scrape_ticker(self):
        tickers = {}
        for market, qmarket in self.markets.items():
            res = self.api.get("/v1/ticker/{}".format(market))

            log.debug("Ticker %s from %s was acquired successfully", market,
                      self.exchange_name)
            bid = Decimal(res["bid"]).quantize(COIN)
            log.debug("Bid price is %s", bid)
            last = Decimal(res["last"]).quantize(COIN)
            log.debug("Last price is %s", last)
            ask = Decimal(res["ask"]).quantize(COIN)
            log.debug("Ask price is %s", ask)
            tickers[qmarket] = {"bid": bid, "last": last, "ask": ask}
        return tickers
コード例 #6
0
    log.warning(f"day_volume_base: {'%.8f' % pair_market.day_volume_base}")
    log.warning(f"day_volume_market: {'%.8f' % pair_market.day_volume_market}")
    log.warning(f"id: {pair_market.id}")
    log.warning(f"id_hr: {pair_market.id_hr}")
    log.warning(f"last_price: {'%.8f' % pair_market.last_price}")
    log.warning(f"day_spread: {'%.8f' % pair_market.day_spread}")
    log.warning(f"spread_pct: {'%.8f' % pair_market.spread_pct}")


if __name__ == "__main__":

    # Create a session object to make repeated API calls easy!
    api = requests.Session()
    # Create an authenticator with your API key

    api.auth_native = QtradeAPI("https://api.qtrade.io",
                                key=load_credentials())  # use in the future

    api.auth = QtradeAuth(load_credentials())

    # load currencies
    active_currencies = []
    with open("config.json") as confile:
        confile_contents = json.loads(confile.read())
        for currency in confile_contents:
            log.warning(f"Loaded {currency}")

            active_currencies.append(
                Config(
                    name=currency["name"],
                    sell_amount=currency["sell_amount"],
                    buy_amount=currency["buy_amount"],
コード例 #7
0
from qtrade_client.api import QtradeAPI
import json


def scrape_trades(api):
    trades = api.get('/v1/user/trades')["trades"]

    while True:
        new_trades = api.get('/v1/user/trades',
                             newer_than=trades[-1]["id"])["trades"]

        if len(new_trades) == 0:
            break

        trades += new_trades

    return trades


if __name__ == "__main__":
    hmac = open("lpbot_hmac.txt").read().strip()

    api = QtradeAPI("https://api.qtrade.io", key=hmac)

    trades = scrape_trades(api)

    f = open("trades.json", "w")
    f.write(json.dumps(trades))
    f.close()
コード例 #8
0
from qtrade_client.api import QtradeAPI

# Create an API client with your key
endpoint = "https://api.qtrade.io"
key = "1:1111111111111111111111111111111111111111111111111111111111111111"
api = QtradeAPI(endpoint, key)

# Make a call to API
res = api.orders(open=True)
print(res)

# Returned list:
[{'created_at': '2019-11-12T22:42:15.643486Z',
  'id': 8932525,
  'market_amount': '1.10428011',
  'market_amount_remaining': '1.10428011',
  'market_id': 1,
  'open': True,
  'order_type': 'sell_limit',
  'price': '0.0083759',
  'trades': None},
 {'created_at': '2019-11-12T22:42:14.713136Z',
  'id': 8932523,
  'market_amount': '0.92023342',
  'market_amount_remaining': '0.92023342',
  'market_id': 1,
  'open': True,
  'order_type': 'sell_limit',
  'price': '0.00788731',
  'trades': None},
 {'base_amount': '0.00433166',
コード例 #9
0
def verify_credentials(exchange, bot):
    try:
        credentials = load_file(exchange + '_credentials')
    except FileNotFoundError:
        print('Creating ' + exchange + ' Credentials File' + '\n')
        while True:
            credentials = {
                'exchange':
                exchange,
                'api_key':
                str(input('Input Your ' + exchange + ' API Key' + '\n' + '> '))
            }
            if exchange != 'qTrade':
                credentials.update({
                    'api_secret':
                    str(
                        input('Input Your ' + exchange + ' API Secret' + '\n' +
                              '> '))
                })
            if exchange == 'Bitmex':
                client = bitmex(test=False,
                                api_key=credentials['api_key'],
                                api_secret=credentials['api_secret'])
                try:
                    print('\n' + 'Testing Bitmex Credentials' + '\n')
                    client.User.User_getWalletHistory().result()
                except bravado.exception.HTTPError:
                    print('Invalid Credentials' + '\n')
                    continue
                else:
                    print('Bitmex Credentials Verified' + '\n')
                    break
            elif exchange == 'Bybit':
                client = bybit(test=False,
                               api_key=credentials['api_key'],
                               api_secret=credentials['api_secret'])
                resp = client.APIkey.APIkey_info().result()[0]['ret_msg']
                if resp == 'invalid api_key':
                    print('Invalid Credentials' + '\n')
                    continue
                else:
                    print('Bybit Credentials Verified' + '\n')
                    break
            elif exchange == 'qTrade':
                client = QtradeAPI('https://api.qtrade.io',
                                   key=credentials['api_key'])
                try:
                    client.get("/v1/user/me")
                except:
                    print('Invalid Credentials' + '\n')
                    continue
                else:
                    print('qTrade Credentials Verified' + '\n')
                    break
        save_file(exchange + '_credentials', credentials)

    else:
        print('Change Existing ' + exchange + ' Credentials?')
        resp = y_n_prompt()
        if resp == 'No':
            while True:
                credentials = load_file(exchange + '_credentials')
                if exchange == 'Bitmex':
                    client = bitmex(test=False,
                                    api_key=credentials['api_key'],
                                    api_secret=credentials['api_secret'])
                    try:
                        print('\n' + 'Testing Bitmex Credentials' + '\n')
                        client.User.User_getWalletHistory().result()
                    except bravado.exception.HTTPError:
                        print('Invalid Credentials' + '\n')
                        credentials = {
                            'exchange':
                            exchange,
                            'api_key':
                            str(
                                input('Input Your ' + exchange + ' API Key' +
                                      '\n' + '> ')),
                            'api_secret':
                            str(
                                input('Input Your ' + exchange +
                                      ' API Secret' + '\n' + '> ')),
                        }
                        save_file(exchange + '_credentials', credentials)
                        continue
                    else:
                        print('Bitmex Credentials Verified' + '\n')
                        break
                elif exchange == 'Bybit':
                    client = bybit(test=False,
                                   api_key=credentials['api_key'],
                                   api_secret=credentials['api_secret'])
                    resp = client.APIkey.APIkey_info().result()[0]['ret_msg']
                    if resp == 'invalid api_key':
                        print('Invalid Credentials' + '\n')
                        credentials = {
                            'exchange':
                            exchange,
                            'api_key':
                            str(
                                input('Input Your ' + exchange + ' API Key' +
                                      '\n' + '> ')),
                            'api_secret':
                            str(
                                input('Input Your ' + exchange +
                                      ' API Secret' + '\n' + '> ')),
                        }
                        save_file(exchange + '_credentials', credentials)
                        continue
                    else:
                        print('Bybit Credentials Verified' + '\n')
                        break
                elif exchange == 'qTrade':
                    client = QtradeAPI('https://api.qtrade.io',
                                       key=credentials['api_key'])
                    try:
                        client.get("/v1/user/me")
                    except:
                        print('Invalid Credentials' + '\n')
                        credentials = {
                            'exchange':
                            exchange,
                            'api_key':
                            str(
                                input('Input Your ' + exchange + ' API Key' +
                                      '\n' + '> '))
                        }
                        save_file(exchange + '_credentials', credentials)
                        continue
                    else:
                        print('qTrade Credentials Verified' + '\n')
                        break
    while bot:
        credentials = load_file(exchange + '_credentials')
        try:
            credentials['bot_token'] == True
        except KeyError:
            credentials.update({
                'bot_token':
                str(input('Input Your Telegram Bot API Key' + '\n' + '> ')),
                'bot_chatID':
                str(input('Input Your Telegram User ChatID' + '\n' + '> ')),
            })
            test_msg = telegram_sendText(
                (credentials['bot_token'], credentials['bot_chatID']),
                'Testing')['ok']
            if test_msg:
                print('\n' + 'Confirm Test Message Receipt')
                resp = y_n_prompt()
                if resp == 'No':
                    print('Try Again' + '\n')
                    continue
                else:
                    print('Bot Credentials Verified' + '\n')
                    save_file(exchange + '_credentials', credentials)
                    bot = (credentials['bot_token'], credentials['bot_chatID'])
            else:
                print('Test Message Failed. Reenter Bot Credentials' + '\n')
                continue
        else:
            print('Change Existing Bot Credentials?')
            resp = y_n_prompt()
            if resp == 'Yes':
                credentials['bot_token'] = str(
                    input('Input Your Telegram Bot API Key' + '\n' + '> '))
                credentials['bot_chatID'] = str(
                    input('Input Your Telegram User ChatID' + '\n' + '> '))
                test_msg = telegram_sendText(
                    (credentials['bot_token'], credentials['bot_chatID']),
                    'Testing')['ok']
                if test_msg:
                    print('\n' + 'Confirm Test Message Receipt')
                    resp = y_n_prompt()
                    if resp == 'No':
                        print('Try Again' + '\n')
                        continue
                    else:
                        print('Bot Credentials Verified' + '\n')
                        save_file(exchange + '_credentials', credentials)
                        bot = (credentials['bot_token'],
                               credentials['bot_chatID'])
                else:
                    print('Test Message Failed. Reenter Bot Credentials' +
                          '\n')
                    continue
            else:
                bot = (credentials['bot_token'], credentials['bot_chatID'])
        break

    return client, bot
コード例 #10
0
def load_credentials(exchange, bot):
    credentials = load_file(exchange + '_credentials')
    client = QtradeAPI('https://api.qtrade.io', key=credentials['api_key'])
    if bot == True:
        bot = (credentials['bot_token'], credentials['bot_chatID'])
    return client, bot
コード例 #11
0
from qtrade_client.api import QtradeAPI


def load_credentials():
    with open("secret") as authfile:
        return authfile.read()


# String is of the format "[key_id]:[key]"
client_native = QtradeAPI("https://api.qtrade.io", key=load_credentials())

# result = client.post("/v1/user/sell_limit", amount="1", price="0.0001", market_id=12)
# print(result)

# Only closed orders
print(client_native.orders(open=False))
# Print all orders before ID 25
print(client_native.orders(older_than=25))
# Print all orders after ID 25
print(client_native.orders(newer_than=25))
コード例 #12
0
ファイル: test_api.py プロジェクト: Henelik/qtrade-py-client
def api_with_market():
    api = QtradeAPI("http://localhost:9898/")
    # manually set lazily loaded properties
    api._markets_map = {
        "LTC_BTC": {
            "base_currency": {
                "can_withdraw": True,
                "code": "BTC",
                "config": {
                    "address_version": 0,
                    "default_signer": 6,
                    "explorerAddressURL":
                    "https: //live.blockcypher.com/btc/address/",
                    "explorerTransactionURL":
                    "https: //live.blockcypher.com/btc/tx/",
                    "p2sh_address_version": 5,
                    "price": 8614.27,
                    "required_confirmations": 2,
                    "required_generate_confirmations": 100,
                    "satoshi_per_byte": 15,
                    "withdraw_fee": "0.0005",
                },
                "long_name": "Bitcoin",
                "metadata": {
                    "withdraw_notices": []
                },
                "precision": 8,
                "status": "ok",
                "type": "bitcoin_like",
            },
            "can_cancel": True,
            "can_trade": True,
            "can_view": True,
            "id": 1,
            "maker_fee": "0",
            "market_currency": {
                "can_withdraw": True,
                "code": "LTC",
                "config": {
                    "additional_versions": [5],
                    "address_version": 48,
                    "default_signer": 5,
                    "explorerAddressURL":
                    "https: //live.blockcypher.com/ltc/address/",
                    "explorerTransactionURL":
                    "https: //live.blockcypher.com/ltc/tx/",
                    "p2sh_address_version": 50,
                    "price": 58.73,
                    "required_confirmations": 10,
                    "required_generate_confirmations": 100,
                    "satoshi_per_byte": 105,
                    "withdraw_fee": "0.001",
                },
                "long_name": "Litecoin",
                "metadata": {},
                "precision": 8,
                "status": "ok",
                "type": "bitcoin_like",
            },
            "metadata": {},
            "string": "LTC_BTC",
            "taker_fee": "0.005",
        },
        1: {
            "base_currency": {
                "can_withdraw": True,
                "code": "BTC",
                "config": {
                    "address_version": 0,
                    "default_signer": 6,
                    "explorerAddressURL":
                    "https: //live.blockcypher.com/btc/address/",
                    "explorerTransactionURL":
                    "https: //live.blockcypher.com/btc/tx/",
                    "p2sh_address_version": 5,
                    "price": 8614.27,
                    "required_confirmations": 2,
                    "required_generate_confirmations": 100,
                    "satoshi_per_byte": 15,
                    "withdraw_fee": "0.0005",
                },
                "long_name": "Bitcoin",
                "metadata": {
                    "withdraw_notices": []
                },
                "precision": 8,
                "status": "ok",
                "type": "bitcoin_like",
            },
            "can_cancel": True,
            "can_trade": True,
            "can_view": True,
            "id": 1,
            "maker_fee": "0",
            "market_currency": {
                "can_withdraw": True,
                "code": "LTC",
                "config": {
                    "additional_versions": [5],
                    "address_version": 48,
                    "default_signer": 5,
                    "explorerAddressURL":
                    "https: //live.blockcypher.com/ltc/address/",
                    "explorerTransactionURL":
                    "https: //live.blockcypher.com/ltc/tx/",
                    "p2sh_address_version": 50,
                    "price": 58.73,
                    "required_confirmations": 10,
                    "required_generate_confirmations": 100,
                    "satoshi_per_byte": 105,
                    "withdraw_fee": "0.001",
                },
                "long_name": "Litecoin",
                "metadata": {},
                "precision": 8,
                "status": "ok",
                "type": "bitcoin_like",
            },
            "metadata": {},
            "string": "LTC_BTC",
            "taker_fee": "0.005",
        },
    }
    api._tickers = {
        1: {
            "ask": "0.00707017",
            "bid": "0.00664751",
            "day_avg_price": "0.0071579647440367",
            "day_change": "0.0173330516998029",
            "day_high": "0.00727268",
            "day_low": "0.00713415",
            "day_open": "0.00714877",
            "day_volume_base": "0.00169664",
            "day_volume_market": "0.23702827",
            "id": 1,
            "id_hr": "LTC_BTC",
            "last": "0.00727268",
        },
        "LTC_BTC": {
            "ask": "0.00707017",
            "bid": "0.00664751",
            "day_avg_price": "0.0071579647440367",
            "day_change": "0.0173330516998029",
            "day_high": "0.00727268",
            "day_low": "0.00713415",
            "day_open": "0.00714877",
            "day_volume_base": "0.00169664",
            "day_volume_market": "0.23702827",
            "id": 1,
            "id_hr": "LTC_BTC",
            "last": "0.00727268",
        },
    }

    # prevent lazily loaded properties from updating and making http calls
    def ret(*args, **kwargs):
        return

    api._refresh_tickers = ret
    api._refresh_common = ret

    return api
コード例 #13
0
ファイル: test_api.py プロジェクト: Henelik/qtrade-py-client
def api():
    return QtradeAPI("http://localhost:9898/")
コード例 #14
0
 def __init__(self, **kwargs):
     self.api = QtradeAPI("https://api.qtrade.io",
                          key=open("lpbot_hmac.txt", "r").read().strip())
     super().__init__(**kwargs)
コード例 #15
0
class OrderbookManager:
    def __init__(self, endpoint, key, config):
        self.config = config
        self.api = QtradeAPI(endpoint, key=key)
        self.prev_alloc_profile = None
        self.market_configs = {
            ms: MarketConfig(ms, mkt, default=config['markets'].get('default'))
            for ms, mkt in config['markets'].items() if ms != 'default'
        }

    def compute_allocations(self):
        """ Given our allocation % targets and our current balances, figure out
        how much market and base currency we would _ideally_ be
        allocating to each market
        return {
            "DOGE_BTC": [1200, 0.0012],
        }
        """
        balances = {
            c: Decimal(b)
            for c, b in self.api.balances_merged().items()
        }
        balances.update({
            c: 0
            for c in self.config['currency_reserves']
            if c not in balances.keys()
        })
        reserve_config = self.config['currency_reserves']
        allocs = {}
        for market_string, market_alloc in self.market_configs.items():
            market = self.api.markets[market_string]

            def allocate_coin(coin):
                """ Factor in allocation precentage and reserve amount to
                determine how much (base|market)-currency we're going to
                allocate to orders on this particular market. """
                reserve = Decimal(reserve_config[coin])
                alloc_perc = Decimal(market_alloc[coin])

                post_reserve = balances[coin] - reserve
                return max(post_reserve * alloc_perc, 0)

            market_amount = allocate_coin(market['market_currency']['code'])
            base_amount = allocate_coin(market['base_currency']['code'])

            # TODO: At some point COIN will need to be based off base currency
            # precision. Not needed until we have ETH base markets really
            allocs[market_string] = (market_amount, base_amount)
        return allocs

    def allocate_orders(self, market_alloc, base_alloc, market_string):
        """ Given some amount of base and market currency determine how we'll
        allocate orders. Returns a tuple of (slippage_ratio, currency_allocation)
        return {
            "buy_limit": [
                (0.01, 0.00001256),
            ],
            "sell_limit": [
                (0.01, 1250),
            ]
        }
        """
        buy_allocs = []
        sell_allocs = []
        mc = self.market_configs[market_string]
        for slip, ratio in mc['intervals']['sell_limit'].items():
            ratio = Decimal(ratio)
            amount = (market_alloc * ratio).quantize(COIN)
            sell_allocs.append((slip, amount))
        for slip, ratio in mc['intervals']['buy_limit'].items():
            ratio = Decimal(ratio)
            value = (base_alloc * ratio).quantize(COIN)
            buy_allocs.append((slip, value))
        return {'buy_limit': buy_allocs, 'sell_limit': sell_allocs}

    def price_orders(self, orders, bid, ask):
        """
        return {
            "buy_limit": [
                (0.00000033, 0.00001256),
            ],
            "sell_limit": [
                (0.00000034, 1250),
            ]
        } """
        priced_sell_orders = []
        priced_buy_orders = []
        bid = Decimal(bid)
        ask = Decimal(ask)
        for slip, amount in orders['sell_limit']:
            slip = Decimal(slip)
            price = (ask + (ask * slip)).quantize(COIN)
            priced_sell_orders.append((price, amount))
        for slip, value in orders['buy_limit']:
            slip = Decimal(slip)
            price = (bid - (bid * slip)).quantize(COIN)
            priced_buy_orders.append((price, value))
        return {
            'buy_limit': priced_buy_orders,
            'sell_limit': priced_sell_orders
        }

    def rebalance_orders(self, allocation_profile, orders, force=False):
        if self.check_for_rebalance(
                allocation_profile) is False and force is False:
            return

        if self.config['dry_run_mode']:
            log.warning(
                "You are in dry run mode! Orders will not be cancelled or placed!"
            )
            pprint(allocation_profile)
            return

        self.api.cancel_all_orders()

        for market_string, profile in allocation_profile.items():
            for price, value in profile['buy_limit']:
                self.place_order('buy_limit', market_string, price, value)
            for price, amount in profile['sell_limit']:
                self.place_order('sell_limit', market_string, price, amount)
        self.prev_alloc_profile = allocation_profile

    def place_order(self, order_type, market_string, price, quantity):
        if quantity <= 0:
            return
        log.info("Placing %s on %s market for %s at %s", order_type,
                 market_string, quantity, price)
        if order_type == 'buy_limit':
            value = quantity
            amount = None
        elif order_type == 'sell_limit':
            value = None
            amount = quantity
        try:
            self.api.order(order_type,
                           price,
                           market_string=market_string,
                           value=value,
                           amount=amount,
                           prevent_taker=False)
        except APIException as e:
            if e.code == 400:
                log.warning("Caught API error!")
            else:
                raise e

    def check_for_rebalance(self, allocation_profile):
        if self.prev_alloc_profile is None:
            log.info("Rebalance! No previous rebalance data!")
            return True

        for market, profile in allocation_profile.items():
            prev_profile = self.prev_alloc_profile[market]
            for t in ('buy_limit', 'sell_limit'):
                for n, o in zip(profile[t], prev_profile[t]):
                    price_diff = (n[0] - o[0]) / n[0]
                    price_tol = self.config['price_tolerance']
                    if price_diff > price_tol:
                        if o[0] > price_diff:
                            log.info(
                                'Rebalance! %s %s price is %s%% higher than allotted',
                                market, t,
                                price_diff.quantize(PERC) * Decimal(100))
                        else:
                            log.info(
                                'Rebalance! %s %s price is %s%% lower than allotted',
                                market, t,
                                price_diff.quantize(PERC) * Decimal(100))
                        return True
                    if n[1] == 0:
                        continue
                    amount_diff = (n[1] - o[1]) / n[1]
                    amount_tol = self.config['amount_tolerance']
                    if amount_diff > amount_tol:
                        if o[1] > amount_diff:
                            log.info(
                                'Rebalance! %s %s amount is %s%% higher than allotted',
                                market, t,
                                amount_diff.quantize(PERC) * Decimal(100))
                        else:
                            log.info(
                                'Rebalance! %s %s amount is %s%% lower than allotted',
                                market, t,
                                amount_diff.quantize(PERC) * Decimal(100))
                        return True

        balances = self.api.balances()
        for coin, reserve in self.config['currency_reserves'].items():
            balance_usd = self.coin_to_usd(coin, balances.get(coin, 0))
            reserve_usd = self.coin_to_usd(coin, reserve)
            thresh = Decimal(self.config['reserve_thresh_usd'])
            if balance_usd > reserve_usd + thresh:
                log.info(
                    f"Rebalance! {coin} balance_usd {balance_usd} > reserve {reserve} + thresh {thresh}."
                )
                return True
            if balance_usd < reserve_usd - thresh:
                log.info(
                    f"Rebalance! {coin} balance_usd {balance_usd} < reserve {reserve} - thresh {thresh}."
                )
                return True
        return False

    def get_orders(self):
        orders = self.api.get("/v1/user/orders")["orders"]

        log.debug("Updating orders...")
        sorted_orders = {}
        for o in orders:
            if o['open']:
                mi = self.api.get("/v1/market/" +
                                  str(o['market_id']))['market']
                o['price'] = Decimal(o['price'])
                o['market_amount_remaining'] = Decimal(
                    o['market_amount_remaining'])
                o['base_amount'] = o['price'] * o['market_amount_remaining']
                market = mi['market_currency'] + '_' + mi['base_currency']
                sorted_orders.setdefault(market, {'buy': [], 'sell': []})
                if o["order_type"] == "sell_limit":
                    sorted_orders[market]['sell'].append(o)
                elif o["order_type"] == "buy_limit":
                    sorted_orders[market]['buy'].append(o)
        log.debug("Active buy orders: %s", sorted_orders)

        log.info(
            "%s active buy orders",
            sum([len(market['buy']) for market in sorted_orders.values()]))
        log.info(
            "%s active sell orders",
            sum([len(market['sell']) for market in sorted_orders.values()]))
        return sorted_orders

    def generate_orders(self, force_rebalance=False):
        allocs = self.compute_allocations()
        allocation_profile = {}
        for market, (market_amount, base_amount) in allocs.items():
            if market in ExchangeDatastore.tickers['bittrex'].keys():
                bid = ExchangeDatastore.tickers['bittrex'][market]['bid']
                ask = ExchangeDatastore.tickers['bittrex'][market]['ask']
            elif market in ExchangeDatastore.tickers['ccxt'].keys():
                bid = ExchangeDatastore.tickers['ccxt'][market]['bid']
                ask = ExchangeDatastore.tickers['ccxt'][market]['ask']
            else:
                log.warning(
                    f"Can't get bid/ask price for {market} to generate orders!"
                )
                continue
            log.info("Generating %s orders with bid %s and ask %s", market,
                     bid, ask)
            allocation_profile[market] = self.price_orders(
                self.allocate_orders(market_amount, base_amount, market), bid,
                ask)
        self.rebalance_orders(allocation_profile,
                              self.get_orders(),
                              force=force_rebalance)

    def estimate_account_value(self):
        # convert all coin values to BTC using the Bittrex bid price
        # then convert to USD
        total_bal = 0
        bals = self.api.balances_merged()
        for coin, bal in bals.items():
            if coin == "BTC":
                total_bal += Decimal(bal)
            else:
                total_bal += self.coin_to_btc(coin, bal)
        return total_bal, self.btc_to_usd(total_bal).quantize(PERC)

    def estimate_account_gain(self, btc_bal):
        cost_basis = Decimal(self.config['cost_basis_btc'])
        gain = (btc_bal - cost_basis).quantize(COIN)
        return gain, self.btc_to_usd(gain).quantize(PERC)

    def coin_to_btc(self, coin, amt):
        exchanges = ['bittrex', 'ccxt', 'qtrade']
        for e in exchanges:
            try:
                bid = ExchangeDatastore.tickers[e][coin + '_BTC']['bid']
                return (Decimal(amt) * Decimal(bid)).quantize(COIN)
            except KeyError:
                pass
        log.warning("Can't get bid price for %s for price estimation", coin)
        return 0

    def btc_to_usd(self, amt):
        btc_price = Decimal(
            self.api.get('/v1/currency/BTC')['currency']['config']['price'])
        return Decimal(amt) * btc_price

    def coin_to_usd(self, coin: str, amt: Union[Decimal, float]) -> Decimal:
        if coin == "BTC":
            return self.btc_to_usd(amt)
        return self.btc_to_usd(self.coin_to_btc(coin, amt)).quantize(PERC)

    def boot_trades(self):
        trades = {
            t['id']: t
            for t in self.api.get('/v1/user/trades')['trades']
        }
        newest_ids = heapq.nlargest(10, trades.keys())
        recent_trades = {id: trades[id] for id in newest_ids}
        self.most_recent_trade_id = max(newest_ids)
        log.info("10 most recent trades:\n%s", pformat(recent_trades))

    def check_for_trades(self):
        res = self.api.get('/v1/user/trades',
                           newer_than=self.most_recent_trade_id)
        if res['trades'] == []:
            log.info('No new trades!')
            return
        trades = {t['id']: t for t in res['trades']}
        log.info("Bot made new trades:\n%s", pformat(trades))
        self.most_recent_trade_id = max(trades.keys())

    async def monitor(self):
        # Sleep to allow data scrapers to populate
        await asyncio.sleep(2)
        log.info("Starting orderbook manager; interval period %s sec",
                 self.config['monitor_period'])
        self.boot_trades()
        while True:
            try:
                self.generate_orders()
                btc_val, usd_val = self.estimate_account_value()
                log.info("Current account value is about $%s, %s BTC", usd_val,
                         btc_val)
                btc_gain, usd_gain = self.estimate_account_gain(btc_val)
                log.info("The bot has earned $%s, %s BTC", usd_gain, btc_gain)
                self.check_for_trades()
                await asyncio.sleep(self.config['monitor_period'])
            except Exception:
                log.warning("Orderbook manager loop exploded", exc_info=True)
                # Just in case the entire program explodes, so that we don't have orders out.
                self.api.cancel_market_orders()