From 9b4c0f01f2b19ad556f337d290e1823e8f0014f7 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 26 Dec 2017 08:05:49 +0200 Subject: [PATCH 01/11] more unit tests for backtesting --- freqtrade/tests/test_optimize_backtesting.py | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_optimize_backtesting.py b/freqtrade/tests/test_optimize_backtesting.py index 558cfd8d1..99f95d9bf 100644 --- a/freqtrade/tests/test_optimize_backtesting.py +++ b/freqtrade/tests/test_optimize_backtesting.py @@ -1,12 +1,36 @@ # pragma pylint: disable=missing-docstring,W0212 import os +import pandas as pd from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex -from freqtrade.optimize.backtesting import backtest +from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe from freqtrade.optimize.__init__ import testdata_path, download_pairs, download_backtesting_testdata +def test_generate_text_table(): + results = pd.DataFrame( + { + 'currency': ['BTC_ETH', 'BTC_ETH'], + 'profit_percent': [0.1, 0.2], + 'profit_BTC': [0.2, 0.4], + 'duration': [10, 30] + } + ) + assert generate_text_table({'BTC_ETH': {}}, results, 'BTC', 5) == ( + 'pair buy count avg profit total profit avg duration\n' + '------- ----------- ------------ -------------- --------------\n' + 'BTC_ETH 2 15.00% 0.60000000 BTC 100\n' + 'TOTAL 2 15.00% 0.60000000 BTC 100') + + +def test_get_timeframe(): + data = optimize.load_data(ticker_interval=1, pairs=['BTC_UNITEST']) + min_date, max_date = get_timeframe(data) + assert min_date.isoformat() == '2017-11-04T23:02:00+00:00' + assert max_date.isoformat() == '2017-11-14T22:59:00+00:00' + + def test_backtest(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) From d61d88559cae01b42b4aacf8c99ce56743efed9d Mon Sep 17 00:00:00 2001 From: Jean Baptiste LE STANG Date: Wed, 27 Dec 2017 21:06:05 +0100 Subject: [PATCH 02/11] Fixing daily profit, taking into account the time part of the date (removing it in fact) --- freqtrade/rpc/telegram.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 79290d159..4661459b1 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -220,29 +220,28 @@ def _daily(bot: Bot, update: Update) -> None: :param update: message update :return: None """ - today = datetime.utcnow().toordinal() + today = datetime.utcnow().date() profit_days = {} try: timescale = int(update.message.text.replace('/daily', '').strip()) except (TypeError, ValueError): - timescale = 5 + timescale = 7 if not (isinstance(timescale, int) and timescale > 0): send_msg('*Daily [n]:* `must be an integer greater than 0`', bot=bot) return for day in range(0, timescale): - # need to query between day+1 and day-1 - nextdate = date.fromordinal(today - day + 1) - prevdate = date.fromordinal(today - day - 1) + profitday = today - timedelta(days=day) trades = Trade.query \ .filter(Trade.is_open.is_(False)) \ - .filter(between(Trade.close_date, prevdate, nextdate)) \ + .filter(Trade.close_date >= profitday)\ + .filter(Trade.close_date < (profitday + timedelta(days=1)))\ .order_by(Trade.close_date)\ .all() curdayprofit = sum(trade.calc_profit() for trade in trades) - profit_days[date.fromordinal(today - day)] = format(curdayprofit, '.8f') + profit_days[profitday] = format(curdayprofit, '.8f') stats = [ [ From 8537e9f40f0bea3e01221e385b5da97a22de2280 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Wed, 27 Dec 2017 21:33:42 +0100 Subject: [PATCH 03/11] CI flake8 error --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4661459b1..7636c2b8a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1,12 +1,12 @@ import logging import re from decimal import Decimal -from datetime import timedelta, date, datetime +from datetime import timedelta, datetime from typing import Callable, Any import arrow from pandas import DataFrame -from sqlalchemy import and_, func, text, between +from sqlalchemy import and_, func, text from tabulate import tabulate from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup from telegram.error import NetworkError, TelegramError From ae0a1436e2a7bbcd5903e558f0dec87ba398b161 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 26 Dec 2017 18:54:14 +0200 Subject: [PATCH 04/11] match test files to prod files for backtesting/hyperopt --- .../test_backtesting.py} | 0 .../{test_optimize_hyperopt.py => optimize/test_hyperopt.py} | 0 .../test_hyperopt_config.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename freqtrade/tests/{test_optimize_backtesting.py => optimize/test_backtesting.py} (100%) rename freqtrade/tests/{test_optimize_hyperopt.py => optimize/test_hyperopt.py} (100%) rename freqtrade/tests/{test_optimize_hyperopt_config.py => optimize/test_hyperopt_config.py} (100%) diff --git a/freqtrade/tests/test_optimize_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py similarity index 100% rename from freqtrade/tests/test_optimize_backtesting.py rename to freqtrade/tests/optimize/test_backtesting.py diff --git a/freqtrade/tests/test_optimize_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py similarity index 100% rename from freqtrade/tests/test_optimize_hyperopt.py rename to freqtrade/tests/optimize/test_hyperopt.py diff --git a/freqtrade/tests/test_optimize_hyperopt_config.py b/freqtrade/tests/optimize/test_hyperopt_config.py similarity index 100% rename from freqtrade/tests/test_optimize_hyperopt_config.py rename to freqtrade/tests/optimize/test_hyperopt_config.py From 7b0beb0afa0de4beb545452431499c77162db5f9 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 26 Dec 2017 09:59:38 +0200 Subject: [PATCH 05/11] cleanups --- freqtrade/optimize/__init__.py | 10 +++------- freqtrade/optimize/backtesting.py | 4 ++-- freqtrade/optimize/hyperopt.py | 14 +++++--------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index a77576a27..ac077506a 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -4,11 +4,9 @@ import logging import json import os from typing import Optional, List, Dict +from pandas import DataFrame from freqtrade.exchange import get_ticker_history from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf - -from pandas import DataFrame - from freqtrade.analyze import populate_indicators, parse_ticker_dataframe logger = logging.getLogger(__name__) @@ -50,10 +48,8 @@ def load_data(ticker_interval: int = 5, pairs: Optional[List[str]] = None, def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: """Creates a dataframe and populates indicators for given ticker data""" - processed = {} - for pair, pair_data in tickerdata.items(): - processed[pair] = populate_indicators(parse_ticker_dataframe(pair_data)) - return processed + return {pair: populate_indicators(parse_ticker_dataframe(pair_data)) + for pair, pair_data in tickerdata.items()} def testdata_path() -> str: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2684d2576..984ca3e72 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -111,14 +111,14 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame], if min_roi_reached(trade, row2.close, row2.date) or row2.sell == 1: current_profit_percent = trade.calc_profit_percent(rate=row2.close) - current_profit_BTC = trade.calc_profit(rate=row2.close) + current_profit_btc = trade.calc_profit(rate=row2.close) lock_pair_until = row2.Index trades.append( ( pair, current_profit_percent, - current_profit_BTC, + current_profit_btc, row2.Index - row.Index ) ) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index dcdafc946..686f15a17 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -118,8 +118,7 @@ def optimizer(params): backtesting.populate_buy_trend = buy_strategy_generator(params) results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED) - - result = format_results(results) + result_explanation = format_results(results) total_profit = results.profit_percent.sum() trade_count = len(results.index) @@ -135,20 +134,17 @@ def optimizer(params): loss = trade_loss + profit_loss _CURRENT_TRIES += 1 - result_data = { + log_results({ 'loss': loss, 'current_tries': _CURRENT_TRIES, 'total_tries': TOTAL_TRIES, - 'result': result, - } - log_results(result_data) + 'result': result_explanation, + }) return { 'loss': loss, 'status': STATUS_OK, - 'result': result, - 'total_profit': total_profit, - 'avg_profit': results.profit_percent.mean() * 100.0, + 'result': result_explanation, } From 7f44ba6df48579659d379d2f7614d67fb91f221d Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 26 Dec 2017 10:08:10 +0200 Subject: [PATCH 06/11] unit tests for optimize.hyperopt --- freqtrade/optimize/hyperopt.py | 14 ++-- freqtrade/tests/optimize/test_hyperopt.py | 81 +++++++++++++++++++++-- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 686f15a17..aeb7c1456 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -25,12 +25,10 @@ logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING) logger = logging.getLogger(__name__) - # set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data TARGET_TRADES = 1100 TOTAL_TRIES = None _CURRENT_TRIES = 0 - CURRENT_BEST_LOSS = 100 # this is expexted avg profit * expected trade count @@ -111,6 +109,13 @@ def log_results(results): sys.stdout.flush() +def calculate_loss(total_profit: float, trade_count: int): + """ objective function, returns smaller number for more optimal results """ + trade_loss = 1 - 0.35 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.2) + profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) + return trade_loss + profit_loss + + def optimizer(params): global _CURRENT_TRIES @@ -129,9 +134,8 @@ def optimizer(params): 'loss': float('inf') } - trade_loss = 1 - 0.35 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.2) - profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) - loss = trade_loss + profit_loss + loss = calculate_loss(total_profit, trade_count) + _CURRENT_TRIES += 1 log_results({ diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index a8bfe7dd4..104b3bfdd 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -1,6 +1,79 @@ -# pragma pylint: disable=missing-docstring,W0212 +# pragma pylint: disable=missing-docstring,W0212,C0103 + +from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_MAX_PROFIT, start, \ + log_results -def test_optimizer(default_conf, mocker): - # TODO: implement test - pass +def test_loss_calculation_prefer_correct_trade_count(): + correct = calculate_loss(1, TARGET_TRADES) + over = calculate_loss(1, TARGET_TRADES + 100) + under = calculate_loss(1, TARGET_TRADES - 100) + assert over > correct + assert under > correct + + +def test_loss_calculation_has_limited_profit(): + correct = calculate_loss(EXPECTED_MAX_PROFIT, TARGET_TRADES) + over = calculate_loss(EXPECTED_MAX_PROFIT * 2, TARGET_TRADES) + under = calculate_loss(EXPECTED_MAX_PROFIT / 2, TARGET_TRADES) + assert over == correct + assert under > correct + + +def create_trials(mocker): + return mocker.Mock( + results=[{ + 'loss': 1, + 'result': 'foo' + }] + ) + + +def test_start_calls_fmin(mocker): + mocker.patch('freqtrade.optimize.hyperopt.Trials', return_value=create_trials(mocker)) + mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.load_data') + mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) + + args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False) + start(args) + + mock_fmin.assert_called_once() + + +def test_start_uses_mongotrials(mocker): + mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', + return_value=create_trials(mocker)) + mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.load_data') + mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) + + args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True) + start(args) + + mock_mongotrials.assert_called_once() + + +def test_log_results_if_loss_improves(mocker): + logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info') + global CURRENT_BEST_LOSS + CURRENT_BEST_LOSS = 2 + log_results({ + 'loss': 1, + 'current_tries': 1, + 'total_tries': 2, + 'result': 'foo' + }) + + logger.assert_called_once() + + +def test_no_log_if_loss_does_not_improve(mocker): + logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info') + global CURRENT_BEST_LOSS + CURRENT_BEST_LOSS = 2 + log_results({ + 'loss': 3, + }) + + assert not logger.called From a36fd00f6a76e54da1135b354f52aa39e4f93608 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 26 Dec 2017 18:39:23 +0200 Subject: [PATCH 07/11] also print dot when hyperopt eval result is fail --- freqtrade/optimize/hyperopt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index aeb7c1456..d0d0916f8 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -129,6 +129,7 @@ def optimizer(params): trade_count = len(results.index) if trade_count == 0: + print('.', end='') return { 'status': STATUS_FAIL, 'loss': float('inf') From 965616b2144e1f32306253ba51f1c02c5ba0576c Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Thu, 28 Dec 2017 10:11:32 +0100 Subject: [PATCH 08/11] Update sqlalchemy from 1.1.15 to 1.2.0 (#245) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b37a15c79..9d6c3e4d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ python-bittrex==0.2.2 -SQLAlchemy==1.1.15 +SQLAlchemy==1.2.0 python-telegram-bot==9.0.0 arrow==0.12.0 cachetools==2.0.1 From 847dde0d6517ecb6b1895cebefe2359c95aea8f1 Mon Sep 17 00:00:00 2001 From: kryofly Date: Fri, 29 Dec 2017 00:00:30 +0100 Subject: [PATCH 09/11] execute sell if get_signal OR ROI reached --- freqtrade/main.py | 15 +++++++++------ freqtrade/tests/test_main.py | 37 ++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 77f956aba..ad7cc7289 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -179,17 +179,20 @@ def handle_trade(trade: Trade) -> bool: current_rate = exchange.get_ticker(trade.pair)['bid'] # Check if minimal roi has been reached - if not min_roi_reached(trade, current_rate, datetime.utcnow()): - return False + if min_roi_reached(trade, current_rate, datetime.utcnow()): + logger.debug('Executing sell due to ROI ...') + execute_sell(trade, current_rate) + return True # Check if sell signal has been enabled and triggered if _CONF.get('experimental', {}).get('use_sell_signal'): logger.debug('Checking sell_signal ...') - if not get_signal(trade.pair, SignalType.SELL): - return False + if get_signal(trade.pair, SignalType.SELL): + logger.debug('Executing sell due to sell signal ...') + execute_sell(trade, current_rate) + return True - execute_sell(trade, current_rate) - return True + return False def get_target_bid(ticker: Dict[str, float]) -> float: diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 037d6f836..2d5263159 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -216,8 +216,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): assert trade.calc_profit() == 0.00006217 assert trade.close_date is not None - -def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog): +def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) @@ -235,10 +234,44 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker trade = Trade.query.first() trade.is_open = True + # FIX: sniffing logs, suggest handle_trade should not execute_sell + # instead that responsibility should be moved out of handle_trade(), + # we might just want to check if we are in a sell condition without + # executing + # if ROI is reached we must sell + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) + assert handle_trade(trade) + assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples + # if ROI is reached we must sell even if sell-signal is not signalled + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + assert handle_trade(trade) + assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples + +def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog): + default_conf.update({'experimental': {'use_sell_signal': True}}) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy')) + mocker.patch('freqtrade.main.min_roi_reached', return_value=False) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + trade = Trade.query.first() + trade.is_open = True + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) value_returned = handle_trade(trade) assert ('freqtrade', logging.DEBUG, 'Checking sell_signal ...') in caplog.record_tuples assert value_returned is False + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + assert handle_trade(trade) + assert ('freqtrade', logging.DEBUG, 'Executing sell due to sell signal ...') in caplog.record_tuples def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker): From 0d605d2396920d171d477336c0ee111cafa4936e Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Thu, 28 Dec 2017 00:11:52 -0800 Subject: [PATCH 10/11] Refactor Optimize tests, and add more unit tests --- freqtrade/optimize/__init__.py | 14 +- freqtrade/tests/optimize/test_backtesting.py | 76 +-------- freqtrade/tests/optimize/test_optimize.py | 166 +++++++++++++++++++ freqtrade/tests/test_main.py | 2 +- 4 files changed, 175 insertions(+), 83 deletions(-) create mode 100644 freqtrade/tests/optimize/test_optimize.py diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index ac077506a..1be7ce536 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -87,17 +87,17 @@ def download_backtesting_testdata(pair: str, interval: int = 5) -> bool: )) filepair = pair.replace("-", "_") - filename = os.path.join(path, '{}-{}.json'.format( - filepair, - interval, + filename = os.path.join(path, '{pair}-{interval}.json'.format( + pair=filepair, + interval=interval, )) filename = filename.replace('USDT_BTC', 'BTC_FAKEBULL') if os.path.isfile(filename): with open(filename, "rt") as fp: data = json.load(fp) - logger.debug("Current Start:", data[1]['T']) - logger.debug("Current End: ", data[-1:][0]['T']) + logger.debug("Current Start: {}".format(data[1]['T'])) + logger.debug("Current End: {}".format(data[-1:][0]['T'])) else: data = [] logger.debug("Current Start: None") @@ -107,8 +107,8 @@ def download_backtesting_testdata(pair: str, interval: int = 5) -> bool: for row in new_data: if row not in data: data.append(row) - logger.debug("New Start:", data[1]['T']) - logger.debug("New End: ", data[-1:][0]['T']) + logger.debug("New Start: {}".format(data[1]['T'])) + logger.debug("New End: {}".format(data[-1:][0]['T'])) data = sorted(data, key=lambda data: data['T']) with open(filename, "wt") as fp: diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 99f95d9bf..e3d423b7d 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -1,11 +1,9 @@ # pragma pylint: disable=missing-docstring,W0212 -import os import pandas as pd from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe -from freqtrade.optimize.__init__ import testdata_path, download_pairs, download_backtesting_testdata def test_generate_text_table(): @@ -40,7 +38,7 @@ def test_backtest(default_conf, mocker): assert not results.empty -def test_1min_ticker_interval(default_conf, mocker): +def test_backtest_1min_ticker_interval(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) @@ -48,75 +46,3 @@ def test_1min_ticker_interval(default_conf, mocker): data = optimize.load_data(ticker_interval=1, pairs=['BTC_UNITEST']) results = backtest(default_conf['stake_amount'], optimize.preprocess(data), 1, True) assert not results.empty - - -def test_backtest_with_new_pair(default_conf, ticker_history, mocker): - mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) - mocker.patch.dict('freqtrade.main._CONF', default_conf) - - exchange._API = Bittrex({'key': '', 'secret': ''}) - - optimize.load_data(ticker_interval=1, pairs=['BTC_MEME']) - file = 'freqtrade/tests/testdata/BTC_MEME-1.json' - assert os.path.isfile(file) is True - - # delete file freshly downloaded - if os.path.isfile(file): - os.remove(file) - - -def test_testdata_path(): - assert os.path.join('freqtrade', 'tests', 'testdata') in testdata_path() - - -def test_download_pairs(default_conf, ticker_history, mocker): - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) - mocker.patch.dict('freqtrade.main._CONF', default_conf) - exchange._API = Bittrex({'key': '', 'secret': ''}) - - file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json' - file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json' - file2_1 = 'freqtrade/tests/testdata/BTC_CFI-1.json' - file2_5 = 'freqtrade/tests/testdata/BTC_CFI-5.json' - - assert download_pairs(pairs=['BTC-MEME', 'BTC-CFI']) is True - - assert os.path.isfile(file1_1) is True - assert os.path.isfile(file1_5) is True - assert os.path.isfile(file2_1) is True - assert os.path.isfile(file2_5) is True - - # delete files freshly downloaded - if os.path.isfile(file1_1): - os.remove(file1_1) - - if os.path.isfile(file1_5): - os.remove(file1_5) - - if os.path.isfile(file2_1): - os.remove(file2_1) - - if os.path.isfile(file2_5): - os.remove(file2_5) - - -def test_download_backtesting_testdata(default_conf, ticker_history, mocker): - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) - mocker.patch.dict('freqtrade.main._CONF', default_conf) - exchange._API = Bittrex({'key': '', 'secret': ''}) - - # Download a 1 min ticker file - file1 = 'freqtrade/tests/testdata/BTC_XEL-1.json' - download_backtesting_testdata(pair="BTC-XEL", interval=1) - assert os.path.isfile(file1) is True - - if os.path.isfile(file1): - os.remove(file1) - - # Download a 5 min ticker file - file2 = 'freqtrade/tests/testdata/BTC_STORJ-5.json' - download_backtesting_testdata(pair="BTC-STORJ", interval=5) - assert os.path.isfile(file2) is True - - if os.path.isfile(file2): - os.remove(file2) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py new file mode 100644 index 000000000..aad20567e --- /dev/null +++ b/freqtrade/tests/optimize/test_optimize.py @@ -0,0 +1,166 @@ +# pragma pylint: disable=missing-docstring,W0212 + +import os +import logging +from shutil import copyfile +from freqtrade import exchange, optimize +from freqtrade.exchange import Bittrex +from freqtrade.optimize.__init__ import testdata_path, download_pairs, download_backtesting_testdata + + +def _backup_file(file: str, copy_file: bool = False) -> None: + """ + Backup existing file to avoid deleting the user file + :param file: complete path to the file + :param touch_file: create an empty file in replacement + :return: None + """ + file_swp = file + '.swp' + if os.path.isfile(file): + os.rename(file, file_swp) + + if copy_file: + copyfile(file_swp, file) + + +def _clean_test_file(file: str) -> None: + """ + Backup existing file to avoid deleting the user file + :param file: complete path to the file + :return: None + """ + file_swp = file + '.swp' + # 1. Delete file from the test + if os.path.isfile(file): + os.remove(file) + + # 2. Rollback to the initial file + if os.path.isfile(file_swp): + os.rename(file_swp, file) + + +def test_load_data_5min_ticker(default_conf, ticker_history, mocker, caplog): + mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + + exchange._API = Bittrex({'key': '', 'secret': ''}) + + file = 'freqtrade/tests/testdata/BTC_ETH-5.json' + _backup_file(file, copy_file=True) + optimize.load_data(pairs=['BTC_ETH']) + assert os.path.isfile(file) is True + assert ('freqtrade.optimize', + logging.INFO, + 'Download the pair: "BTC_ETH", Interval: 5 min' + ) not in caplog.record_tuples + _clean_test_file(file) + + +def test_load_data_1min_ticker(default_conf, ticker_history, mocker, caplog): + mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + + exchange._API = Bittrex({'key': '', 'secret': ''}) + + file = 'freqtrade/tests/testdata/BTC_ETH-1.json' + _backup_file(file, copy_file=True) + optimize.load_data(ticker_interval=1, pairs=['BTC_ETH']) + assert os.path.isfile(file) is True + assert ('freqtrade.optimize', + logging.INFO, + 'Download the pair: "BTC_ETH", Interval: 1 min' + ) not in caplog.record_tuples + _clean_test_file(file) + + +def test_load_data_with_new_pair_1min(default_conf, ticker_history, mocker, caplog): + mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + + exchange._API = Bittrex({'key': '', 'secret': ''}) + + file = 'freqtrade/tests/testdata/BTC_MEME-1.json' + _backup_file(file) + optimize.load_data(ticker_interval=1, pairs=['BTC_MEME']) + assert os.path.isfile(file) is True + assert ('freqtrade.optimize', + logging.INFO, + 'Download the pair: "BTC_MEME", Interval: 1 min' + ) in caplog.record_tuples + _clean_test_file(file) + + +def test_testdata_path(): + assert os.path.join('freqtrade', 'tests', 'testdata') in testdata_path() + + +def test_download_pairs(default_conf, ticker_history, mocker): + mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + exchange._API = Bittrex({'key': '', 'secret': ''}) + + file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json' + file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json' + file2_1 = 'freqtrade/tests/testdata/BTC_CFI-1.json' + file2_5 = 'freqtrade/tests/testdata/BTC_CFI-5.json' + + _backup_file(file1_1) + _backup_file(file1_5) + _backup_file(file2_1) + _backup_file(file2_5) + + assert download_pairs(pairs=['BTC-MEME', 'BTC-CFI']) is True + + assert os.path.isfile(file1_1) is True + assert os.path.isfile(file1_5) is True + assert os.path.isfile(file2_1) is True + assert os.path.isfile(file2_5) is True + + # clean files freshly downloaded + _clean_test_file(file1_1) + _clean_test_file(file1_5) + _clean_test_file(file2_1) + _clean_test_file(file2_5) + + +def test_download_pairs_exception(default_conf, ticker_history, mocker, caplog): + mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) + mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata', + side_effect=BaseException('File Error')) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + exchange._API = Bittrex({'key': '', 'secret': ''}) + + file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json' + file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json' + _backup_file(file1_1) + _backup_file(file1_5) + + download_pairs(pairs=['BTC-MEME']) + # clean files freshly downloaded + _clean_test_file(file1_1) + _clean_test_file(file1_5) + assert ('freqtrade.optimize.__init__', + logging.INFO, + 'Failed to download the pair: "BTC-MEME", Interval: 1 min' + ) in caplog.record_tuples + + +def test_download_backtesting_testdata(default_conf, ticker_history, mocker): + mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + exchange._API = Bittrex({'key': '', 'secret': ''}) + + # Download a 1 min ticker file + file1 = 'freqtrade/tests/testdata/BTC_XEL-1.json' + _backup_file(file1) + download_backtesting_testdata(pair="BTC-XEL", interval=1) + assert os.path.isfile(file1) is True + _clean_test_file(file1) + + # Download a 5 min ticker file + file2 = 'freqtrade/tests/testdata/BTC_STORJ-5.json' + _backup_file(file2) + + download_backtesting_testdata(pair="BTC-STORJ", interval=5) + assert os.path.isfile(file2) is True + _clean_test_file(file2) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 037d6f836..f8bad9fa5 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -217,7 +217,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): assert trade.close_date is not None -def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog): +def test_handle_trade_experimental(default_conf, ticker, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) From 3e0458da7dd61cd09e92ab8bd344707422bf07f0 Mon Sep 17 00:00:00 2001 From: kryofly Date: Fri, 29 Dec 2017 09:40:24 +0100 Subject: [PATCH 11/11] flake8 --- freqtrade/tests/test_main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 2d5263159..3d6335572 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -216,6 +216,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): assert trade.calc_profit() == 0.00006217 assert trade.close_date is not None + def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) @@ -247,6 +248,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog) assert handle_trade(trade) assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples + def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) @@ -271,7 +273,8 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker assert value_returned is False mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) assert handle_trade(trade) - assert ('freqtrade', logging.DEBUG, 'Executing sell due to sell signal ...') in caplog.record_tuples + s = 'Executing sell due to sell signal ...' + assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker):