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

View File

@ -2,32 +2,56 @@
""" Cryptocurrency Exchanges support """
import enum
import logging
import ccxt
from random import randint
from typing import List, Dict, Any, Optional
from cachetools import cached, TTLCache
import arrow
import requests
from cachetools import cached, TTLCache
from freqtrade import OperationalException
from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.interface import Exchange
logger = logging.getLogger(__name__)
# Current selected exchange
_API: Exchange = None
_API = None
_CONF: dict = {}
API_RETRY_COUNT = 4
# Holds all open sell orders for dry_run
_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):
"""
Maps supported exchange names to correspondent classes.
"""
BITTREX = Bittrex
def _get_market_url(exchange):
"get market url for exchange"
# TODO: PR to ccxt
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:
@ -49,12 +73,21 @@ def init(config: dict) -> None:
# Find matching class for the given exchange name
name = exchange_config['name']
# TODO add check for a list of supported exchanges
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:
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
validate_pairs(config['exchange']['pair_whitelist'])
@ -67,15 +100,22 @@ def validate_pairs(pairs: List[str]) -> None:
:param pairs: list of pairs
:return: None
"""
if not _API.markets:
_API.load_markets()
try:
markets = _API.get_markets()
markets = _API.markets
except requests.exceptions.RequestException as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return
stake_cur = _CONF['stake_currency']
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(
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
)
@ -124,23 +164,31 @@ def get_balance(currency: str) -> float:
if _CONF['dry_run']:
return 999.9
return _API.get_balance(currency)
return _API.fetch_balance()[currency]
def get_balances():
if _CONF['dry_run']:
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:
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]:
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:
@ -162,7 +210,9 @@ def get_order(order_id: str) -> Dict:
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]:
@ -170,16 +220,27 @@ def get_markets() -> List[str]:
def get_market_summaries() -> List[Dict]:
return _API.get_market_summaries()
return _API.fetch_tickers()
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:
return _API.fee
return _API.fees['trading']
def get_wallet_health() -> List[Dict]:
return _API.get_wallet_health()
if not _API.markets:
_API.load_markets()
return _API.markets