Merge branch 'develop' into db_keep_orders

This commit is contained in:
Matthias
2020-08-23 10:36:56 +02:00
61 changed files with 1415 additions and 740 deletions

View File

@@ -667,7 +667,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
args = [
"list-hyperopts",
"--hyperopt-path",
str(Path(__file__).parent.parent / "optimize"),
str(Path(__file__).parent.parent / "optimize" / "hyperopts"),
"-1"
]
pargs = get_args(args)
@@ -683,7 +683,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
args = [
"list-hyperopts",
"--hyperopt-path",
str(Path(__file__).parent.parent / "optimize"),
str(Path(__file__).parent.parent / "optimize" / "hyperopts"),
]
pargs = get_args(args)
# pargs['config'] = None
@@ -692,7 +692,6 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
assert "TestHyperoptLegacy" not in captured.out
assert "legacy_hyperopt.py" not in captured.out
assert "DefaultHyperOpt" in captured.out
assert "test_hyperopt.py" in captured.out
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):

View File

@@ -181,7 +181,8 @@ def create_mock_trades(fee):
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='dry_run_buy_12345'
open_order_id='dry_run_buy_12345',
strategy='DefaultStrategy',
)
Trade.session.add(trade)
@@ -197,7 +198,8 @@ def create_mock_trades(fee):
close_profit=0.005,
exchange='bittrex',
is_open=False,
open_order_id='dry_run_sell_12345'
open_order_id='dry_run_sell_12345',
strategy='DefaultStrategy',
)
Trade.session.add(trade)
@@ -225,7 +227,8 @@ def create_mock_trades(fee):
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='prod_buy_12345'
open_order_id='prod_buy_12345',
strategy='DefaultStrategy',
)
Trade.session.add(trade)

View File

