2017-10-06 10:22:04 +00:00
|
|
|
import logging
|
2017-10-13 19:09:55 +00:00
|
|
|
from typing import List, Optional, Dict
|
2017-10-06 10:22:04 +00:00
|
|
|
|
|
|
|
import arrow
|
2017-10-13 19:47:49 +00:00
|
|
|
from bittrex.bittrex import Bittrex as _Bittrex, API_V2_0, TICKINTERVAL_FIVEMIN, ORDERTYPE_LIMIT, \
|
|
|
|
TIMEINEFFECT_GOOD_TIL_CANCELLED
|
2017-10-06 10:22:04 +00:00
|
|
|
|
2017-10-07 16:07:29 +00:00
|
|
|
from freqtrade.exchange.interface import Exchange
|
2017-10-06 10:22:04 +00:00
|
|
|
|
|
|
|
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'
|
|
|
|
PAIR_DETAIL_METHOD: str = BASE_URL + '/Market/Index'
|
|
|
|
# Ticker inveral
|
2017-10-13 19:47:49 +00:00
|
|
|
TICKER_INTERVAL: str = TICKINTERVAL_FIVEMIN
|
2017-10-06 10:22:04 +00:00
|
|
|
# Sleep time to avoid rate limits, used in the main loop
|
|
|
|
SLEEP_TIME: float = 25
|
|
|
|
|
|
|
|
@property
|
|
|
|
def sleep_time(self) -> float:
|
|
|
|
return self.SLEEP_TIME
|
|
|
|
|
|
|
|
def __init__(self, config: dict) -> None:
|
|
|
|
global _API, _EXCHANGE_CONF
|
|
|
|
|
|
|
|
_EXCHANGE_CONF.update(config)
|
2017-10-13 17:56:31 +00:00
|
|
|
_API = _Bittrex(
|
|
|
|
api_key=_EXCHANGE_CONF['key'],
|
|
|
|
api_secret=_EXCHANGE_CONF['secret'],
|
|
|
|
api_version=API_V2_0,
|
|
|
|
)
|
2017-10-06 10:22:04 +00:00
|
|
|
|
|
|
|
def buy(self, pair: str, rate: float, amount: float) -> str:
|
2017-10-13 19:47:49 +00:00
|
|
|
data = _API.trade_buy(
|
|
|
|
market=pair.replace('_', '-'),
|
|
|
|
order_type=ORDERTYPE_LIMIT,
|
|
|
|
quantity=amount,
|
|
|
|
rate=rate,
|
|
|
|
time_in_effect=TIMEINEFFECT_GOOD_TIL_CANCELLED,
|
|
|
|
)
|
2017-10-06 10:22:04 +00:00
|
|
|
if not data['success']:
|
|
|
|
raise RuntimeError('{}: {}'.format(self.name.upper(), data['message']))
|
2017-10-13 19:47:49 +00:00
|
|
|
return data['result']['OrderId']
|
2017-10-06 10:22:04 +00:00
|
|
|
|
|
|
|
def sell(self, pair: str, rate: float, amount: float) -> str:
|
2017-10-13 19:48:43 +00:00
|
|
|
data = _API.trade_sell(
|
|
|
|
market=pair.replace('_', '-'),
|
|
|
|
order_type=ORDERTYPE_LIMIT,
|
|
|
|
quantity=amount,
|
|
|
|
rate=rate,
|
|
|
|
time_in_effect=TIMEINEFFECT_GOOD_TIL_CANCELLED,
|
|
|
|
)
|
2017-10-06 10:22:04 +00:00
|
|
|
if not data['success']:
|
|
|
|
raise RuntimeError('{}: {}'.format(self.name.upper(), data['message']))
|
2017-10-13 19:48:43 +00:00
|
|
|
return data['result']['OrderId']
|
2017-10-06 10:22:04 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2017-10-15 16:59:52 +00:00
|
|
|
def get_ticker(self, pair: str) -> Dict[str, float]:
|
|
|
|
data = self.get_orderbook(pair, top_most=1)
|
|
|
|
return {
|
|
|
|
'bid': data['bid'][0]['Rate'],
|
|
|
|
'ask': data['ask'][0]['Rate'],
|
|
|
|
}
|
|
|
|
|
2017-10-13 19:09:55 +00:00
|
|
|
def get_orderbook(self, pair: str, top_most: Optional[int] = None) -> Dict[str, List[Dict]]:
|
|
|
|
data = _API.get_orderbook(pair.replace('_', '-'))
|
2017-10-06 10:22:04 +00:00
|
|
|
if not data['success']:
|
|
|
|
raise RuntimeError('{}: {}'.format(self.name.upper(), data['message']))
|
|
|
|
return {
|
2017-10-13 19:09:55 +00:00
|
|
|
'bid': data['result']['buy'][:top_most],
|
|
|
|
'ask': data['result']['sell'][:top_most],
|
2017-10-06 10:22:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def get_ticker_history(self, pair: str, minimum_date: Optional[arrow.Arrow] = None):
|
2017-10-13 17:58:46 +00:00
|
|
|
data = _API.get_candles(pair.replace('_', '-'), self.TICKER_INTERVAL)
|
2017-10-06 10:22:04 +00:00
|
|
|
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']]
|