2020-06-28 07:27:19 +00:00
|
|
|
import re
|
2020-07-03 18:03:33 +00:00
|
|
|
from datetime import timedelta
|
2020-03-15 14:36:53 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
2020-01-02 06:33:55 +00:00
|
|
|
import pandas as pd
|
2020-05-25 05:14:21 +00:00
|
|
|
import pytest
|
2020-03-15 14:36:53 +00:00
|
|
|
from arrow import Arrow
|
2020-01-02 06:33:55 +00:00
|
|
|
|
2020-06-26 18:08:45 +00:00
|
|
|
from freqtrade.configuration import TimeRange
|
2020-06-28 07:27:19 +00:00
|
|
|
from freqtrade.constants import LAST_BT_RESULT_FN
|
2020-06-26 18:08:45 +00:00
|
|
|
from freqtrade.data import history
|
2020-07-03 18:03:33 +00:00
|
|
|
from freqtrade.data.btanalysis import (get_latest_backtest_filename,
|
|
|
|
load_backtest_data)
|
2020-06-28 07:27:19 +00:00
|
|
|
from freqtrade.edge import PairInfo
|
2020-06-26 18:08:45 +00:00
|
|
|
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
|
2020-07-03 18:03:33 +00:00
|
|
|
generate_daily_stats,
|
2020-06-26 18:08:45 +00:00
|
|
|
generate_edge_table,
|
|
|
|
generate_pair_metrics,
|
|
|
|
generate_sell_reason_stats,
|
|
|
|
generate_strategy_metrics,
|
2020-06-27 17:48:45 +00:00
|
|
|
store_backtest_stats,
|
2020-06-26 18:08:45 +00:00
|
|
|
text_table_bt_results,
|
|
|
|
text_table_sell_reason,
|
|
|
|
text_table_strategy)
|
2020-01-02 06:33:55 +00:00
|
|
|
from freqtrade.strategy.interface import SellType
|
2020-06-27 17:48:45 +00:00
|
|
|
from tests.data.test_history import _backup_file, _clean_test_file
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
2020-06-07 09:35:02 +00:00
|
|
|
def test_text_table_bt_results(default_conf, mocker):
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2],
|
|
|
|
'profit_abs': [0.2, 0.4],
|
|
|
|
'trade_duration': [10, 30],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0],
|
|
|
|
'draws': [0, 0],
|
|
|
|
'losses': [0, 0]
|
2020-01-02 06:33:55 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
|
|
|
' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
|
|
|
'|---------+--------+----------------+----------------+------------------+'
|
|
|
|
'----------------+----------------+--------+---------+----------|\n'
|
2020-02-07 02:54:47 +00:00
|
|
|
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
|
2020-02-27 12:28:28 +00:00
|
|
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
|
|
|
|
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
|
|
|
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 17:34:46 +00:00
|
|
|
|
2020-05-25 18:46:31 +00:00
|
|
|
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
|
2020-05-25 17:50:09 +00:00
|
|
|
max_open_trades=2, results=results)
|
2020-06-07 09:35:02 +00:00
|
|
|
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
2020-06-26 18:08:45 +00:00
|
|
|
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
|
|
|
|
|
2020-06-28 07:27:19 +00:00
|
|
|
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]
|
|
|
|
})}
|
2020-06-26 18:08:45 +00:00
|
|
|
|
|
|
|
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
|
2020-06-28 07:04:19 +00:00
|
|
|
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
|
2020-06-26 18:08:45 +00:00
|
|
|
|
2020-06-27 17:48:45 +00:00
|
|
|
# Test storing stats
|
|
|
|
filename = Path(testdatadir / 'btresult.json')
|
2020-06-28 07:27:19 +00:00
|
|
|
filename_last = Path(testdatadir / LAST_BT_RESULT_FN)
|
2020-06-27 17:48:45 +00:00
|
|
|
_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
|
2020-06-28 07:04:19 +00:00
|
|
|
assert 'pairlist' in content
|
2020-06-27 17:48:45 +00:00
|
|
|
|
|
|
|
assert filename_last.is_file()
|
|
|
|
|
|
|
|
_clean_test_file(filename_last)
|
|
|
|
filename1.unlink()
|
|
|
|
|
2020-06-26 18:08:45 +00:00
|
|
|
|
2020-05-25 18:46:31 +00:00
|
|
|
def test_generate_pair_metrics(default_conf, mocker):
|
2020-05-25 17:50:09 +00:00
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2],
|
|
|
|
'profit_abs': [0.2, 0.4],
|
|
|
|
'trade_duration': [10, 30],
|
|
|
|
'wins': [2, 0],
|
|
|
|
'draws': [0, 0],
|
|
|
|
'losses': [0, 0]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-05-25 18:46:31 +00:00
|
|
|
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
|
2020-05-25 17:50:09 +00:00
|
|
|
max_open_trades=2, results=results)
|
|
|
|
assert isinstance(pair_results, list)
|
|
|
|
assert len(pair_results) == 2
|
|
|
|
assert pair_results[-1]['key'] == 'TOTAL'
|
|
|
|
assert (
|
|
|
|
pytest.approx(pair_results[-1]['profit_mean_pct']) == pair_results[-1]['profit_mean'] * 100)
|
|
|
|
assert (
|
|
|
|
pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100)
|
|
|
|
|
|
|
|
|
2020-07-03 18:03:33 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-06-07 09:31:33 +00:00
|
|
|
def test_text_table_sell_reason(default_conf):
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
2020-01-09 05:46:39 +00:00
|
|
|
'profit_percent': [0.1, 0.2, -0.1],
|
|
|
|
'profit_abs': [0.2, 0.4, -0.2],
|
2020-01-02 06:33:55 +00:00
|
|
|
'trade_duration': [10, 30, 10],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Sell Reason | Sells | Wins | Draws | Losses |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
2020-02-27 12:28:28 +00:00
|
|
|
'|---------------+---------+--------+---------+----------+'
|
|
|
|
'----------------+----------------+------------------+----------------|\n'
|
|
|
|
'| roi | 2 | 2 | 0 | 0 |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' 15 | 30 | 0.6 | 15 |\n'
|
2020-02-27 12:28:28 +00:00
|
|
|
'| stop_loss | 1 | 0 | 0 | 1 |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' -10 | -10 | -0.2 | -5 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 05:08:15 +00:00
|
|
|
|
|
|
|
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
|
|
|
|
results=results)
|
2020-06-07 09:31:33 +00:00
|
|
|
assert text_table_sell_reason(sell_reason_stats=sell_reason_stats,
|
|
|
|
stake_currency='BTC') == result_str
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
2020-05-25 05:14:21 +00:00
|
|
|
def test_generate_sell_reason_stats(default_conf):
|
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2, -0.1],
|
|
|
|
'profit_abs': [0.2, 0.4, -0.2],
|
|
|
|
'trade_duration': [10, 30, 10],
|
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
|
|
|
|
results=results)
|
|
|
|
roi_result = sell_reason_stats[0]
|
|
|
|
assert roi_result['sell_reason'] == 'roi'
|
|
|
|
assert roi_result['trades'] == 2
|
|
|
|
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
|
|
|
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
|
|
|
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
|
|
|
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
|
|
|
|
|
|
|
stop_result = sell_reason_stats[1]
|
|
|
|
|
|
|
|
assert stop_result['sell_reason'] == 'stop_loss'
|
|
|
|
assert stop_result['trades'] == 1
|
|
|
|
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
|
|
|
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
|
|
|
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
|
|
|
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
|
|
|
|
|
|
|
|
2020-06-07 09:31:33 +00:00
|
|
|
def test_text_table_strategy(default_conf, mocker):
|
2020-01-02 06:33:55 +00:00
|
|
|
results = {}
|
2020-02-07 02:51:50 +00:00
|
|
|
results['TestStrategy1'] = pd.DataFrame(
|
2020-01-02 06:33:55 +00:00
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2, 0.3],
|
|
|
|
'profit_abs': [0.2, 0.4, 0.5],
|
|
|
|
'trade_duration': [10, 30, 10],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
2020-02-07 02:51:50 +00:00
|
|
|
results['TestStrategy2'] = pd.DataFrame(
|
2020-01-02 06:33:55 +00:00
|
|
|
{
|
|
|
|
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
|
|
|
'profit_percent': [0.4, 0.2, 0.3],
|
|
|
|
'profit_abs': [0.4, 0.4, 0.5],
|
|
|
|
'trade_duration': [15, 30, 15],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [4, 1, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
|
|
|
|
' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
|
|
|
'|---------------+--------+----------------+----------------+------------------+'
|
|
|
|
'----------------+----------------+--------+---------+----------|\n'
|
|
|
|
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
|
|
|
|
' 30.00 | 0:17:00 | 3 | 0 | 0 |\n'
|
|
|
|
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
|
|
|
|
' 45.00 | 0:20:00 | 3 | 0 | 0 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 17:55:02 +00:00
|
|
|
|
2020-05-25 18:46:31 +00:00
|
|
|
strategy_results = generate_strategy_metrics(stake_currency='BTC',
|
2020-05-25 17:55:02 +00:00
|
|
|
max_open_trades=2,
|
|
|
|
all_results=results)
|
|
|
|
|
2020-06-07 09:31:33 +00:00
|
|
|
assert text_table_strategy(strategy_results, 'BTC') == result_str
|
2020-01-09 05:52:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_generate_edge_table(edge_conf, mocker):
|
|
|
|
|
|
|
|
results = {}
|
|
|
|
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
|
2020-02-27 12:28:28 +00:00
|
|
|
assert generate_edge_table(results).count('+') == 7
|
2020-01-09 05:52:34 +00:00
|
|
|
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
|
|
|
assert generate_edge_table(results).count(
|
2020-02-07 02:51:50 +00:00
|
|
|
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|