Merge pull request #10022 from freqtrade/align-exitreasons
Align exitreasons, remove duplicated code
This commit is contained in:
@@ -6,13 +6,12 @@ from freqtrade.optimize.optimize_reports.bt_output import (generate_edge_table,
|
|||||||
show_sorted_pairlist,
|
show_sorted_pairlist,
|
||||||
text_table_add_metrics,
|
text_table_add_metrics,
|
||||||
text_table_bt_results,
|
text_table_bt_results,
|
||||||
text_table_exit_reason,
|
|
||||||
text_table_periodic_breakdown,
|
text_table_periodic_breakdown,
|
||||||
text_table_strategy, text_table_tags)
|
text_table_strategy, text_table_tags)
|
||||||
from freqtrade.optimize.optimize_reports.bt_storage import (store_backtest_analysis_results,
|
from freqtrade.optimize.optimize_reports.bt_storage import (store_backtest_analysis_results,
|
||||||
store_backtest_stats)
|
store_backtest_stats)
|
||||||
from freqtrade.optimize.optimize_reports.optimize_reports import (
|
from freqtrade.optimize.optimize_reports.optimize_reports import (
|
||||||
generate_all_periodic_breakdown_stats, generate_backtest_stats, generate_daily_stats,
|
generate_all_periodic_breakdown_stats, generate_backtest_stats, generate_daily_stats,
|
||||||
generate_exit_reason_stats, generate_pair_metrics, generate_periodic_breakdown_stats,
|
generate_pair_metrics, generate_periodic_breakdown_stats, generate_rejected_signals,
|
||||||
generate_rejected_signals, generate_strategy_comparison, generate_strategy_stats,
|
generate_strategy_comparison, generate_strategy_stats, generate_tag_metrics,
|
||||||
generate_tag_metrics, generate_trade_signal_candles, generate_trading_stats)
|
generate_trade_signal_candles, generate_trading_stats)
|
||||||
|
|||||||
@@ -60,32 +60,6 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
|
|||||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
||||||
|
|
||||||
|
|
||||||
def text_table_exit_reason(exit_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
|
|
||||||
"""
|
|
||||||
Generate small table outlining Backtest results
|
|
||||||
:param exit_reason_stats: Exit reason metrics
|
|
||||||
:param stake_currency: Stakecurrency used
|
|
||||||
:return: pretty printed table with tabulate as string
|
|
||||||
"""
|
|
||||||
headers = [
|
|
||||||
'Exit Reason',
|
|
||||||
'Exits',
|
|
||||||
'Win Draws Loss Win%',
|
|
||||||
'Avg Profit %',
|
|
||||||
f'Tot Profit {stake_currency}',
|
|
||||||
'Tot Profit %',
|
|
||||||
]
|
|
||||||
|
|
||||||
output = [[
|
|
||||||
t.get('exit_reason', t.get('sell_reason')), t['trades'],
|
|
||||||
generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
|
|
||||||
t['profit_mean_pct'],
|
|
||||||
fmt_coin(t['profit_total_abs'], stake_currency, False),
|
|
||||||
t['profit_total_pct'],
|
|
||||||
] for t in exit_reason_stats]
|
|
||||||
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
|
|
||||||
|
|
||||||
|
|
||||||
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
|
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
|
||||||
"""
|
"""
|
||||||
Generates and returns a text table for the given backtest data and the results dataframe
|
Generates and returns a text table for the given backtest data and the results dataframe
|
||||||
@@ -93,20 +67,23 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
|
|||||||
:param stake_currency: stake-currency - used to correctly name headers
|
:param stake_currency: stake-currency - used to correctly name headers
|
||||||
:return: pretty printed table with tabulate as string
|
:return: pretty printed table with tabulate as string
|
||||||
"""
|
"""
|
||||||
|
fallback: str = ''
|
||||||
if (tag_type == "enter_tag"):
|
if (tag_type == "enter_tag"):
|
||||||
headers = _get_line_header("TAG", stake_currency)
|
headers = _get_line_header("TAG", stake_currency)
|
||||||
else:
|
else:
|
||||||
headers = _get_line_header("TAG", stake_currency, 'Exits')
|
headers = _get_line_header("Exit Reason", stake_currency, 'Exits')
|
||||||
|
fallback = 'exit_reason'
|
||||||
|
|
||||||
floatfmt = _get_line_floatfmt(stake_currency)
|
floatfmt = _get_line_floatfmt(stake_currency)
|
||||||
output = [
|
output = [
|
||||||
[
|
[
|
||||||
t['key'] if t['key'] is not None and len(
|
t['key'] if t.get('key') is not None and len(
|
||||||
t['key']) > 0 else "OTHER",
|
str(t['key'])) > 0 else t.get(fallback, "OTHER"),
|
||||||
t['trades'],
|
t['trades'],
|
||||||
t['profit_mean_pct'],
|
t['profit_mean_pct'],
|
||||||
t['profit_total_abs'],
|
t['profit_total_abs'],
|
||||||
t['profit_total_pct'],
|
t['profit_total_pct'],
|
||||||
t['duration_avg'],
|
t.get('duration_avg'),
|
||||||
generate_wins_draws_losses(
|
generate_wins_draws_losses(
|
||||||
t['wins'],
|
t['wins'],
|
||||||
t['draws'],
|
t['draws'],
|
||||||
@@ -317,17 +294,16 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
if (results.get('results_per_enter_tag') is not None):
|
if (enter_tags := results.get('results_per_enter_tag')) is not None:
|
||||||
table = text_table_tags("enter_tag", results['results_per_enter_tag'], stake_currency)
|
table = text_table_tags("enter_tag", enter_tags, stake_currency)
|
||||||
|
|
||||||
if isinstance(table, str) and len(table) > 0:
|
if isinstance(table, str) and len(table) > 0:
|
||||||
print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '='))
|
print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
exit_reasons = results.get('exit_reason_summary')
|
if (exit_reasons := results.get('exit_reason_summary')) is not None:
|
||||||
if exit_reasons:
|
table = text_table_tags("exit_tag", exit_reasons, stake_currency)
|
||||||
table = text_table_exit_reason(exit_reason_stats=exit_reasons,
|
|
||||||
stake_currency=stake_currency)
|
|
||||||
if isinstance(table, str) and len(table) > 0:
|
if isinstance(table, str) and len(table) > 0:
|
||||||
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Tuple, Union
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pandas import DataFrame, Series, concat, to_datetime
|
from pandas import DataFrame, Series, concat, to_datetime
|
||||||
|
|
||||||
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, IntOrInf
|
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
|
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
|
||||||
calculate_expectancy, calculate_market_change,
|
calculate_expectancy, calculate_market_change,
|
||||||
calculate_max_drawdown, calculate_sharpe, calculate_sortino)
|
calculate_max_drawdown, calculate_sharpe, calculate_sortino)
|
||||||
@@ -71,7 +71,8 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column
|
|||||||
'key': first_column,
|
'key': first_column,
|
||||||
'trades': len(result),
|
'trades': len(result),
|
||||||
'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
|
'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
|
||||||
'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0,
|
'profit_mean_pct': round(result['profit_ratio'].mean() * 100.0, 2
|
||||||
|
) if len(result) > 0 else 0.0,
|
||||||
'profit_sum': profit_sum,
|
'profit_sum': profit_sum,
|
||||||
'profit_sum_pct': round(profit_sum * 100.0, 2),
|
'profit_sum_pct': round(profit_sum * 100.0, 2),
|
||||||
'profit_total_abs': result['profit_abs'].sum(),
|
'profit_total_abs': result['profit_abs'].sum(),
|
||||||
@@ -154,42 +155,6 @@ def generate_tag_metrics(tag_type: str,
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def generate_exit_reason_stats(max_open_trades: IntOrInf, results: DataFrame) -> List[Dict]:
|
|
||||||
"""
|
|
||||||
Generate small table outlining Backtest results
|
|
||||||
:param max_open_trades: Max_open_trades parameter
|
|
||||||
:param results: Dataframe containing the backtest result for one strategy
|
|
||||||
:return: List of Dicts containing the metrics per Sell reason
|
|
||||||
"""
|
|
||||||
tabular_data = []
|
|
||||||
|
|
||||||
for reason, count in results['exit_reason'].value_counts().items():
|
|
||||||
result = results.loc[results['exit_reason'] == reason]
|
|
||||||
|
|
||||||
profit_mean = result['profit_ratio'].mean()
|
|
||||||
profit_sum = result['profit_ratio'].sum()
|
|
||||||
profit_total = profit_sum / max_open_trades
|
|
||||||
|
|
||||||
tabular_data.append(
|
|
||||||
{
|
|
||||||
'exit_reason': reason,
|
|
||||||
'trades': count,
|
|
||||||
'wins': len(result[result['profit_abs'] > 0]),
|
|
||||||
'draws': len(result[result['profit_abs'] == 0]),
|
|
||||||
'losses': len(result[result['profit_abs'] < 0]),
|
|
||||||
'winrate': len(result[result['profit_abs'] > 0]) / count if count else 0.0,
|
|
||||||
'profit_mean': profit_mean,
|
|
||||||
'profit_mean_pct': round(profit_mean * 100, 2),
|
|
||||||
'profit_sum': profit_sum,
|
|
||||||
'profit_sum_pct': round(profit_sum * 100, 2),
|
|
||||||
'profit_total_abs': result['profit_abs'].sum(),
|
|
||||||
'profit_total': profit_total,
|
|
||||||
'profit_total_pct': round(profit_total * 100, 2),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return tabular_data
|
|
||||||
|
|
||||||
|
|
||||||
def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]:
|
def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Generate summary per strategy
|
Generate summary per strategy
|
||||||
@@ -383,9 +348,8 @@ def generate_strategy_stats(pairlist: List[str],
|
|||||||
|
|
||||||
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
|
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
|
||||||
results=results, skip_nan=False)
|
results=results, skip_nan=False)
|
||||||
|
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=start_balance,
|
||||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=max_open_trades,
|
results=results, skip_nan=False)
|
||||||
results=results)
|
|
||||||
left_open_results = generate_pair_metrics(
|
left_open_results = generate_pair_metrics(
|
||||||
pairlist, stake_currency=stake_currency, starting_balance=start_balance,
|
pairlist, stake_currency=stake_currency, starting_balance=start_balance,
|
||||||
results=results.loc[results['exit_reason'] == 'force_exit'], skip_nan=True)
|
results=results.loc[results['exit_reason'] == 'force_exit'], skip_nan=True)
|
||||||
|
|||||||
@@ -1473,7 +1473,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||||
text_table_mock = MagicMock()
|
text_table_mock = MagicMock()
|
||||||
sell_reason_mock = MagicMock()
|
tag_metrics_mock = MagicMock()
|
||||||
strattable_mock = MagicMock()
|
strattable_mock = MagicMock()
|
||||||
strat_summary = MagicMock()
|
strat_summary = MagicMock()
|
||||||
|
|
||||||
@@ -1483,7 +1483,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
)
|
)
|
||||||
mocker.patch.multiple('freqtrade.optimize.optimize_reports.optimize_reports',
|
mocker.patch.multiple('freqtrade.optimize.optimize_reports.optimize_reports',
|
||||||
generate_pair_metrics=MagicMock(),
|
generate_pair_metrics=MagicMock(),
|
||||||
generate_exit_reason_stats=sell_reason_mock,
|
generate_tag_metrics=tag_metrics_mock,
|
||||||
generate_strategy_comparison=strat_summary,
|
generate_strategy_comparison=strat_summary,
|
||||||
generate_daily_stats=MagicMock(),
|
generate_daily_stats=MagicMock(),
|
||||||
)
|
)
|
||||||
@@ -1508,7 +1508,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
assert backtestmock.call_count == 2
|
assert backtestmock.call_count == 2
|
||||||
assert text_table_mock.call_count == 4
|
assert text_table_mock.call_count == 4
|
||||||
assert strattable_mock.call_count == 1
|
assert strattable_mock.call_count == 1
|
||||||
assert sell_reason_mock.call_count == 2
|
assert tag_metrics_mock.call_count == 4
|
||||||
assert strat_summary.call_count == 1
|
assert strat_summary.call_count == 1
|
||||||
|
|
||||||
# check the logs, that will contain the backtest result
|
# check the logs, that will contain the backtest result
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backte
|
|||||||
from freqtrade.edge import PairInfo
|
from freqtrade.edge import PairInfo
|
||||||
from freqtrade.enums import ExitType
|
from freqtrade.enums import ExitType
|
||||||
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
|
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
|
||||||
generate_edge_table, generate_exit_reason_stats,
|
generate_edge_table, generate_pair_metrics,
|
||||||
generate_pair_metrics,
|
|
||||||
generate_periodic_breakdown_stats,
|
generate_periodic_breakdown_stats,
|
||||||
generate_strategy_comparison,
|
generate_strategy_comparison,
|
||||||
generate_trading_stats, show_sorted_pairlist,
|
generate_trading_stats, show_sorted_pairlist,
|
||||||
store_backtest_analysis_results,
|
store_backtest_analysis_results,
|
||||||
store_backtest_stats, text_table_bt_results,
|
store_backtest_stats, text_table_bt_results,
|
||||||
text_table_exit_reason, text_table_strategy)
|
text_table_strategy)
|
||||||
|
from freqtrade.optimize.optimize_reports.bt_output import text_table_tags
|
||||||
from freqtrade.optimize.optimize_reports.optimize_reports import (_get_resample_from_period,
|
from freqtrade.optimize.optimize_reports.optimize_reports import (_get_resample_from_period,
|
||||||
calc_streak)
|
calc_streak, generate_tag_metrics)
|
||||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||||
from freqtrade.util import dt_ts
|
from freqtrade.util import dt_ts
|
||||||
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
||||||
@@ -392,20 +392,21 @@ def test_text_table_exit_reason():
|
|||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Exit Reason | Exits | Win Draws Loss Win% | Avg Profit % |'
|
'| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |'
|
||||||
' Tot Profit BTC | Tot Profit % |\n'
|
' Avg Duration | Win Draw Loss Win% |\n'
|
||||||
'|---------------+---------+--------------------------+----------------+'
|
'|---------------+---------+----------------+------------------+----------------+'
|
||||||
'------------------+----------------|\n'
|
'----------------+-------------------------|\n'
|
||||||
'| roi | 2 | 2 0 0 100 | 15 |'
|
'| roi | 2 | 15.00 | 0.60000000 | 2.73 |'
|
||||||
' 0.6 | 15 |\n'
|
' 0:20:00 | 2 0 0 100 |\n'
|
||||||
'| stop_loss | 1 | 0 0 1 0 | -10 |'
|
'| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |'
|
||||||
' -0.2 | -5 |'
|
' 0:10:00 | 0 0 1 0 |\n'
|
||||||
|
'| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |'
|
||||||
|
' 0:17:00 | 2 0 1 66.7 |'
|
||||||
)
|
)
|
||||||
|
|
||||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
|
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
|
||||||
results=results)
|
results=results, skip_nan=False)
|
||||||
assert text_table_exit_reason(exit_reason_stats=exit_reason_stats,
|
assert text_table_tags('exit_tag', exit_reason_stats, 'BTC') == result_str
|
||||||
stake_currency='BTC') == result_str
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_sell_reason_stats():
|
def test_generate_sell_reason_stats():
|
||||||
@@ -423,10 +424,10 @@ def test_generate_sell_reason_stats():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
|
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
|
||||||
results=results)
|
results=results, skip_nan=False)
|
||||||
roi_result = exit_reason_stats[0]
|
roi_result = exit_reason_stats[0]
|
||||||
assert roi_result['exit_reason'] == 'roi'
|
assert roi_result['key'] == 'roi'
|
||||||
assert roi_result['trades'] == 2
|
assert roi_result['trades'] == 2
|
||||||
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
||||||
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
||||||
@@ -435,7 +436,7 @@ def test_generate_sell_reason_stats():
|
|||||||
|
|
||||||
stop_result = exit_reason_stats[1]
|
stop_result = exit_reason_stats[1]
|
||||||
|
|
||||||
assert stop_result['exit_reason'] == 'stop_loss'
|
assert stop_result['key'] == 'stop_loss'
|
||||||
assert stop_result['trades'] == 1
|
assert stop_result['trades'] == 1
|
||||||
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
||||||
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user