stable/exchange.py

208 lines
7.3 KiB
Python
Raw Normal View History

2017-05-12 17:11:56 +00:00
import enum
2017-05-14 12:14:16 +00:00
import logging
2017-09-01 19:11:46 +00:00
from typing import List
2017-05-12 17:11:56 +00:00
from bittrex.bittrex import Bittrex
from poloniex import Poloniex
2017-05-14 12:14:16 +00:00
from wrapt import synchronized
2017-05-12 17:11:56 +00:00
2017-05-14 12:14:16 +00:00
logger = logging.getLogger(__name__)
2017-05-12 17:11:56 +00:00
_exchange_api = None
class Exchange(enum.Enum):
POLONIEX = 0
BITTREX = 1
class ApiWrapper(object):
"""
Wrapper for exchanges.
Currently implemented:
* Bittrex
* Poloniex (partly)
"""
2017-09-01 19:11:46 +00:00
def __init__(self, config: dict):
2017-05-12 17:11:56 +00:00
"""
2017-09-01 23:09:57 +00:00
Initializes the ApiWrapper with the given config,
it does basic validation whether the specified
exchange and pairs are valid.
2017-05-12 17:11:56 +00:00
:param config: dict
"""
self.dry_run = config['dry_run']
2017-05-14 12:14:16 +00:00
if self.dry_run:
logger.info('Instance is running with dry_run enabled')
2017-05-12 17:11:56 +00:00
use_poloniex = config.get('poloniex', {}).get('enabled', False)
use_bittrex = config.get('bittrex', {}).get('enabled', False)
if use_poloniex:
self.exchange = Exchange.POLONIEX
self.api = Poloniex(key=config['poloniex']['key'], secret=config['poloniex']['secret'])
2017-05-12 17:11:56 +00:00
elif use_bittrex:
self.exchange = Exchange.BITTREX
self.api = Bittrex(api_key=config['bittrex']['key'], api_secret=config['bittrex']['secret'])
2017-05-12 17:11:56 +00:00
else:
self.api = None
2017-09-01 23:09:57 +00:00
raise RuntimeError('No exchange specified. Aborting!')
# Check if all pairs are available
markets = self.get_markets()
for pair in config[self.exchange.name.lower()]['pair_whitelist']:
if pair not in markets:
raise RuntimeError('Pair {} is not available at Poloniex'.format(pair))
2017-05-12 17:11:56 +00:00
2017-09-01 19:11:46 +00:00
def buy(self, pair: str, rate: float, amount: float) -> str:
2017-05-12 17:11:56 +00:00
"""
Places a limit buy order.
:param pair: Pair as str, format: BTC_ETH
:param rate: Rate limit for order
:param amount: The amount to purchase
2017-09-01 19:11:46 +00:00
:return: order_id of the placed buy order
2017-05-12 17:11:56 +00:00
"""
if self.dry_run:
pass
elif self.exchange == Exchange.POLONIEX:
self.api.buy(pair, rate, amount)
2017-05-14 12:14:16 +00:00
# TODO: return order id
2017-05-12 17:11:56 +00:00
elif self.exchange == Exchange.BITTREX:
data = self.api.buy_limit(pair.replace('_', '-'), amount, rate)
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
2017-05-14 12:14:16 +00:00
return data['result']['uuid']
2017-05-12 17:11:56 +00:00
2017-09-01 19:11:46 +00:00
def sell(self, pair: str, rate: float, amount: float) -> str:
2017-05-12 17:11:56 +00:00
"""
Places a limit sell order.
:param pair: Pair as str, format: BTC_ETH
:param rate: Rate limit for order
:param amount: The amount to sell
:return: None
"""
if self.dry_run:
pass
elif self.exchange == Exchange.POLONIEX:
self.api.sell(pair, rate, amount)
2017-05-14 12:14:16 +00:00
# TODO: return order id
2017-05-12 17:11:56 +00:00
elif self.exchange == Exchange.BITTREX:
data = self.api.sell_limit(pair.replace('_', '-'), amount, rate)
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
2017-05-14 12:14:16 +00:00
return data['result']['uuid']
2017-05-12 17:11:56 +00:00
2017-09-01 19:11:46 +00:00
def get_balance(self, currency: str) -> float:
2017-05-12 17:11:56 +00:00
"""
Get account balance.
:param currency: currency as str, format: BTC
:return: float
"""
2017-05-14 12:14:16 +00:00
if self.dry_run:
return 999.9
elif self.exchange == Exchange.POLONIEX:
2017-05-12 17:11:56 +00:00
data = self.api.returnBalances()
return float(data[currency])
elif self.exchange == Exchange.BITTREX:
data = self.api.get_balance(currency)
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
return float(data['result']['Balance'] or 0.0)
2017-09-01 19:11:46 +00:00
def get_ticker(self, pair: str) -> dict:
2017-05-12 17:11:56 +00:00
"""
Get Ticker for given pair.
:param pair: Pair as str, format: BTC_ETC
:return: dict
"""
if self.exchange == Exchange.POLONIEX:
data = self.api.returnTicker()
return {
'bid': float(data[pair]['highestBid']),
'ask': float(data[pair]['lowestAsk']),
'last': float(data[pair]['last'])
}
elif self.exchange == Exchange.BITTREX:
data = self.api.get_ticker(pair.replace('_', '-'))
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
return {
'bid': float(data['result']['Bid']),
'ask': float(data['result']['Ask']),
'last': float(data['result']['Last']),
}
2017-09-01 19:11:46 +00:00
def cancel_order(self, order_id: str) -> None:
2017-05-14 12:14:16 +00:00
"""
Cancel order for given order_id
:param order_id: id as str
:return: None
"""
if self.dry_run:
pass
elif self.exchange == Exchange.POLONIEX:
raise NotImplemented('Not implemented')
elif self.exchange == Exchange.BITTREX:
data = self.api.cancel(order_id)
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
2017-09-01 19:11:46 +00:00
def get_open_orders(self, pair: str) -> List[dict]:
2017-05-12 17:11:56 +00:00
"""
Get all open orders for given pair.
:param pair: Pair as str, format: BTC_ETC
:return: list of dicts
"""
2017-05-14 12:14:16 +00:00
if self.dry_run:
return []
elif self.exchange == Exchange.POLONIEX:
2017-05-12 17:11:56 +00:00
raise NotImplemented('Not implemented')
elif self.exchange == Exchange.BITTREX:
data = self.api.get_open_orders(pair.replace('_', '-'))
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
return [{
2017-05-14 12:14:16 +00:00
'id': entry['OrderUuid'],
2017-05-12 17:11:56 +00:00
'type': entry['OrderType'],
'opened': entry['Opened'],
'rate': entry['PricePerUnit'],
'amount': entry['Quantity'],
'remaining': entry['QuantityRemaining'],
} for entry in data['result']]
2017-06-05 19:17:10 +00:00
2017-09-01 19:11:46 +00:00
def get_pair_detail_url(self, pair: str) -> str:
2017-06-05 19:17:10 +00:00
"""
Returns the market detail url for the given pair
:param pair: pair as str, format: BTC_ANT
:return: url as str
"""
if self.exchange == Exchange.POLONIEX:
raise NotImplemented('Not implemented')
elif self.exchange == Exchange.BITTREX:
return 'https://bittrex.com/Market/Index?MarketName={}'.format(pair.replace('_', '-'))
2017-09-01 19:11:46 +00:00
2017-09-01 23:09:57 +00:00
def get_markets(self) -> List[str]:
"""
Returns all available markets
:return: list of all available pairs
"""
if self.exchange == Exchange.POLONIEX:
# TODO: implement
raise NotImplemented('Not implemented')
elif self.exchange == Exchange. BITTREX:
data = self.api.get_markets()
if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message']))
return [m['MarketName'].replace('-', '_') for m in data['result']]
2017-09-01 19:11:46 +00:00
@synchronized
def get_exchange_api(conf: dict) -> ApiWrapper:
"""
Returns the current exchange api or instantiates a new one
:return: exchange.ApiWrapper
"""
global _exchange_api
if not _exchange_api:
_exchange_api = ApiWrapper(conf)
return _exchange_api