exhcange now uses ccxt in dry_run, update config

This commit is contained in:
Samuel Husso 2018-03-21 19:40:16 +02:00
parent 14d16d573c
commit 40a0689183
2 changed files with 86 additions and 25 deletions

View File

@ -91,7 +91,7 @@ class Constants(object):
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': 'string', 'type': 'string',
'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
}, },
'uniqueItems': True 'uniqueItems': True
}, },
@ -99,7 +99,7 @@ class Constants(object):
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': 'string', 'type': 'string',
'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
}, },
'uniqueItems': True 'uniqueItems': True
} }

View File

@ -2,32 +2,56 @@
""" Cryptocurrency Exchanges support """ """ Cryptocurrency Exchanges support """
import enum import enum
import logging import logging
import ccxt
from random import randint from random import randint
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from cachetools import cached, TTLCache
import arrow import arrow
import requests import requests
from cachetools import cached, TTLCache
from freqtrade import OperationalException from freqtrade import OperationalException
from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.interface import Exchange from freqtrade.exchange.interface import Exchange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current selected exchange # Current selected exchange
_API: Exchange = None _API = None
_CONF: dict = {} _CONF: dict = {}
API_RETRY_COUNT = 4
# Holds all open sell orders for dry_run # Holds all open sell orders for dry_run
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {} _DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
def retrier(f):
def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return f(*args, **kwargs)
# TODO dont be a gotta-catch-them-all pokemon collector
except Exception as ex:
logger.warn('%s returned exception: "%s"', f, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warn('retrying %s still for %s times', f, count)
return wrapper(*args, **kwargs)
else:
raise OperationalException('Giving up retrying: %s', f)
return wrapper
class Exchanges(enum.Enum):
""" def _get_market_url(exchange):
Maps supported exchange names to correspondent classes. "get market url for exchange"
""" # TODO: PR to ccxt
BITTREX = Bittrex base = exchange.urls.get('www')
market = ""
if 'bittrex' in get_name():
market = base + '/Market/Index?MarketName={}'
if 'binance' in get_name():
market = base + '/trade.html?symbol={}'
return market
def init(config: dict) -> None: def init(config: dict) -> None:
@ -49,12 +73,21 @@ def init(config: dict) -> None:
# Find matching class for the given exchange name # Find matching class for the given exchange name
name = exchange_config['name'] name = exchange_config['name']
# TODO add check for a list of supported exchanges
try: try:
exchange_class = Exchanges[name.upper()].value # exchange_class = Exchanges[name.upper()].value
_API = getattr(ccxt, name.lower())({
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
})
logger.info('Using Exchange %s', name.capitalize())
except KeyError: except KeyError:
raise OperationalException('Exchange {} is not supported'.format(name)) raise OperationalException('Exchange {} is not supported'.format(name))
_API = exchange_class(exchange_config) # we need load api markets
_API.load_markets()
# Check if all pairs are available # Check if all pairs are available
validate_pairs(config['exchange']['pair_whitelist']) validate_pairs(config['exchange']['pair_whitelist'])
@ -67,15 +100,22 @@ def validate_pairs(pairs: List[str]) -> None:
:param pairs: list of pairs :param pairs: list of pairs
:return: None :return: None
""" """
if not _API.markets:
_API.load_markets()
try: try:
markets = _API.get_markets() markets = _API.markets
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e) logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return return
stake_cur = _CONF['stake_currency'] stake_cur = _CONF['stake_currency']
for pair in pairs: for pair in pairs:
if not pair.startswith(stake_cur): # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
pair = pair.replace('_', '/')
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException( raise OperationalException(
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur) 'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
) )
@ -124,23 +164,31 @@ def get_balance(currency: str) -> float:
if _CONF['dry_run']: if _CONF['dry_run']:
return 999.9 return 999.9
return _API.get_balance(currency) return _API.fetch_balance()[currency]
def get_balances(): def get_balances():
if _CONF['dry_run']: if _CONF['dry_run']:
return [] return []
return _API.get_balances() return _API.fetch_balance()
# @cached(TTLCache(maxsize=100, ttl=30))
@retrier
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
return _API.get_ticker(pair, refresh) return _API.fetch_ticker(pair)
@cached(TTLCache(maxsize=100, ttl=30)) # @cached(TTLCache(maxsize=100, ttl=30))
@retrier
def get_ticker_history(pair: str, tick_interval) -> List[Dict]: def get_ticker_history(pair: str, tick_interval) -> List[Dict]:
return _API.get_ticker_history(pair, tick_interval) # TODO: tickers need to be in format 1m,5m
# fetch_ohlcv returns an [[datetime,o,h,l,c,v]]
if not _API.markets:
_API.load_markets()
ohlcv = _API.fetch_ohlcv(pair, str(tick_interval)+'m')
return ohlcv
def cancel_order(order_id: str) -> None: def cancel_order(order_id: str) -> None:
@ -162,7 +210,9 @@ def get_order(order_id: str) -> Dict:
def get_pair_detail_url(pair: str) -> str: def get_pair_detail_url(pair: str) -> str:
return _API.get_pair_detail_url(pair) return _get_market_url(_API).format(
_API.markets[pair]['id']
)
def get_markets() -> List[str]: def get_markets() -> List[str]:
@ -170,16 +220,27 @@ def get_markets() -> List[str]:
def get_market_summaries() -> List[Dict]: def get_market_summaries() -> List[Dict]:
return _API.get_market_summaries() return _API.fetch_tickers()
def get_name() -> str: def get_name() -> str:
return _API.name return _API.__class__.__name__
def get_fee_maker() -> float:
return _API.fees['trading']['maker']
def get_fee_taker() -> float:
return _API.fees['trading']['taker']
def get_fee() -> float: def get_fee() -> float:
return _API.fee return _API.fees['trading']
def get_wallet_health() -> List[Dict]: def get_wallet_health() -> List[Dict]:
return _API.get_wallet_health() if not _API.markets:
_API.load_markets()
return _API.markets