@@ -6,24 +6,48 @@ from arrow import Arrow
from pandas import DataFrame, DateOffset, Timestamp, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
analyze_trade_parallelism,
calculate_market_change,
calculate_max_drawdown,
combine_dataframes_with_mean,
create_cum_profit,
extract_trades_of_period,
get_latest_backtest_filename,
load_backtest_data, load_trades,
load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history
from freqtrade.optimize.backtesting import BacktestResult
from tests.conftest import create_mock_trades
def test_load_backtest_data(testdatadir):
def test_get_latest_backtest_filename(testdatadir, mocker):
with pytest.raises(ValueError, match=r"Directory .* does not exist\."):
get_latest_backtest_filename(testdatadir / 'does_not_exist')
with pytest.raises(ValueError,
match=r"Directory .* does not seem to contain .*"):
get_latest_backtest_filename(testdatadir.parent)
res = get_latest_backtest_filename(testdatadir)
assert res == 'backtest-result_new.json'
res = get_latest_backtest_filename(str(testdatadir))
assert res == 'backtest-result_new.json'
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."):
get_latest_backtest_filename(testdatadir)
def test_load_backtest_data_old_format(testdatadir):
filename = testdatadir / "backtest-result_test.json"
bt_data = load_backtest_data(filename)
assert isinstance(bt_data, DataFrame)
assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profit"]
assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profit_abs"]
assert len(bt_data) == 179
# Test loading from string (must yield same result)
@@ -34,6 +58,49 @@ def test_load_backtest_data(testdatadir):
load_backtest_data(str("filename") + "nofile")
def test_load_backtest_data_new_format(testdatadir):
filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename)
assert isinstance(bt_data, DataFrame)
assert set(bt_data.columns) == set(list(BacktestResult._fields) + ["profit_abs"])
assert len(bt_data) == 179
# Test loading from string (must yield same result)
bt_data2 = load_backtest_data(str(filename))
assert bt_data.equals(bt_data2)
# Test loading from folder (must yield same result)
bt_data3 = load_backtest_data(testdatadir)
assert bt_data.equals(bt_data3)
with pytest.raises(ValueError, match=r"File .* does not exist\."):
load_backtest_data(str("filename") + "nofile")
with pytest.raises(ValueError, match=r"Unknown dataformat."):
load_backtest_data(testdatadir / LAST_BT_RESULT_FN)
def test_load_backtest_data_multi(testdatadir):
filename = testdatadir / "backtest-result_multistrat.json"
for strategy in ('DefaultStrategy', 'TestStrategy'):
bt_data = load_backtest_data(filename, strategy=strategy)
assert isinstance(bt_data, DataFrame)
assert set(bt_data.columns) == set(list(BacktestResult._fields) + ["profit_abs"])
assert len(bt_data) == 179
# Test loading from string (must yield same result)
bt_data2 = load_backtest_data(str(filename), strategy=strategy)
assert bt_data.equals(bt_data2)
with pytest.raises(ValueError, match=r"Strategy XYZ not available in the backtest result\."):
load_backtest_data(filename, strategy='XYZ')
with pytest.raises(ValueError, match=r"Detected backtest result with more than one strategy.*"):
load_backtest_data(filename)
@pytest.mark.usefixtures("init_persistence")
def test_load_trades_from_db(default_conf, fee, mocker):
@@ -46,12 +113,16 @@ def test_load_trades_from_db(default_conf, fee, mocker):
assert len(trades) == 4
assert isinstance(trades, DataFrame)
assert "pair" in trades.columns
assert "open_time" in trades.columns
assert "open_date" in trades.columns
assert "profit_percent" in trades.columns
for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']:
assert col in trades.columns
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='DefaultStrategy')
assert len(trades) == 3
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
assert len(trades) == 0
def test_extract_trades_of_period(testdatadir):
@@ -66,13 +137,13 @@ def test_extract_trades_of_period(testdatadir):
{'pair': [pair, pair, pair, pair],
'profit_percent': [0.0, 0.1, -0.2, -0.5],
'profit_abs': [0.0, 1, -2, -5],
'open_time': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime,
'open_date': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime,
Arrow(2017, 11, 14, 9, 41, 0).datetime,
Arrow(2017, 11, 14, 14, 20, 0).datetime,
Arrow(2017, 11, 15, 3, 40, 0).datetime,
], utc=True
),
'close_time': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime,
'close_date': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime,
Arrow(2017, 11, 14, 10, 41, 0).datetime,
Arrow(2017, 11, 14, 15, 25, 0).datetime,
Arrow(2017, 11, 15, 3, 55, 0).datetime,
@@ -81,10 +152,10 @@ def test_extract_trades_of_period(testdatadir):
trades1 = extract_trades_of_period(data, trades)
# First and last trade are dropped as they are out of range
assert len(trades1) == 2
assert trades1.iloc[0].open_time == Arrow(2017, 11, 14, 9, 41, 0).datetime
assert trades1.iloc[0].close_time == Arrow(2017, 11, 14, 10, 41, 0).datetime
assert trades1.iloc[-1].open_time == Arrow(2017, 11, 14, 14, 20, 0).datetime
assert trades1.iloc[-1].close_time == Arrow(2017, 11, 14, 15, 25, 0).datetime
assert trades1.iloc[0].open_date == Arrow(2017, 11, 14, 9, 41, 0).datetime
assert trades1.iloc[0].close_date == Arrow(2017, 11, 14, 10, 41, 0).datetime
assert trades1.iloc[-1].open_date == Arrow(2017, 11, 14, 14, 20, 0).datetime
assert trades1.iloc[-1].close_date == Arrow(2017, 11, 14, 15, 25, 0).datetime
def test_analyze_trade_parallelism(default_conf, mocker, testdatadir):
@@ -105,7 +176,8 @@ def test_load_trades(default_conf, mocker):
load_trades("DB",
db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'),
no_trades=False
no_trades=False,
strategy="DefaultStrategy",
)
assert db_mock.call_count == 1
@@ -135,6 +207,14 @@ def test_load_trades(default_conf, mocker):
assert bt_mock.call_count == 0
def test_calculate_market_change(testdatadir):
pairs = ["ETH/BTC", "ADA/BTC"]
data = load_data(datadir=testdatadir, pairs=pairs, timeframe='5m')
result = calculate_market_change(data)
assert isinstance(result, float)
assert pytest.approx(result) == 0.00955514
def test_combine_dataframes_with_mean(testdatadir):
pairs = ["ETH/BTC", "ADA/BTC"]
data = load_data(datadir=testdatadir, pairs=pairs, timeframe='5m')
@@ -165,7 +245,7 @@ def test_create_cum_profit1(testdatadir):
filename = testdatadir / "backtest-result_test.json"
bt_data = load_backtest_data(filename)
# Move close-time to "off" the candle, to make sure the logic still works
bt_data.loc[:, 'close_time'] = bt_data.loc[:, 'close_time'] + DateOffset(seconds=20)
bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20)
timerange = TimeRange.parse_timerange("20180110-20180112")
df = load_pair_history(pair="TRX/BTC", timeframe='5m',
@@ -204,11 +284,11 @@ def test_calculate_max_drawdown2():
-0.033961, 0.010680, 0.010886, -0.029274, 0.011178, 0.010693, 0.010711]
dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))]
df = DataFrame(zip(values, dates), columns=['profit', 'open_time'])
df = DataFrame(zip(values, dates), columns=['profit', 'open_date'])
# sort by profit and reset index
df = df.sort_values('profit').reset_index(drop=True)
df1 = df.copy()
drawdown, h, low = calculate_max_drawdown(df, date_col='open_time', value_col='profit')
drawdown, h, low = calculate_max_drawdown(df, date_col='open_date', value_col='profit')
# Ensure df has not been altered.
assert df.equals(df1)
@@ -217,6 +297,6 @@ def test_calculate_max_drawdown2():
assert h < low
assert drawdown == 0.091755
df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_time'])
df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date'])
with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'):
calculate_max_drawdown(df, date_col='open_time', value_col='profit')
calculate_max_drawdown(df, date_col='open_date', value_col='profit')

