diff --git a/config.json.example b/config.json.example index 2c464a925..6a4f20cd6 100644 --- a/config.json.example +++ b/config.json.example @@ -13,19 +13,19 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "pair_whitelist": [ - "BTC_ETH", - "BTC_LTC", - "BTC_ETC", - "BTC_DASH", - "BTC_ZEC", - "BTC_XLM", - "BTC_NXT", - "BTC_POWR", - "BTC_ADA", - "BTC_XMR" + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "POWR/BTC", + "ADA/BTC", + "XMR/BTC" ], "pair_blacklist": [ - "BTC_DOGE" + "DOGE/BTC" ] }, "experimental": { diff --git a/config_full.json.example b/config_full.json.example index c74b59660..d0f9d1260 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -21,19 +21,19 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "pair_whitelist": [ - "BTC_ETH", - "BTC_LTC", - "BTC_ETC", - "BTC_DASH", - "BTC_ZEC", - "BTC_XLM", - "BTC_NXT", - "BTC_POWR", - "BTC_ADA", - "BTC_XMR" + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "POWR/BTC", + "ADA/BTC", + "XMR/BTC" ], "pair_blacklist": [ - "BTC_DOGE" + "DOGE/BTC" ] }, "experimental": { diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f52de835e..16f8b3b4a 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -21,4 +21,4 @@ class NetworkException(BaseException): Network related error. This could happen when an exchange is congested, unavailable, or the user has networking problems. Usually resolves itself after a time. - """ \ No newline at end of file + """ diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 8bc552d74..e6e8023f0 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -44,12 +44,14 @@ class Analyze(object): :param ticker: See exchange.get_ticker_history :return: DataFrame """ - columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'} - frame = DataFrame(ticker) \ - .rename(columns=columns) - if 'BV' in frame: - frame.drop('BV', 1, inplace=True) - frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True) + cols = ['date', 'open', 'high', 'low', 'close', 'volume'] + frame = DataFrame(ticker, columns=cols) + + frame['date'] = to_datetime(frame['date'], + unit='ms', + utc=True, + infer_datetime_format=True) + frame.sort_values('date', inplace=True) return frame diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 9a7cd08a5..931fd366c 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -2,8 +2,11 @@ """ 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 +from datetime import datetime import ccxt import arrow @@ -11,10 +14,12 @@ from cachetools import cached, TTLCache from freqtrade import OperationalException, DependencyException, NetworkException + logger = logging.getLogger(__name__) # Current selected exchange _API: ccxt.Exchange = None + _CONF: dict = {} API_RETRY_COUNT = 4 @@ -23,7 +28,6 @@ _DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {} _TICKER_CACHE: dict = {} - def retrier(f): def wrapper(*args, **kwargs): count = kwargs.pop('count', API_RETRY_COUNT) @@ -85,6 +89,10 @@ def validate_pairs(pairs: List[str]) -> None: :param pairs: list of pairs :return: None """ + + if not _API.markets: + _API.load_markets() + try: markets = _API.load_markets() except ccxt.BaseError as e: @@ -314,7 +322,15 @@ def get_markets() -> List[dict]: 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: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 29790515e..1cdaff749 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -193,13 +193,12 @@ class FreqtradeBot(object): :return: List of pairs """ summaries = sorted( - (s for s in exchange.get_market_summaries() if - s['MarketName'].startswith(base_currency)), - key=lambda s: s.get(key) or 0.0, + (v for s, v in exchange.get_market_summaries().items() if v['symbol'].endswith(base_currency)), + key=lambda v: v.get('info').get(key) or 0.0, 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]: """ @@ -212,15 +211,15 @@ class FreqtradeBot(object): sanitized_whitelist = whitelist health = exchange.get_wallet_health() known_pairs = set() - for status in health: - pair = '{}_{}'.format(self.config['stake_currency'], status['Currency']) + for symbol, status in health.items(): + pair = f"{status['base']}/{self.config['stake_currency']}" # 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', []): continue # else the pair is valid known_pairs.add(pair) # Market is not active - if not status['IsActive']: + if not status['active']: sanitized_whitelist.remove(pair) self.logger.info( 'Ignoring %s from whitelist (reason: %s).', @@ -313,7 +312,7 @@ class FreqtradeBot(object): pair=pair, stake_amount=stake_amount, amount=amount, - fee=exchange.get_fee(), + fee=exchange.get_fee_maker(), open_rate=buy_limit, open_date=datetime.utcnow(), exchange=exchange.get_name().upper(), diff --git a/freqtrade/misc.py b/freqtrade/misc.py index f5d045c44..bc04d6b88 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -72,3 +72,11 @@ def file_dump_json(filename, data) -> None: """ with open(filename, 'w') as fp: 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') diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index a26744691..30be5dc33 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -35,7 +35,7 @@ def load_tickerdata_file( """ path = make_testdata_path(datadir) file = os.path.join(path, '{pair}-{ticker_interval}.json'.format( - pair=pair, + pair=pair.replace('/', '_'), ticker_interval=ticker_interval, )) gzipfile = file + '.gz' @@ -126,7 +126,7 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> interval ) - filepair = pair.replace("-", "_") + filepair = pair.replace("/", "_") filename = os.path.join(path, '{pair}-{interval}.json'.format( pair=filepair, interval=interval, @@ -135,8 +135,8 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> if os.path.isfile(filename): with open(filename, "rt") as file: data = json.load(file) - logger.debug("Current Start: %s", data[1]['T']) - logger.debug("Current End: %s", data[-1:][0]['T']) + logger.debug("Current Start: %s", misc.format_ms_time(data[1][0])) + logger.debug("Current End: %s", misc.format_ms_time(data[-1:][0][0])) else: data = [] 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: if row not in data: data.append(row) - logger.debug("New Start: %s", data[1]['T']) - logger.debug("New End: %s", data[-1:][0]['T']) - data = sorted(data, key=lambda data: data['T']) + logger.debug("New Start: %s", misc.format_ms_time(data[0][0])) + logger.debug("New End: %s", misc.format_ms_time(data[-1:][0][0])) + data = sorted(data, key=lambda data: data[0]) misc.file_dump_json(filename, data) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a3edfaa7d..6a06c6616 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -12,6 +12,7 @@ from pandas import DataFrame, Series from tabulate import tabulate import freqtrade.optimize as optimize +import freqtrade.exchange as exchange from freqtrade import exchange from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments @@ -52,7 +53,11 @@ class Backtesting(object): self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe self.populate_buy_trend = self.analyze.populate_buy_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 def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 0b0b6d3da..3f14ef87b 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -128,32 +128,31 @@ def ticker_sell_down(): @pytest.fixture def health(): - return MagicMock(return_value=[{ - 'Currency': 'BTC', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'ETH', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'TRST', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'SWT', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'BCC', - 'IsActive': False, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }]) + return MagicMock(return_value={ + "ETH/BTC": { + 'base': 'ETH', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "TRST/BTC": { + 'base': 'TRST', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "SWT/BTC": { + 'base': 'SWT', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "BCC/BTC": { + 'base': 'BCC', + 'active': False, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }}) @pytest.fixture @@ -336,4 +335,3 @@ def result(): # that inserts a trade of some type and open-status # return the open-order-id # See tests in rpc/main that could use this - diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 8805cfab3..88dee0b26 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -334,7 +334,8 @@ def test_get_ticker_history(default_conf, mocker): type(api_mock).has = has api_mock.fetch_ohlcv = MagicMock(return_value=tick) 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 ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) assert ticks[0]['O'] == 1 diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 84b06bee2..bbaa2385f 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -59,7 +59,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None: result_message = [ '*Trade ID:* `1`\n' '*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' '*Amount:* `90.99181074`\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)`' ] 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: @@ -102,7 +102,7 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None: freqtradebot.create_trade() (error, result) = rpc.rpc_status_table() 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() @@ -214,7 +214,7 @@ def test_rpc_trade_statistics( assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' 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) @@ -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['latest_trade_date'] == 'just now' 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) @@ -509,7 +509,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, (error, res) = rpc.rpc_performance() assert not error assert len(res) == 1 - assert res[0]['pair'] == 'BTC_ETH' + assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 86dfd6f41..6dbe6c710 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -248,7 +248,8 @@ def test_status(default_conf, update, mocker, ticker) -> None: mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), - get_ticker=ticker + get_ticker=ticker, + get_pair_detail_url=MagicMock() ) msg_mock = MagicMock() status_table = MagicMock() @@ -319,7 +320,7 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None: telegram._status(bot=MagicMock(), update=update) 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: @@ -369,7 +370,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None: fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 - assert fields[1] == 'BTC_ETH' + assert fields[1] == 'ETH/BTC' assert msg_mock.call_count == 1 @@ -387,7 +388,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), - get_ticker=ticker + get_ticker=ticker, + get_pair_detail_url=MagicMock() ) msg_mock = MagicMock() 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.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: @@ -779,7 +781,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker) assert rpc_mock.call_count == 2 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 '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] @@ -822,7 +824,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m assert rpc_mock.call_count == 2 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 '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] @@ -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) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -942,7 +945,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order, telegram._performance(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert 'BTC_ETH\t6.20% (1)' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC\t6.20% (1)' in msg_mock.call_args_list[0][0][0] def test_performance_handle_invalid(default_conf, update, mocker) -> None: diff --git a/freqtrade/tests/test_acl_pair.py b/freqtrade/tests/test_acl_pair.py index b5f52774d..174f1fde9 100644 --- a/freqtrade/tests/test_acl_pair.py +++ b/freqtrade/tests/test_acl_pair.py @@ -12,77 +12,87 @@ def whitelist_conf(): config['stake_currency'] = 'BTC' config['exchange']['pair_whitelist'] = [ - 'BTC_ETH', - 'BTC_TKN', - 'BTC_TRST', - 'BTC_SWT', - 'BTC_BCC' + 'ETH/BTC', + 'TKN/BTC', + 'TRST/BTC', + 'SWT/BTC', + 'BCC/BTC' ] config['exchange']['pair_blacklist'] = [ - 'BTC_BLK' + 'BLK/BTC' ] return config def get_market_summaries(): - return [{ - 'MarketName': 'BTC-TKN', - 'High': 0.00000919, - 'Low': 0.00000820, - 'Volume': 74339.61396015, - 'Last': 0.00000820, - 'BaseVolume': 1664, - 'TimeStamp': '2014-07-09T07:19:30.15', - 'Bid': 0.00000820, - 'Ask': 0.00000831, - 'OpenBuyOrders': 15, - 'OpenSellOrders': 15, - 'PrevDay': 0.00000821, - 'Created': '2014-03-20T06:00:00', - 'DisplayMarketName': '' - }, { - 'MarketName': 'BTC-ETH', - 'High': 0.00000072, - 'Low': 0.00000001, - 'Volume': 166340678.42280999, - 'Last': 0.00000005, - 'BaseVolume': 42, - 'TimeStamp': '2014-07-09T07:21:40.51', - 'Bid': 0.00000004, - 'Ask': 0.00000005, - 'OpenBuyOrders': 18, - 'OpenSellOrders': 18, - 'PrevDay': 0.00000002, - 'Created': '2014-05-30T07:57:49.637', - 'DisplayMarketName': '' - }, { - 'MarketName': 'BTC-BLK', - 'High': 0.00000072, - 'Low': 0.00000001, - 'Volume': 166340678.42280999, - 'Last': 0.00000005, - 'BaseVolume': 3, - 'TimeStamp': '2014-07-09T07:21:40.51', - 'Bid': 0.00000004, - 'Ask': 0.00000005, - 'OpenBuyOrders': 18, - 'OpenSellOrders': 18, - 'PrevDay': 0.00000002, - 'Created': '2014-05-30T07:57:49.637', - 'DisplayMarketName': '' - }] + return { + 'TKN/BTC': { + 'symbol': 'TKN/BTC', + 'info': { + 'High': 0.00000919, + 'Low': 0.00000820, + 'Volume': 74339.61396015, + 'Last': 0.00000820, + 'BaseVolume': 1664, + 'TimeStamp': '2014-07-09T07:19:30.15', + 'Bid': 0.00000820, + 'Ask': 0.00000831, + 'OpenBuyOrders': 15, + 'OpenSellOrders': 15, + 'PrevDay': 0.00000821, + 'Created': '2014-03-20T06:00:00', + 'DisplayMarketName': '' + } + }, + 'ETH/BTC': { + 'symbol': 'ETH/BTC', + 'info': { + 'High': 0.00000072, + 'Low': 0.00000001, + 'Volume': 166340678.42280999, + 'Last': 0.00000005, + 'BaseVolume': 42, + 'TimeStamp': '2014-07-09T07:21:40.51', + 'Bid': 0.00000004, + 'Ask': 0.00000005, + 'OpenBuyOrders': 18, + 'OpenSellOrders': 18, + 'PrevDay': 0.00000002, + 'Created': '2014-05-30T07:57:49.637', + 'DisplayMarketName': '' + } + }, + 'BLK/BTC': { + 'symbol': 'BLK/BTC', + 'info': { + 'High': 0.00000072, + 'Low': 0.00000001, + 'Volume': 166340678.42280999, + 'Last': 0.00000005, + 'BaseVolume': 3, + 'TimeStamp': '2014-07-09T07:21:40.51', + 'Bid': 0.00000004, + 'Ask': 0.00000005, + 'OpenBuyOrders': 18, + 'OpenSellOrders': 18, + 'PrevDay': 0.00000002, + 'Created': '2014-05-30T07:57:49.637', + 'DisplayMarketName': '' + }} + } def get_health(): - return [{'Currency': 'ETH', 'IsActive': True}, - {'Currency': 'TKN', 'IsActive': True}, - {'Currency': 'BLK', 'IsActive': True}] + return { + 'ETH/BTC': {'base': 'ETH', 'active': True}, + 'TKN/BTC': {'base': 'TKN', 'active': True}, + 'BLK/BTC': {'base': 'BLK', 'active': True}} def get_health_empty(): - return [] + return {} 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) refreshedwhitelist = freqtradebot._refresh_whitelist( - conf['exchange']['pair_whitelist'] + ['BTC_XXX'] + conf['exchange']['pair_whitelist'] + ['XXX/BTC'] ) # List ordered by BaseVolume - whitelist = ['BTC_ETH', 'BTC_TKN'] + whitelist = ['ETH/BTC', 'TKN/BTC'] # Ensure all except those in whitelist are removed assert whitelist == refreshedwhitelist @@ -108,7 +118,7 @@ def test_refresh_whitelist(mocker): refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist']) # List ordered by BaseVolume - whitelist = ['BTC_ETH', 'BTC_TKN'] + whitelist = ['ETH/BTC', 'TKN/BTC'] # Ensure all except those in whitelist are removed assert whitelist == refreshedwhitelist @@ -123,7 +133,7 @@ def test_refresh_whitelist_dynamic(mocker): ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['BTC_TKN', 'BTC_ETH'] + whitelist = ['TKN/BTC', 'ETH/BTC'] refreshedwhitelist = freqtradebot._refresh_whitelist( freqtradebot._gen_pair_whitelist(conf['stake_currency']) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index caf07868c..3792fd88d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -213,15 +213,15 @@ def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) -> # Test to retrieved BTC sorted on BaseVolume 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 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 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) 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['exchange']['pair_whitelist'] = ["BTC_ETH"] - conf['exchange']['pair_blacklist'] = ["BTC_ETH"] + conf['exchange']['pair_whitelist'] = ["ETH/BTC"] + conf['exchange']['pair_blacklist'] = ["ETH/BTC"] freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade.create_trade() @@ -355,8 +355,8 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) -> ) conf = deepcopy(default_conf) - conf['exchange']['pair_whitelist'] = ["BTC_ETH"] - conf['exchange']['pair_blacklist'] = ["BTC_ETH"] + conf['exchange']['pair_whitelist'] = ["ETH/BTC"] + conf['exchange']['pair_blacklist'] = ["ETH/BTC"] freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) 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://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', 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://')) trade_sell = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', 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://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', 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://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', open_order_id='123456789', @@ -928,7 +928,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - Trade.session.add(trade_buy) 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 ' '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 '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 'Profit' 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 '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 '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] @@ -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 '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 '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] @@ -1140,7 +1140,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, assert rpc_mock.call_count == 2 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 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index adf1b37f3..5e80c632b 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -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): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, 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): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, exchange='bittrex', @@ -180,7 +180,7 @@ def test_calc_close_trade_price_exception(limit_buy_order): def test_update_open_order(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=1.00, fee=0.1, exchange='bittrex', @@ -202,7 +202,7 @@ def test_update_open_order(limit_buy_order): def test_update_invalid_order(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=1.00, fee=0.1, exchange='bittrex', @@ -214,7 +214,7 @@ def test_update_invalid_order(limit_buy_order): def test_calc_open_trade_price(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, 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): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, 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): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, 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): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, exchange='bittrex', @@ -309,35 +309,35 @@ def test_clean_dry_run_db(default_conf): # Simulate dry_run entries trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='dry_run_buy_12345' ) Trade.session.add(trade) trade = Trade( - pair='BTC_ETC', + pair='ETC/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='dry_run_sell_12345' ) Trade.session.add(trade) # Simulate prod entry trade = Trade( - pair='BTC_ETC', + pair='ETC/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='prod_buy_12345' ) Trade.session.add(trade) diff --git a/requirements.txt b/requirements.txt index f802f7ae4..6295164bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -python-bittrex==0.3.0 +ccxt==1.11.149 SQLAlchemy==1.2.5 python-telegram-bot==10.0.1 arrow==0.12.1 diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 285ba6d97..5c5e17661 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -55,7 +55,7 @@ def plot_analyzed_dataframe(args: Namespace) -> None: if args.live: logger.info('Downloading pair.') # 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) else: tickers = optimize.load_data( diff --git a/setup.py b/setup.py index 4b0635efa..856a47181 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup(name='freqtrade', setup_requires=['pytest-runner'], tests_require=['pytest', 'pytest-mock', 'pytest-cov'], install_requires=[ - 'python-bittrex', + 'ccxt', 'SQLAlchemy', 'python-telegram-bot', 'arrow',