Merge branch 'feat/objectify-ccxt' into ccxt-objectify-pr1

This commit is contained in:
enenn 2018-03-26 19:31:15 +02:00 committed by GitHub
commit 916165a7c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 224 additions and 182 deletions

View File

@ -13,19 +13,19 @@
"key": "your_exchange_key", "key": "your_exchange_key",
"secret": "your_exchange_secret", "secret": "your_exchange_secret",
"pair_whitelist": [ "pair_whitelist": [
"BTC_ETH", "ETH/BTC",
"BTC_LTC", "LTC/BTC",
"BTC_ETC", "ETC/BTC",
"BTC_DASH", "DASH/BTC",
"BTC_ZEC", "ZEC/BTC",
"BTC_XLM", "XLM/BTC",
"BTC_NXT", "NXT/BTC",
"BTC_POWR", "POWR/BTC",
"BTC_ADA", "ADA/BTC",
"BTC_XMR" "XMR/BTC"
], ],
"pair_blacklist": [ "pair_blacklist": [
"BTC_DOGE" "DOGE/BTC"
] ]
}, },
"experimental": { "experimental": {

View File

@ -21,19 +21,19 @@
"key": "your_exchange_key", "key": "your_exchange_key",
"secret": "your_exchange_secret", "secret": "your_exchange_secret",
"pair_whitelist": [ "pair_whitelist": [
"BTC_ETH", "ETH/BTC",
"BTC_LTC", "LTC/BTC",
"BTC_ETC", "ETC/BTC",
"BTC_DASH", "DASH/BTC",
"BTC_ZEC", "ZEC/BTC",
"BTC_XLM", "XLM/BTC",
"BTC_NXT", "NXT/BTC",
"BTC_POWR", "POWR/BTC",
"BTC_ADA", "ADA/BTC",
"BTC_XMR" "XMR/BTC"
], ],
"pair_blacklist": [ "pair_blacklist": [
"BTC_DOGE" "DOGE/BTC"
] ]
}, },
"experimental": { "experimental": {

View File

@ -44,12 +44,14 @@ class Analyze(object):
:param ticker: See exchange.get_ticker_history :param ticker: See exchange.get_ticker_history
:return: DataFrame :return: DataFrame
""" """
columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'} cols = ['date', 'open', 'high', 'low', 'close', 'volume']
frame = DataFrame(ticker) \ frame = DataFrame(ticker, columns=cols)
.rename(columns=columns)
if 'BV' in frame: frame['date'] = to_datetime(frame['date'],
frame.drop('BV', 1, inplace=True) unit='ms',
frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True) utc=True,
infer_datetime_format=True)
frame.sort_values('date', inplace=True) frame.sort_values('date', inplace=True)
return frame return frame

View File

@ -2,8 +2,11 @@
""" 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
from datetime import datetime
import ccxt import ccxt
import arrow import arrow
@ -11,10 +14,12 @@ from cachetools import cached, TTLCache
from freqtrade import OperationalException, DependencyException, NetworkException from freqtrade import OperationalException, DependencyException, NetworkException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current selected exchange # Current selected exchange
_API: ccxt.Exchange = None _API: ccxt.Exchange = None
_CONF: dict = {} _CONF: dict = {}
API_RETRY_COUNT = 4 API_RETRY_COUNT = 4
@ -23,7 +28,6 @@ _DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
_TICKER_CACHE: dict = {} _TICKER_CACHE: dict = {}
def retrier(f): def retrier(f):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT) count = kwargs.pop('count', API_RETRY_COUNT)
@ -85,6 +89,10 @@ 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.load_markets() markets = _API.load_markets()
except ccxt.BaseError as e: except ccxt.BaseError as e:
@ -314,7 +322,15 @@ def get_markets() -> List[dict]:
def get_name() -> str: def get_name() -> str:
return _API.name return _API.__class__.__name__.capitalize()
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:

View File

@ -193,13 +193,12 @@ class FreqtradeBot(object):
:return: List of pairs :return: List of pairs
""" """
summaries = sorted( summaries = sorted(
(s for s in exchange.get_market_summaries() if (v for s, v in exchange.get_market_summaries().items() if v['symbol'].endswith(base_currency)),
s['MarketName'].startswith(base_currency)), key=lambda v: v.get('info').get(key) or 0.0,
key=lambda s: s.get(key) or 0.0,
reverse=True reverse=True
) )
return [s['MarketName'].replace('-', '_') for s in summaries] return [s['symbol'] for s in summaries]
def _refresh_whitelist(self, whitelist: List[str]) -> List[str]: def _refresh_whitelist(self, whitelist: List[str]) -> List[str]:
""" """
@ -212,15 +211,15 @@ class FreqtradeBot(object):
sanitized_whitelist = whitelist sanitized_whitelist = whitelist
health = exchange.get_wallet_health() health = exchange.get_wallet_health()
known_pairs = set() known_pairs = set()
for status in health: for symbol, status in health.items():
pair = '{}_{}'.format(self.config['stake_currency'], status['Currency']) pair = f"{status['base']}/{self.config['stake_currency']}"
# pair is not int the generated dynamic market, or in the blacklist ... ignore it # pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.config['exchange'].get('pair_blacklist', []): if pair not in whitelist or pair in self.config['exchange'].get('pair_blacklist', []):
continue continue
# else the pair is valid # else the pair is valid
known_pairs.add(pair) known_pairs.add(pair)
# Market is not active # Market is not active
if not status['IsActive']: if not status['active']:
sanitized_whitelist.remove(pair) sanitized_whitelist.remove(pair)
self.logger.info( self.logger.info(
'Ignoring %s from whitelist (reason: %s).', 'Ignoring %s from whitelist (reason: %s).',
@ -313,7 +312,7 @@ class FreqtradeBot(object):
pair=pair, pair=pair,
stake_amount=stake_amount, stake_amount=stake_amount,
amount=amount, amount=amount,
fee=exchange.get_fee(), fee=exchange.get_fee_maker(),
open_rate=buy_limit, open_rate=buy_limit,
open_date=datetime.utcnow(), open_date=datetime.utcnow(),
exchange=exchange.get_name().upper(), exchange=exchange.get_name().upper(),

View File

@ -72,3 +72,11 @@ def file_dump_json(filename, data) -> None:
""" """
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
json.dump(data, fp, default=str) json.dump(data, fp, default=str)
def format_ms_time(date: str) -> str:
"""
convert MS date to readable format.
: epoch-string in ms
"""
return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S')

View File

@ -35,7 +35,7 @@ def load_tickerdata_file(
""" """
path = make_testdata_path(datadir) path = make_testdata_path(datadir)
file = os.path.join(path, '{pair}-{ticker_interval}.json'.format( file = os.path.join(path, '{pair}-{ticker_interval}.json'.format(
pair=pair, pair=pair.replace('/', '_'),
ticker_interval=ticker_interval, ticker_interval=ticker_interval,
)) ))
gzipfile = file + '.gz' gzipfile = file + '.gz'
@ -126,7 +126,7 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
interval interval
) )
filepair = pair.replace("-", "_") filepair = pair.replace("/", "_")
filename = os.path.join(path, '{pair}-{interval}.json'.format( filename = os.path.join(path, '{pair}-{interval}.json'.format(
pair=filepair, pair=filepair,
interval=interval, interval=interval,
@ -135,8 +135,8 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
if os.path.isfile(filename): if os.path.isfile(filename):
with open(filename, "rt") as file: with open(filename, "rt") as file:
data = json.load(file) data = json.load(file)
logger.debug("Current Start: %s", data[1]['T']) logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]))
logger.debug("Current End: %s", data[-1:][0]['T']) logger.debug("Current End: %s", misc.format_ms_time(data[-1:][0][0]))
else: else:
data = [] data = []
logger.debug("Current Start: None") logger.debug("Current Start: None")
@ -146,9 +146,9 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
for row in new_data: for row in new_data:
if row not in data: if row not in data:
data.append(row) data.append(row)
logger.debug("New Start: %s", data[1]['T']) logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
logger.debug("New End: %s", data[-1:][0]['T']) logger.debug("New End: %s", misc.format_ms_time(data[-1:][0][0]))
data = sorted(data, key=lambda data: data['T']) data = sorted(data, key=lambda data: data[0])
misc.file_dump_json(filename, data) misc.file_dump_json(filename, data)

View File

@ -12,6 +12,7 @@ from pandas import DataFrame, Series
from tabulate import tabulate from tabulate import tabulate
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
import freqtrade.exchange as exchange
from freqtrade import exchange from freqtrade import exchange
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
@ -52,7 +53,11 @@ class Backtesting(object):
self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe
self.populate_buy_trend = self.analyze.populate_buy_trend self.populate_buy_trend = self.analyze.populate_buy_trend
self.populate_sell_trend = self.analyze.populate_sell_trend self.populate_sell_trend = self.analyze.populate_sell_trend
exchange._API = ccxt.bittrex({'key': '', 'secret': ''})
# Reset keys for backtesting
self.config['exchange']['key'] = ''
self.config['exchange']['secret'] = ''
exchange.init(self.config)
@staticmethod @staticmethod
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:

View File

@ -128,32 +128,31 @@ def ticker_sell_down():
@pytest.fixture @pytest.fixture
def health(): def health():
return MagicMock(return_value=[{ return MagicMock(return_value={
'Currency': 'BTC', "ETH/BTC": {
'IsActive': True, 'base': 'ETH',
'active': True,
'LastChecked': '2017-11-13T20:15:00.00', 'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None 'Notice': None
}, { },
'Currency': 'ETH', "TRST/BTC": {
'IsActive': True, 'base': 'TRST',
'active': True,
'LastChecked': '2017-11-13T20:15:00.00', 'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None 'Notice': None
}, { },
'Currency': 'TRST', "SWT/BTC": {
'IsActive': True, 'base': 'SWT',
'active': True,
'LastChecked': '2017-11-13T20:15:00.00', 'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None 'Notice': None
}, { },
'Currency': 'SWT', "BCC/BTC": {
'IsActive': True, 'base': 'BCC',
'active': False,
'LastChecked': '2017-11-13T20:15:00.00', 'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None 'Notice': None
}, { }})
'Currency': 'BCC',
'IsActive': False,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}])
@pytest.fixture @pytest.fixture
@ -336,4 +335,3 @@ def result():
# that inserts a trade of some type and open-status # that inserts a trade of some type and open-status
# return the open-order-id # return the open-order-id
# See tests in rpc/main that could use this # See tests in rpc/main that could use this

View File

@ -334,7 +334,8 @@ def test_get_ticker_history(default_conf, mocker):
type(api_mock).has = has type(api_mock).has = has
api_mock.fetch_ohlcv = MagicMock(return_value=tick) api_mock.fetch_ohlcv = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch('freqtrade.exchange._API.has', {'fetchOHLCV': True})
mocker.patch('freqtrade.exchange._API.fetch_ohlcv', return_value=tick)
# retrieve original ticker # retrieve original ticker
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0]['O'] == 1 assert ticks[0]['O'] == 1

View File

@ -59,7 +59,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
result_message = [ result_message = [
'*Trade ID:* `1`\n' '*Trade ID:* `1`\n'
'*Current Pair:* ' '*Current Pair:* '
'[BTC_ETH](https://www.bittrex.com/Market/Index?MarketName=BTC-ETH)\n' '[ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n'
'*Open Since:* `just now`\n' '*Open Since:* `just now`\n'
'*Amount:* `90.99181074`\n' '*Amount:* `90.99181074`\n'
'*Open Rate:* `0.00001099`\n' '*Open Rate:* `0.00001099`\n'
@ -70,7 +70,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
'*Open Order:* `(LIMIT_BUY rem=0.00000000)`' '*Open Order:* `(LIMIT_BUY rem=0.00000000)`'
] ]
assert result == result_message assert result == result_message
assert trade.find('[BTC_ETH]') >= 0 assert trade.find('[ETH/BTC]') >= 0
def test_rpc_status_table(default_conf, ticker, mocker) -> None: def test_rpc_status_table(default_conf, ticker, mocker) -> None:
@ -102,7 +102,7 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None:
freqtradebot.create_trade() freqtradebot.create_trade()
(error, result) = rpc.rpc_status_table() (error, result) = rpc.rpc_status_table()
assert 'just now' in result['Since'].all() assert 'just now' in result['Since'].all()
assert 'BTC_ETH' in result['Pair'].all() assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all() assert '-0.59%' in result['Profit'].all()
@ -214,7 +214,7 @@ def test_rpc_trade_statistics(
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] == '0:00:00' assert stats['avg_duration'] == '0:00:00'
assert stats['best_pair'] == 'BTC_ETH' assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2) assert prec_satoshi(stats['best_rate'], 6.2)
@ -274,7 +274,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] == '0:00:00' assert stats['avg_duration'] == '0:00:00'
assert stats['best_pair'] == 'BTC_ETH' assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2) assert prec_satoshi(stats['best_rate'], 6.2)
@ -509,7 +509,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order,
(error, res) = rpc.rpc_performance() (error, res) = rpc.rpc_performance()
assert not error assert not error
assert len(res) == 1 assert len(res) == 1
assert res[0]['pair'] == 'BTC_ETH' assert res[0]['pair'] == 'ETH/BTC'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 6.2) assert prec_satoshi(res[0]['profit'], 6.2)

View File

@ -248,7 +248,8 @@ def test_status(default_conf, update, mocker, ticker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker get_ticker=ticker,
get_pair_detail_url=MagicMock()
) )
msg_mock = MagicMock() msg_mock = MagicMock()
status_table = MagicMock() status_table = MagicMock()
@ -319,7 +320,7 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
telegram._status(bot=MagicMock(), update=update) telegram._status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0] assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
def test_status_table_handle(default_conf, update, ticker, mocker) -> None: def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
@ -369,7 +370,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
assert int(fields[0]) == 1 assert int(fields[0]) == 1
assert fields[1] == 'BTC_ETH' assert fields[1] == 'ETH/BTC'
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -387,7 +388,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order,
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker get_ticker=ticker,
get_pair_detail_url=MagicMock()
) )
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -541,7 +543,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
assert '*Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0] assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
def test_telegram_balance_handle(default_conf, update, mocker) -> None: def test_telegram_balance_handle(default_conf, update, mocker) -> None:
@ -779,7 +781,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker)
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
@ -822,7 +824,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
@ -838,6 +840,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None:
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -942,7 +945,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order,
telegram._performance(bot=MagicMock(), update=update) telegram._performance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>BTC_ETH\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
def test_performance_handle_invalid(default_conf, update, mocker) -> None: def test_performance_handle_invalid(default_conf, update, mocker) -> None:

View File

@ -12,23 +12,25 @@ def whitelist_conf():
config['stake_currency'] = 'BTC' config['stake_currency'] = 'BTC'
config['exchange']['pair_whitelist'] = [ config['exchange']['pair_whitelist'] = [
'BTC_ETH', 'ETH/BTC',
'BTC_TKN', 'TKN/BTC',
'BTC_TRST', 'TRST/BTC',
'BTC_SWT', 'SWT/BTC',
'BTC_BCC' 'BCC/BTC'
] ]
config['exchange']['pair_blacklist'] = [ config['exchange']['pair_blacklist'] = [
'BTC_BLK' 'BLK/BTC'
] ]
return config return config
def get_market_summaries(): def get_market_summaries():
return [{ return {
'MarketName': 'BTC-TKN', 'TKN/BTC': {
'symbol': 'TKN/BTC',
'info': {
'High': 0.00000919, 'High': 0.00000919,
'Low': 0.00000820, 'Low': 0.00000820,
'Volume': 74339.61396015, 'Volume': 74339.61396015,
@ -42,8 +44,11 @@ def get_market_summaries():
'PrevDay': 0.00000821, 'PrevDay': 0.00000821,
'Created': '2014-03-20T06:00:00', 'Created': '2014-03-20T06:00:00',
'DisplayMarketName': '' 'DisplayMarketName': ''
}, { }
'MarketName': 'BTC-ETH', },
'ETH/BTC': {
'symbol': 'ETH/BTC',
'info': {
'High': 0.00000072, 'High': 0.00000072,
'Low': 0.00000001, 'Low': 0.00000001,
'Volume': 166340678.42280999, 'Volume': 166340678.42280999,
@ -57,8 +62,11 @@ def get_market_summaries():
'PrevDay': 0.00000002, 'PrevDay': 0.00000002,
'Created': '2014-05-30T07:57:49.637', 'Created': '2014-05-30T07:57:49.637',
'DisplayMarketName': '' 'DisplayMarketName': ''
}, { }
'MarketName': 'BTC-BLK', },
'BLK/BTC': {
'symbol': 'BLK/BTC',
'info': {
'High': 0.00000072, 'High': 0.00000072,
'Low': 0.00000001, 'Low': 0.00000001,
'Volume': 166340678.42280999, 'Volume': 166340678.42280999,
@ -72,17 +80,19 @@ def get_market_summaries():
'PrevDay': 0.00000002, 'PrevDay': 0.00000002,
'Created': '2014-05-30T07:57:49.637', 'Created': '2014-05-30T07:57:49.637',
'DisplayMarketName': '' 'DisplayMarketName': ''
}] }}
}
def get_health(): def get_health():
return [{'Currency': 'ETH', 'IsActive': True}, return {
{'Currency': 'TKN', 'IsActive': True}, 'ETH/BTC': {'base': 'ETH', 'active': True},
{'Currency': 'BLK', 'IsActive': True}] 'TKN/BTC': {'base': 'TKN', 'active': True},
'BLK/BTC': {'base': 'BLK', 'active': True}}
def get_health_empty(): def get_health_empty():
return [] return {}
def test_refresh_market_pair_not_in_whitelist(mocker): def test_refresh_market_pair_not_in_whitelist(mocker):
@ -92,10 +102,10 @@ def test_refresh_market_pair_not_in_whitelist(mocker):
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health) mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health)
refreshedwhitelist = freqtradebot._refresh_whitelist( refreshedwhitelist = freqtradebot._refresh_whitelist(
conf['exchange']['pair_whitelist'] + ['BTC_XXX'] conf['exchange']['pair_whitelist'] + ['XXX/BTC']
) )
# List ordered by BaseVolume # List ordered by BaseVolume
whitelist = ['BTC_ETH', 'BTC_TKN'] whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed # Ensure all except those in whitelist are removed
assert whitelist == refreshedwhitelist assert whitelist == refreshedwhitelist
@ -108,7 +118,7 @@ def test_refresh_whitelist(mocker):
refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist']) refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist'])
# List ordered by BaseVolume # List ordered by BaseVolume
whitelist = ['BTC_ETH', 'BTC_TKN'] whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed # Ensure all except those in whitelist are removed
assert whitelist == refreshedwhitelist assert whitelist == refreshedwhitelist
@ -123,7 +133,7 @@ def test_refresh_whitelist_dynamic(mocker):
) )
# argument: use the whitelist dynamically by exchange-volume # argument: use the whitelist dynamically by exchange-volume
whitelist = ['BTC_TKN', 'BTC_ETH'] whitelist = ['TKN/BTC', 'ETH/BTC']
refreshedwhitelist = freqtradebot._refresh_whitelist( refreshedwhitelist = freqtradebot._refresh_whitelist(
freqtradebot._gen_pair_whitelist(conf['stake_currency']) freqtradebot._gen_pair_whitelist(conf['stake_currency'])

View File

@ -213,15 +213,15 @@ def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) ->
# Test to retrieved BTC sorted on BaseVolume # Test to retrieved BTC sorted on BaseVolume
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC') whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
assert whitelist == ['BTC_ZCL', 'BTC_ZEC', 'BTC_XZC', 'BTC_XWC'] assert whitelist == ['ZCL/BTC', 'ZEC/BTC', 'XZC/BTC', 'XWC/BTC']
# Test to retrieved BTC sorted on OpenBuyOrders # Test to retrieved BTC sorted on OpenBuyOrders
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders') whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders')
assert whitelist == ['BTC_XWC', 'BTC_ZCL', 'BTC_ZEC', 'BTC_XZC'] assert whitelist == ['XWC/BTC', 'ZCL/BTC', 'ZEC/BTC', 'XZC/BTC']
# Test with USDT sorted on BaseVolume # Test with USDT sorted on BaseVolume
whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT') whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT')
assert whitelist == ['USDT_XRP', 'USDT_XVG', 'USDT_XMR', 'USDT_ZEC'] assert whitelist == ['XRP/USDT', 'XVG/USDT', 'XMR/USDT', 'ZEC/USDT']
# Test with ETH (our fixture does not have ETH, but Bittrex returns them) # Test with ETH (our fixture does not have ETH, but Bittrex returns them)
whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH') whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH')
@ -330,8 +330,8 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ["BTC_ETH"] conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
conf['exchange']['pair_blacklist'] = ["BTC_ETH"] conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
freqtrade.create_trade() freqtrade.create_trade()
@ -355,8 +355,8 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) ->
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ["BTC_ETH"] conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
conf['exchange']['pair_blacklist'] = ["BTC_ETH"] conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
freqtrade.create_trade() freqtrade.create_trade()
@ -790,7 +790,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
trade_buy = Trade( trade_buy = Trade(
pair='BTC_ETH', pair='ETH/BTC',
open_rate=0.00001099, open_rate=0.00001099,
exchange='BITTREX', exchange='BITTREX',
open_order_id='123456789', open_order_id='123456789',
@ -829,7 +829,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
trade_sell = Trade( trade_sell = Trade(
pair='BTC_ETH', pair='ETH/BTC',
open_rate=0.00001099, open_rate=0.00001099,
exchange='BITTREX', exchange='BITTREX',
open_order_id='123456789', open_order_id='123456789',
@ -868,7 +868,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
trade_buy = Trade( trade_buy = Trade(
pair='BTC_ETH', pair='ETH/BTC',
open_rate=0.00001099, open_rate=0.00001099,
exchange='BITTREX', exchange='BITTREX',
open_order_id='123456789', open_order_id='123456789',
@ -915,7 +915,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
trade_buy = Trade( trade_buy = Trade(
pair='BTC_ETH', pair='ETH/BTC',
open_rate=0.00001099, open_rate=0.00001099,
exchange='BITTREX', exchange='BITTREX',
open_order_id='123456789', open_order_id='123456789',
@ -928,7 +928,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
Trade.session.add(trade_buy) Trade.session.add(trade_buy)
regexp = re.compile( regexp = re.compile(
'Cannot query order for Trade(id=1, pair=BTC_ETH, amount=90.99181073, ' 'Cannot query order for Trade(id=1, pair=ETH/BTC, amount=90.99181073, '
'open_rate=0.00001099, open_since=10 hours ago) due to Traceback (most ' 'open_rate=0.00001099, open_since=10 hours ago) due to Traceback (most '
'recent call last):\n.*' 'recent call last):\n.*'
) )
@ -1021,7 +1021,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert 'Profit' in rpc_mock.call_args_list[-1][0][0] assert 'Profit' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
@ -1061,7 +1061,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
@ -1100,7 +1100,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
@ -1140,7 +1140,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker,
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]

View File

@ -145,7 +145,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order): def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -167,7 +167,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
def test_calc_close_trade_price_exception(limit_buy_order): def test_calc_close_trade_price_exception(limit_buy_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -180,7 +180,7 @@ def test_calc_close_trade_price_exception(limit_buy_order):
def test_update_open_order(limit_buy_order): def test_update_open_order(limit_buy_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=1.00, stake_amount=1.00,
fee=0.1, fee=0.1,
exchange='bittrex', exchange='bittrex',
@ -202,7 +202,7 @@ def test_update_open_order(limit_buy_order):
def test_update_invalid_order(limit_buy_order): def test_update_invalid_order(limit_buy_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=1.00, stake_amount=1.00,
fee=0.1, fee=0.1,
exchange='bittrex', exchange='bittrex',
@ -214,7 +214,7 @@ def test_update_invalid_order(limit_buy_order):
def test_calc_open_trade_price(limit_buy_order): def test_calc_open_trade_price(limit_buy_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -231,7 +231,7 @@ def test_calc_open_trade_price(limit_buy_order):
def test_calc_close_trade_price(limit_buy_order, limit_sell_order): def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -252,7 +252,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
def test_calc_profit(limit_buy_order, limit_sell_order): def test_calc_profit(limit_buy_order, limit_sell_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -282,7 +282,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order):
def test_calc_profit_percent(limit_buy_order, limit_sell_order): def test_calc_profit_percent(limit_buy_order, limit_sell_order):
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
fee=0.0025, fee=0.0025,
exchange='bittrex', exchange='bittrex',
@ -309,35 +309,35 @@ def test_clean_dry_run_db(default_conf):
# Simulate dry_run entries # Simulate dry_run entries
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
fee=0.0025, fee=0.0025,
open_rate=0.123, open_rate=0.123,
exchange='BITTREX', exchange='bittrex',
open_order_id='dry_run_buy_12345' open_order_id='dry_run_buy_12345'
) )
Trade.session.add(trade) Trade.session.add(trade)
trade = Trade( trade = Trade(
pair='BTC_ETC', pair='ETC/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
fee=0.0025, fee=0.0025,
open_rate=0.123, open_rate=0.123,
exchange='BITTREX', exchange='bittrex',
open_order_id='dry_run_sell_12345' open_order_id='dry_run_sell_12345'
) )
Trade.session.add(trade) Trade.session.add(trade)
# Simulate prod entry # Simulate prod entry
trade = Trade( trade = Trade(
pair='BTC_ETC', pair='ETC/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
fee=0.0025, fee=0.0025,
open_rate=0.123, open_rate=0.123,
exchange='BITTREX', exchange='bittrex',
open_order_id='prod_buy_12345' open_order_id='prod_buy_12345'
) )
Trade.session.add(trade) Trade.session.add(trade)

View File

@ -1,4 +1,4 @@
python-bittrex==0.3.0 ccxt==1.11.149
SQLAlchemy==1.2.5 SQLAlchemy==1.2.5
python-telegram-bot==10.0.1 python-telegram-bot==10.0.1
arrow==0.12.1 arrow==0.12.1

View File

@ -55,7 +55,7 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
if args.live: if args.live:
logger.info('Downloading pair.') logger.info('Downloading pair.')
# Init Bittrex to use public API # Init Bittrex to use public API
exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) exchange.init({'key': '', 'secret': ''})
tickers[pair] = exchange.get_ticker_history(pair, tick_interval) tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
else: else:
tickers = optimize.load_data( tickers = optimize.load_data(

View File

@ -21,7 +21,7 @@ setup(name='freqtrade',
setup_requires=['pytest-runner'], setup_requires=['pytest-runner'],
tests_require=['pytest', 'pytest-mock', 'pytest-cov'], tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
install_requires=[ install_requires=[
'python-bittrex', 'ccxt',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot', 'python-telegram-bot',
'arrow', 'arrow',