View File

@@ -36,7 +36,7 @@ def _backup_file(file: Path, 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
:param copy_file: keep file in place too.
:return: None
"""
file_swp = str(file) + '.swp'

View File

@@ -163,8 +163,8 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.exit_type == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
def test_adjust(mocker, edge_conf):
@@ -354,10 +354,8 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_index': 1,
'close_index': 1,
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
'trade_duration': '',
'open_rate': 17,
'close_rate': 17,
@@ -367,10 +365,8 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'trade_duration': '',
'open_rate': 20,
'close_rate': 20,
@@ -380,10 +376,8 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_index': 6,
'close_index': 7,
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
'trade_duration': '',
'open_rate': 26,
'close_rate': 34,
@@ -424,8 +418,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_index': 1,
'close_index': 1,
'trade_duration': '',
@@ -437,8 +431,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
@@ -449,8 +443,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
@@ -461,8 +455,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
@@ -473,8 +467,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
@@ -486,8 +480,8 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_index': 6,
'close_index': 7,
'trade_duration': '',

View File

@@ -15,7 +15,7 @@ from freqtrade.exceptions import (DDosProtection, DependencyException,
from freqtrade.exchange import Binance, Exchange, Kraken
from freqtrade.exchange.common import (API_RETRY_COUNT, API_FETCH_ORDER_RETRY_COUNT,
calculate_backoff)
from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair,
from freqtrade.exchange.exchange import (market_is_active,
timeframe_to_minutes,
timeframe_to_msecs,
timeframe_to_next_date,
@@ -2245,25 +2245,42 @@ def test_timeframe_to_next_date():
assert timeframe_to_next_date("5m") > date
@pytest.mark.parametrize("market_symbol,base_currency,quote_currency,expected_result", [
("BTC/USDT", None, None, True),
("USDT/BTC", None, None, True),
("BTCUSDT", None, None, False),
("BTC/USDT", None, "USDT", True),
("USDT/BTC", None, "USDT", False),
("BTCUSDT", None, "USDT", False),
("BTC/USDT", "BTC", None, True),
("USDT/BTC", "BTC", None, False),
("BTCUSDT", "BTC", None, False),
("BTC/USDT", "BTC", "USDT", True),
("BTC/USDT", "USDT", "BTC", False),
("BTC/USDT", "BTC", "USD", False),
("BTCUSDT", "BTC", "USDT", False),
("BTC/", None, None, False),
("/USDT", None, None, False),
@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [
("BTC/USDT", 'BTC', 'USDT', "binance", {}, True),
("USDT/BTC", 'USDT', 'BTC', "binance", {}, True),
("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies
("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating /
("BTCUSDT", None, "USDT", "binance", {}, False), #
("USDT/BTC", "BTC", None, "binance", {}, False),
("BTCUSDT", "BTC", None, "binance", {}, False),
("BTC/USDT", "BTC", "USDT", "binance", {}, True),
("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies
("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency
("BTC/", "BTC", 'UNK', "binance", {}, False),
("/USDT", 'UNK', 'USDT', "binance", {}, False),
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies
("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True),
("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True),
("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency
("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
])
def test_symbol_is_pair(market_symbol, base_currency, quote_currency, expected_result) -> None:
assert symbol_is_pair(market_symbol, base_currency, quote_currency) == expected_result
def test_market_is_tradable(mocker, default_conf, market_symbol, base,
quote, add_dict, exchange, expected_result) -> None:
ex = get_patched_exchange(mocker, default_conf, id=exchange)
market = {
'symbol': market_symbol,
'base': base,
'quote': quote,
**(add_dict),
}
assert ex.market_is_tradable(market) == expected_result
@pytest.mark.parametrize("market,expected_result", [

View File

@@ -0,0 +1,202 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from functools import reduce
from typing import Any, Callable, Dict, List
import talib.abstract as ta
from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt
class DefaultHyperOpt(IHyperOpt):
"""
Default hyperopt provided by the Freqtrade bot.
You can override it with your own Hyperopt
"""
@staticmethod
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Add several indicators needed for buy and sell strategies defined below.
"""
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
# Minus-DI
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
# SAR
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by Hyperopt.
"""
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Buy strategy Hyperopt will build and use.
"""
conditions = []
# GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value'])
if 'fastd-enabled' in params and params['fastd-enabled']:
conditions.append(dataframe['fastd'] < params['fastd-value'])
if 'adx-enabled' in params and params['adx-enabled']:
conditions.append(dataframe['adx'] > params['adx-value'])
if 'rsi-enabled' in params and params['rsi-enabled']:
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
return populate_buy_trend
@staticmethod
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching buy strategy parameters.
"""
return [
Integer(10, 25, name='mfi-value'),
Integer(15, 45, name='fastd-value'),
Integer(20, 50, name='adx-value'),
Integer(20, 40, name='rsi-value'),
Categorical([True, False], name='mfi-enabled'),
Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by Hyperopt.
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use.
"""
conditions = []
# GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters.
"""
return [
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')
]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of same method from strategy.
Must align to populate_indicators in this file.
Only used when --spaces does not include buy space.
"""
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['mfi'] < 16) &
(dataframe['adx'] > 25) &
(dataframe['rsi'] < 21)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of same method from strategy.
Must align to populate_indicators in this file.
Only used when --spaces does not include sell space.
"""
dataframe.loc[
(
(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
)) &
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe

View File

@@ -395,5 +395,5 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.sell_reason == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)

View File

@@ -354,8 +354,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
exists = [
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:59:00+00:00 (0 days)..'
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:59:00 (0 days)..'
]
for line in exists:
assert log_has(line, caplog)
@@ -464,28 +464,29 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
{'pair': [pair, pair],
'profit_percent': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_time': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
),
'close_time': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
'open_rate': [0.104445, 0.10302485],
'open_fee': [0.0025, 0.0025],
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
'open_index': [78, 184],
'close_index': [125, 192],
'close_rate': [0.104969, 0.103541],
'close_fee': [0.0025, 0.0025],
'amount': [0.00957442, 0.0097064],
'trade_duration': [235, 40],
'open_at_end': [False, False],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]
for _, t in results.iterrows():
ln = data_pair.loc[data_pair["date"] == t["open_time"]]
ln = data_pair.loc[data_pair["date"] == t["open_date"]]
# Check open trade rate alignes to open rate
assert ln is not None
assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
# check close trade rate alignes to close rate or is between high and low
ln = data_pair.loc[data_pair["date"] == t["close_time"]]
ln = data_pair.loc[data_pair["date"] == t["close_date"]]
assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or
round(ln.iloc[0]["low"], 6) < round(
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
@@ -677,10 +678,10 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
f'Using data directory: {testdatadir} ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Loading data from 2017-11-14T20:57:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Loading data from 2017-11-14 20:57:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Parameter --enable-position-stacking detected ...'
]
@@ -707,6 +708,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock,
generate_strategy_metrics=strat_summary,
generate_daily_stats=MagicMock(),
)
patched_configuration_load_config_file(mocker, default_conf)
@@ -740,10 +742,10 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
f'Using data directory: {testdatadir} ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Loading data from 2017-11-14T20:57:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Loading data from 2017-11-14 20:57:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',
@@ -761,13 +763,11 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
'profit_percent': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_time': pd.to_datetime(['2018-01-29 18:40:00',
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00', ], utc=True
),
'close_time': pd.to_datetime(['2018-01-29 20:45:00',
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00', ], utc=True),
'open_index': [78, 184],
'close_index': [125, 192],
'trade_duration': [235, 40],
'open_at_end': [False, False],
'open_rate': [0.104445, 0.10302485],
@@ -777,15 +777,13 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_percent': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_time': pd.to_datetime(['2018-01-29 18:40:00',
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00',
'2018-01-30 05:30:00'], utc=True
),
'close_time': pd.to_datetime(['2018-01-29 20:45:00',
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00',
'2018-01-30 08:30:00'], utc=True),
'open_index': [78, 184, 185],
'close_index': [125, 224, 205],
'trade_duration': [47, 40, 20],
'open_at_end': [False, False, False],
'open_rate': [0.104445, 0.10302485, 0.122541],
@@ -823,10 +821,10 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
f'Using data directory: {testdatadir} ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Loading data from 2017-11-14T20:57:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Loading data from 2017-11-14 20:57:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days)..',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',

View File

@@ -105,3 +105,17 @@ def test_edge_init_fee(mocker, edge_conf) -> None:
edge_cli = EdgeCli(edge_conf)
assert edge_cli.edge.fee == 0.1234
assert fee_mock.call_count == 0
def test_edge_start(mocker, edge_conf) -> None:
mock_calculate = mocker.patch('freqtrade.edge.edge_positioning.Edge.calculate',
return_value=True)
table_mock = mocker.patch('freqtrade.optimize.edge_cli.generate_edge_table')
patch_exchange(mocker)
edge_conf['stake_amount'] = 20
edge_cli = EdgeCli(edge_conf)
edge_cli.start()
assert mock_calculate.call_count == 1
assert table_mock.call_count == 1

View File

@@ -3,6 +3,7 @@ import locale
import logging
from datetime import datetime
from pathlib import Path
from copy import deepcopy
from typing import Dict, List
from unittest.mock import MagicMock, PropertyMock
@@ -16,7 +17,6 @@ from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
start_hyperopt)
from freqtrade.data.history import load_data
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.optimize.default_hyperopt import DefaultHyperOpt
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver,
@@ -26,15 +26,28 @@ from freqtrade.strategy.interface import SellType
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
from .hyperopts.default_hyperopt import DefaultHyperOpt
@pytest.fixture(scope='function')
def hyperopt(default_conf, mocker):
default_conf.update({
'spaces': ['default'],
'hyperopt': 'DefaultHyperOpt',
})
def hyperopt_conf(default_conf):
hyperconf = deepcopy(default_conf)
hyperconf.update({
'hyperopt': 'DefaultHyperOpt',
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
'epochs': 1,
'timerange': None,
'spaces': ['default'],
'hyperopt_jobs': 1,
})
return hyperconf
@pytest.fixture(scope='function')
def hyperopt(hyperopt_conf, mocker):
patch_exchange(mocker)
return Hyperopt(default_conf)
return Hyperopt(hyperopt_conf)
@pytest.fixture(scope='function')
@@ -46,7 +59,7 @@ def hyperopt_results():
'profit_abs': [-0.2, 0.4, 0.6],
'trade_duration': [10, 30, 10],
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI],
'close_time':
'close_date':
[
datetime(2019, 1, 1, 9, 26, 3, 478039),
datetime(2019, 2, 1, 9, 26, 3, 478039),
@@ -160,7 +173,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
assert log_has('Parameter --print-all detected ...', caplog)
def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf) -> None:
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
patched_configuration_load_config_file(mocker, default_conf)
@@ -201,7 +214,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
assert hasattr(x, "timeframe")
def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None:
def test_hyperoptresolver_wrongname(default_conf) -> None:
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
@@ -216,7 +229,7 @@ def test_hyperoptresolver_noname(default_conf):
HyperOptResolver.load_hyperopt(default_conf)
def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
def test_hyperoptlossresolver(mocker, default_conf) -> None:
hl = DefaultHyperOptLoss
mocker.patch(
@@ -227,14 +240,14 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
assert hasattr(x, "hyperopt_loss_function")
def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None:
def test_hyperoptlossresolver_wrongname(default_conf) -> None:
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None:
def test_start_not_installed(mocker, default_conf, import_fails) -> None:
start_mock = MagicMock()
patched_configuration_load_config_file(mocker, default_conf)
@@ -245,6 +258,8 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None
'hyperopt',
'--config', 'config.json',
'--hyperopt', 'DefaultHyperOpt',
'--hyperopt-path',
str(Path(__file__).parent / "hyperopts"),
'--epochs', '5'
]
pargs = get_args(args)
@@ -253,9 +268,9 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None
start_hyperopt(pargs)
def test_start(mocker, default_conf, caplog) -> None:
def test_start(mocker, hyperopt_conf, caplog) -> None:
start_mock = MagicMock()
patched_configuration_load_config_file(mocker, default_conf)
patched_configuration_load_config_file(mocker, hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
patch_exchange(mocker)
@@ -272,8 +287,8 @@ def test_start(mocker, default_conf, caplog) -> None:
assert start_mock.call_count == 1
def test_start_no_data(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
def test_start_no_data(mocker, hyperopt_conf) -> None:
patched_configuration_load_config_file(mocker, hyperopt_conf)
mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=pd.DataFrame))
mocker.patch(
'freqtrade.optimize.hyperopt.get_timerange',
@@ -293,9 +308,9 @@ def test_start_no_data(mocker, default_conf, caplog) -> None:
start_hyperopt(pargs)
def test_start_filelock(mocker, default_conf, caplog) -> None:
start_mock = MagicMock(side_effect=Timeout(Hyperopt.get_lock_filename(default_conf)))
patched_configuration_load_config_file(mocker, default_conf)
def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
start_mock = MagicMock(side_effect=Timeout(Hyperopt.get_lock_filename(hyperopt_conf)))
patched_configuration_load_config_file(mocker, hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
patch_exchange(mocker)
@@ -519,7 +534,7 @@ def test_roi_table_generation(hyperopt) -> None:
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -545,15 +560,9 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
)
patch_exchange(mocker)
# Co-test loading timeframe from strategy
del default_conf['timeframe']
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'default',
'hyperopt_jobs': 1, })
del hyperopt_conf['timeframe']
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -569,7 +578,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == default_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -686,13 +695,36 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
assert 1 in result['buy']
def test_generate_optimizer(mocker, default_conf) -> None:
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'timerange': None,
'spaces': 'all',
'hyperopt_min_trades': 1,
})
def test_sell_strategy_generator(hyperopt, testdatadir) -> None:
data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data)
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
{'pair': 'UNITTEST/BTC'})
populate_sell_trend = hyperopt.custom_hyperopt.sell_strategy_generator(
{
'sell-adx-value': 20,
'sell-fastd-value': 75,
'sell-mfi-value': 80,
'sell-rsi-value': 20,
'sell-adx-enabled': True,
'sell-fastd-enabled': True,
'sell-mfi-enabled': True,
'sell-rsi-enabled': True,
'sell-trigger': 'sell-bb_upper'
}
)
result = populate_sell_trend(dataframe, {'pair': 'UNITTEST/BTC'})
# Check if some indicators are generated. We will not test all of them
print(result)
assert 'sell' in result
assert 1 in result['sell']
def test_generate_optimizer(mocker, hyperopt_conf) -> None:
hyperopt_conf.update({'spaces': 'all',
'hyperopt_min_trades': 1,
})
trades = [
('TRX/BTC', 0.023117, 0.000233, 100)
@@ -790,48 +822,35 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'total_profit': 0.00023300
}
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.dimensions = hyperopt.hyperopt_space()
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected
def test_clean_hyperopt(mocker, default_conf, caplog):
def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'default',
'hyperopt_jobs': 1,
})
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock())
h = Hyperopt(default_conf)
h = Hyperopt(hyperopt_conf)
assert unlinkmock.call_count == 2
assert log_has(f"Removing `{h.data_pickle_file}`.", caplog)
def test_continue_hyperopt(mocker, default_conf, caplog):
def test_continue_hyperopt(mocker, hyperopt_conf, caplog):
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'default',
'hyperopt_jobs': 1,
'hyperopt_continue': True
})
hyperopt_conf.update({'hyperopt_continue': True})
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock())
Hyperopt(default_conf)
Hyperopt(hyperopt_conf)
assert unlinkmock.call_count == 0
assert log_has("Continuing on previous hyperopt results.", caplog)
def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -862,16 +881,12 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'all',
'hyperopt_jobs': 1,
'print_json': True,
})
hyperopt_conf.update({'spaces': 'all',
'hyperopt_jobs': 1,
'print_json': True,
})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -890,7 +905,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
assert dumper.call_count == 2
def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None:
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -920,16 +935,9 @@ def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'default',
'hyperopt_jobs': 1,
'print_json': True,
})
hyperopt_conf.update({'print_json': True})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -944,7 +952,7 @@ def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None
assert dumper.call_count == 2
def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> None:
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -970,16 +978,12 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) ->
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'roi stoploss',
'hyperopt_jobs': 1,
'print_json': True,
})
hyperopt_conf.update({'spaces': 'roi stoploss',
'hyperopt_jobs': 1,
'print_json': True,
})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -994,7 +998,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) ->
assert dumper.call_count == 2
def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) -> None:
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1019,14 +1023,9 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys)
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'roi stoploss',
'hyperopt_jobs': 1, })
hyperopt_conf.update({'spaces': 'roi stoploss'})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -1047,11 +1046,11 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys)
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == default_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -> None:
def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1062,14 +1061,9 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'all',
'hyperopt_jobs': 1, })
hyperopt_conf.update({'spaces': 'all', })
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -1082,7 +1076,7 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -
hyperopt.start()
def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1107,14 +1101,9 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'buy',
'hyperopt_jobs': 1, })
hyperopt_conf.update({'spaces': 'buy'})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -1135,11 +1124,11 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == default_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None:
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1164,14 +1153,9 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': 'sell',
'hyperopt_jobs': 1, })
hyperopt_conf.update({'spaces': 'sell', })
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
@@ -1192,7 +1176,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == default_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -1202,7 +1186,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None
('sell_strategy_generator', 'sell'),
('sell_indicator_space', 'sell'),
])
def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, method, space) -> None:
def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> None:
mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1213,14 +1197,9 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt',
'epochs': 1,
'timerange': None,
'spaces': space,
'hyperopt_jobs': 1, })
hyperopt_conf.update({'spaces': space})
hyperopt = Hyperopt(default_conf)
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})

View File

@@ -1,16 +1,29 @@
import re
from datetime import timedelta
from pathlib import Path
import pandas as pd
import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename,
load_backtest_data)
from freqtrade.edge import PairInfo
from freqtrade.optimize.optimize_reports import (
generate_pair_metrics, generate_edge_table, generate_sell_reason_stats,
text_table_bt_results, text_table_sell_reason, generate_strategy_metrics,
text_table_strategy, store_backtest_result)
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
generate_daily_stats,
generate_edge_table,
generate_pair_metrics,
generate_sell_reason_stats,
generate_strategy_metrics,
store_backtest_stats,
text_table_bt_results,
text_table_sell_reason,
text_table_strategy)
from freqtrade.strategy.interface import SellType
from tests.conftest import patch_exchange
from tests.data.test_history import _backup_file, _clean_test_file
def test_text_table_bt_results(default_conf, mocker):
@@ -43,6 +56,115 @@ def test_text_table_bt_results(default_conf, mocker):
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
def test_generate_backtest_stats(default_conf, testdatadir):
results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
})}
timerange = TimeRange.parse_timerange('1510688220-1510700340')
min_date = Arrow.fromtimestamp(1510688220)
max_date = Arrow.fromtimestamp(1510700340)
btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
stats = generate_backtest_stats(default_conf, btdata, results, min_date, max_date)
assert isinstance(stats, dict)
assert 'strategy' in stats
assert 'DefStrat' in stats['strategy']
assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat']
assert strat_stats['backtest_start'] == min_date.datetime
assert strat_stats['backtest_end'] == max_date.datetime
assert strat_stats['total_trades'] == len(results['DefStrat'])
# Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0
results = {'DefStrat': pd.DataFrame(
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
})}
assert strat_stats['max_drawdown'] == 0.0
assert strat_stats['drawdown_start'] == Arrow.fromtimestamp(0).datetime
assert strat_stats['drawdown_end'] == Arrow.fromtimestamp(0).datetime
assert strat_stats['drawdown_end_ts'] == 0
assert strat_stats['drawdown_start_ts'] == 0
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
# Test storing stats
filename = Path(testdatadir / 'btresult.json')
filename_last = Path(testdatadir / LAST_BT_RESULT_FN)
_backup_file(filename_last, copy_file=True)
assert not filename.is_file()
store_backtest_stats(filename, stats)
# get real Filename (it's btresult-<date>.json)
last_fn = get_latest_backtest_filename(filename_last.parent)
assert re.match(r"btresult-.*\.json", last_fn)
filename1 = (testdatadir / last_fn)
assert filename1.is_file()
content = filename1.read_text()
assert 'max_drawdown' in content
assert 'strategy' in content
assert 'pairlist' in content
assert filename_last.is_file()
_clean_test_file(filename_last)
filename1.unlink()
def test_store_backtest_stats(testdatadir, mocker):
dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.file_dump_json')
store_backtest_stats(testdatadir, {})
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir/'backtest-result'))
dump_mock.reset_mock()
filename = testdatadir / 'testresult.json'
store_backtest_stats(filename, {})
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
# result will be testdatadir / testresult-<timestamp>.json
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult'))
def test_generate_pair_metrics(default_conf, mocker):
results = pd.DataFrame(
@@ -68,6 +190,29 @@ def test_generate_pair_metrics(default_conf, mocker):
pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100)
def test_generate_daily_stats(testdatadir):
filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename)
res = generate_daily_stats(bt_data)
assert isinstance(res, dict)
assert round(res['backtest_best_day'], 4) == 0.1796
assert round(res['backtest_worst_day'], 4) == -0.1468
assert res['winning_days'] == 14
assert res['draw_days'] == 4
assert res['losing_days'] == 3
assert res['winner_holding_avg'] == timedelta(seconds=1440)
assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
# Select empty dataframe!
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
assert isinstance(res, dict)
assert round(res['backtest_best_day'], 4) == 0.0
assert res['winning_days'] == 0
assert res['draw_days'] == 0
assert res['losing_days'] == 0
def test_text_table_sell_reason(default_conf):
results = pd.DataFrame(
@@ -188,77 +333,3 @@ def test_generate_edge_table(edge_conf, mocker):
assert generate_edge_table(results).count('| ETH/BTC |') == 1
assert generate_edge_table(results).count(
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
def test_backtest_record(default_conf, fee, mocker):
names = []
records = []
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch(
'freqtrade.optimize.optimize_reports.file_dump_json',
new=lambda n, r: (names.append(n), records.append(r))
)
results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"open_index": [1, 119, 153, 185],
"close_index": [118, 151, 184, 199],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
})}
store_backtest_result(Path("backtest-result.json"), results)
# Assert file_dump_json was only called once
assert names == [Path('backtest-result.json')]
records = records[0]
# Ensure records are of correct type
assert len(records) == 4
# reset test to test with strategy name
names = []
records = []
results['Strat'] = results['DefStrat']
results['Strat2'] = results['DefStrat']
store_backtest_result(Path("backtest-result.json"), results)
assert names == [
Path('backtest-result-DefStrat.json'),
Path('backtest-result-Strat.json'),
Path('backtest-result-Strat2.json'),
]
records = records[0]
# Ensure records are of correct type
assert len(records) == 4
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
# Below follows just a typecheck of the schema/type of trade-records
oix = None
for (pair, profit, date_buy, date_sell, buy_index, dur,
openr, closer, open_at_end, sell_reason) in records:
assert pair == 'UNITTEST/BTC'
assert isinstance(profit, float)
# FIX: buy/sell should be converted to ints
assert isinstance(date_buy, float)
assert isinstance(date_sell, float)
assert isinstance(openr, float)
assert isinstance(closer, float)
assert isinstance(open_at_end, bool)
assert isinstance(sell_reason, str)
isinstance(buy_index, pd._libs.tslib.Timestamp)
if oix:
assert buy_index > oix
oix = buy_index
assert dur > 0

View File

@@ -468,7 +468,9 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
# BCH/BTC not available
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"),
# BTT/BTC is inactive
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active")
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active"),
# XLTCUSDT is not a valid pair
(['ETH/BTC', 'TKN/BTC', 'XLTCUSDT'], "is not tradable with Freqtrade"),
])
def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog,
log_message, tickers):
@@ -547,7 +549,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick
)
with pytest.raises(OperationalException,
match=r'AgeFilter requires min_days_listed must be >= 1'):
match=r'AgeFilter requires min_days_listed to be >= 1'):
get_patched_freqtradebot(mocker, default_conf)
@@ -562,7 +564,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
)
with pytest.raises(OperationalException,
match=r'AgeFilter requires min_days_listed must not exceed '
match=r'AgeFilter requires min_days_listed to not exceed '
r'exchange max request size \([0-9]+\)'):
get_patched_freqtradebot(mocker, default_conf)
@@ -590,34 +592,58 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
@pytest.mark.parametrize("pairlistconfig,expected", [
@pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010,
"max_price": 1.0}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below "
"0.1% or below 0.00000010 or above 1.00000000.'}]"
"max_price": 1.0},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below "
"0.1% or below 0.00000010 or above 1.00000000.'}]",
None
),
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]"
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]",
None
),
({"method": "PriceFilter", "low_price_ratio": 0.001, "max_price": 1.00010000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]"
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]",
None
),
({"method": "PriceFilter", "min_price": 0.00002000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]"
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]",
None
),
({"method": "PriceFilter"},
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]"
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]",
None
),
({"method": "PriceFilter", "low_price_ratio": -0.001},
None,
"PriceFilter requires low_price_ratio to be >= 0"
), # OperationalException expected
({"method": "PriceFilter", "min_price": -0.00000010},
None,
"PriceFilter requires min_price to be >= 0"
), # OperationalException expected
({"method": "PriceFilter", "max_price": -1.00010000},
None,
"PriceFilter requires max_price to be >= 0"
), # OperationalException expected
])
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, expected):
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
desc_expected, exception_expected):
mocker.patch.multiple('freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True)
)
whitelist_conf['pairlists'] = [pairlistconfig]
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
short_desc = str(freqtrade.pairlists.short_desc())
assert short_desc == expected
if desc_expected is not None:
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
short_desc = str(freqtrade.pairlists.short_desc())
assert short_desc == desc_expected
else: # OperationalException expected
with pytest.raises(OperationalException,
match=exception_expected):
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):

View File

@@ -255,11 +255,11 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
assert days['fiat_display_currency'] == default_conf['fiat_display_currency']
for day in days['data']:
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
assert (day['abs_profit'] == '0.00000000' or
day['abs_profit'] == '0.00006217')
assert (day['abs_profit'] == 0.0 or
day['abs_profit'] == 0.00006217)
assert (day['fiat_value'] == '0.000' or
day['fiat_value'] == '0.767')
assert (day['fiat_value'] == 0.0 or
day['fiat_value'] == 0.76748865)
# ensure first day is current date
assert str(days['data'][0]['date']) == str(datetime.utcnow().date())

View File

@@ -321,7 +321,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
# stoploss shoud be hit
assert freqtrade.handle_trade(trade) is True
assert log_has('Executing Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog)
assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value

View File

@@ -267,7 +267,7 @@ def test_generate_profit_graph(testdatadir):
trades = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112")
pairs = ["TRX/BTC", "XLM/BTC"]
trades = trades[trades['close_time'] < pd.Timestamp('2018-01-12', tz='UTC')]
trades = trades[trades['close_date'] < pd.Timestamp('2018-01-12', tz='UTC')]
data = history.load_data(datadir=testdatadir,
pairs=pairs,

1
tests/testdata/.last_result.json vendored Normal file
View File

@@ -0,0 +1 @@
{"latest_backtest":"backtest-result_new.json"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long