import logging from typing import List, Optional import arrow import requests from bittrex.bittrex import Bittrex as _Bittrex from freqtrade.exchange.interface import Exchange logger = logging.getLogger(__name__) _API: _Bittrex = None _EXCHANGE_CONF: dict = {} class Bittrex(Exchange): """ Bittrex API wrapper. """ # Base URL and API endpoints BASE_URL: str = 'https://www.bittrex.com' TICKER_METHOD: str = BASE_URL + '/Api/v2.0/pub/market/GetTicks' PAIR_DETAIL_METHOD: str = BASE_URL + '/Market/Index' # Ticker inveral TICKER_INTERVAL: str = 'fiveMin' # Sleep time to avoid rate limits, used in the main loop SLEEP_TIME: float = 25 @property def name(self) -> str: return self.__class__.__name__ @property def sleep_time(self) -> float: return self.SLEEP_TIME def __init__(self, config: dict) -> None: global _API, _EXCHANGE_CONF _EXCHANGE_CONF.update(config) _API = _Bittrex(api_key=_EXCHANGE_CONF['key'], api_secret=_EXCHANGE_CONF['secret']) # Check if all pairs are available markets = self.get_markets() exchange_name = self.name for pair in _EXCHANGE_CONF['pair_whitelist']: if pair not in markets: raise RuntimeError('Pair {} is not available at {}'.format(pair, exchange_name)) def buy(self, pair: str, rate: float, amount: float) -> str: data = _API.buy_limit(pair.replace('_', '-'), amount, rate) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return data['result']['uuid'] def sell(self, pair: str, rate: float, amount: float) -> str: data = _API.sell_limit(pair.replace('_', '-'), amount, rate) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return data['result']['uuid'] def get_balance(self, currency: str) -> float: data = _API.get_balance(currency) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return float(data['result']['Balance'] or 0.0) def get_ticker(self, pair: str) -> dict: data = _API.get_ticker(pair.replace('_', '-')) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return { 'bid': float(data['result']['Bid']), 'ask': float(data['result']['Ask']), 'last': float(data['result']['Last']), } def get_ticker_history(self, pair: str, minimum_date: Optional[arrow.Arrow] = None): url = self.TICKER_METHOD headers = { # TODO: Set as global setting 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36' } params = { 'marketName': pair.replace('_', '-'), 'tickInterval': self.TICKER_INTERVAL, # TODO: Timestamp has no effect on API response '_': minimum_date.timestamp * 1000 } data = requests.get(url, params=params, headers=headers).json() if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return data def cancel_order(self, order_id: str) -> None: data = _API.cancel(order_id) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) def get_open_orders(self, pair: str) -> List[dict]: data = _API.get_open_orders(pair.replace('_', '-')) if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return [{ 'id': entry['OrderUuid'], 'type': entry['OrderType'], 'opened': entry['Opened'], 'rate': entry['PricePerUnit'], 'amount': entry['Quantity'], 'remaining': entry['QuantityRemaining'], } for entry in data['result']] def get_pair_detail_url(self, pair: str) -> str: return self.PAIR_DETAIL_METHOD + '?MarketName={}'.format(pair.replace('_', '-')) def get_markets(self) -> List[str]: data = _API.get_markets() if not data['success']: raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) return [m['MarketName'].replace('-', '_') for m in data['result']]