Merge pull request #4268 from freqtrade/backtest_trade_object

Backtest trade object
This commit is contained in:
Matthias
2021-01-27 19:10:21 +01:00
committed by GitHub
26 changed files with 181 additions and 202 deletions

View File

@@ -7,14 +7,13 @@ 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,
get_latest_hyperopt_file, load_backtest_data, load_trades,
load_trades_from_db)
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD,
analyze_trade_parallelism, calculate_market_change,
calculate_max_drawdown, combine_dataframes_with_mean,
create_cum_profit, extract_trades_of_period,
get_latest_backtest_filename, get_latest_hyperopt_file,
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
from tests.conftest_trades import MOCK_TRADE_COUNT
@@ -55,7 +54,7 @@ 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_abs"]
assert list(bt_data.columns) == BT_DATA_COLUMNS_OLD + ['profit_abs', 'profit_ratio']
assert len(bt_data) == 179
# Test loading from string (must yield same result)
@@ -71,7 +70,7 @@ 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 set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
assert len(bt_data) == 179
# Test loading from string (must yield same result)
@@ -95,7 +94,7 @@ def test_load_backtest_data_multi(testdatadir):
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 set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
assert len(bt_data) == 179
# Test loading from string (must yield same result)
@@ -122,7 +121,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
assert isinstance(trades, DataFrame)
assert "pair" in trades.columns
assert "open_date" in trades.columns
assert "profit_percent" in trades.columns
assert "profit_ratio" in trades.columns
for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']:

View File

@@ -37,7 +37,7 @@ def hyperopt_results():
return pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [-0.1, 0.2, 0.3],
'profit_ratio': [-0.1, 0.2, 0.3],
'profit_abs': [-0.2, 0.4, 0.6],
'trade_duration': [10, 30, 10],
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI],

View File

@@ -510,7 +510,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
)
assert len(results) == len(data.trades)
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]

View File

@@ -445,7 +445,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf)
def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
@@ -469,21 +469,28 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
expected = pd.DataFrame(
{'pair': [pair, pair],
'profit_percent': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'stake_amount': [0.001, 0.001],
'amount': [0.00957442, 0.0097064],
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
),
'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_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'close_fee': [0.0025, 0.0025],
'amount': [0.00957442, 0.0097064],
'fee_open': [0.0025, 0.0025],
'fee_close': [0.0025, 0.0025],
'trade_duration': [235, 40],
'open_at_end': [False, False],
'sell_reason': [SellType.ROI, SellType.ROI]
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI, SellType.ROI],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
'stop_loss_ratio': [-0.1, -0.1],
'min_rate': [0.1038, 0.10302485],
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]
@@ -629,7 +636,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
# 100 buys signals
assert len(results) == 100
# One trade was force-closed at the end
assert len(results.loc[results.open_at_end]) == 0
assert len(results.loc[results['is_open']]) == 0
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@@ -737,7 +744,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker)
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS + ['profit_abs']))
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
@@ -803,7 +810,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
patch_exchange(mocker)
backtestmock = MagicMock(side_effect=[
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
'profit_percent': [0.0, 0.0],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00', ], utc=True
@@ -811,13 +818,13 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40],
'open_at_end': [False, False],
'is_open': [False, False],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
}),
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_percent': [0.03, 0.01, 0.1],
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00',
@@ -827,7 +834,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'2018-01-30 05:35:00',
'2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20],
'open_at_end': [False, False, False],
'is_open': [False, False, False],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]

View File

@@ -427,7 +427,7 @@ def test_format_results(hyperopt):
('LTC/BTC', 1, 1, 123),
('XPR/BTC', -1, -2, -246)
]
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
df = pd.DataFrame.from_records(trades, columns=labels)
results_metrics = hyperopt._calculate_results_metrics(df)
results_explanation = hyperopt._format_results_explanation_string(results_metrics)
@@ -567,7 +567,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
trades = [
('TRX/BTC', 0.023117, 0.000233, 100)
]
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
mocker.patch(

View File

@@ -60,9 +60,9 @@ def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results)
def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
@@ -77,9 +77,9 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
@@ -95,9 +95,9 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N
def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
@@ -113,9 +113,9 @@ def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results
def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLoss'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
@@ -131,9 +131,9 @@ def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) ->
def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
@@ -149,9 +149,9 @@ def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_result
def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
default_conf.update({'hyperopt_loss': 'OnlyProfitHyperOptLoss'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)

View File

@@ -27,7 +27,7 @@ def test_text_table_bt_results():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2],
'profit_ratio': [0.1, 0.2],
'profit_abs': [0.2, 0.4],
'trade_duration': [10, 30],
'wins': [2, 0],
@@ -59,7 +59,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
results = {'DefStrat': {
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_ratio": [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,
@@ -72,7 +72,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
"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],
"is_open": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
@@ -103,7 +103,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
results = {'DefStrat': {
'results': pd.DataFrame(
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_ratio": [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,
@@ -179,7 +179,7 @@ def test_generate_pair_metrics():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2],
'profit_ratio': [0.1, 0.2],
'profit_abs': [0.2, 0.4],
'trade_duration': [10, 30],
'wins': [2, 0],
@@ -227,7 +227,7 @@ def test_text_table_sell_reason():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, -0.1],
'profit_ratio': [0.1, 0.2, -0.1],
'profit_abs': [0.2, 0.4, -0.2],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
@@ -259,7 +259,7 @@ def test_generate_sell_reason_stats():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, -0.1],
'profit_ratio': [0.1, 0.2, -0.1],
'profit_abs': [0.2, 0.4, -0.2],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
@@ -295,7 +295,7 @@ def test_text_table_strategy(default_conf):
results['TestStrategy1'] = {'results': pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
'profit_ratio': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
@@ -307,7 +307,7 @@ def test_text_table_strategy(default_conf):
results['TestStrategy2'] = {'results': pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3],
'profit_ratio': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
'wins': [4, 1, 0],

View File

@@ -80,6 +80,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'amount': 91.07468123,
'amount_requested': 91.07468123,
'stake_amount': 0.001,
'trade_duration': None,
'trade_duration_s': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
@@ -144,6 +146,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'current_rate': ANY,
'amount': 91.07468123,
'amount_requested': 91.07468123,
'trade_duration': ANY,
'trade_duration_s': ANY,
'stake_amount': 0.001,
'close_profit': None,
'close_profit_pct': None,

View File

@@ -815,6 +815,8 @@ def test_to_json(default_conf, fee):
'amount': 123.0,
'amount_requested': 123.0,
'stake_amount': 0.001,
'trade_duration': None,
'trade_duration_s': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
@@ -869,6 +871,8 @@ def test_to_json(default_conf, fee):
'amount': 100.0,
'amount_requested': 101.0,
'stake_amount': 0.001,
'trade_duration': 60,
'trade_duration_s': 3600,
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long