Backtest-reports should calculate total gains based on starting capital

This commit is contained in:
Matthias 2021-02-13 09:01:05 +01:00
parent 8d61a26382
commit 35e6a9ab3a
4 changed files with 44 additions and 17 deletions

View File

@ -252,7 +252,10 @@ A backtesting result will look like that:
| Max open trades | 3 | | Max open trades | 3 |
| | | | | |
| Total trades | 429 | | Total trades | 429 |
| Total Profit % | 152.41% | | Starting capital | 0.01000000 BTC |
| End capital | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
| Total Profit % | 76.2% |
| Trades per day | 3.575 | | Trades per day | 3.575 |
| | | | | |
| Best Pair | LSK/BTC 26.26% | | Best Pair | LSK/BTC 26.26% |
@ -261,6 +264,7 @@ A backtesting result will look like that:
| Worst Trade | ZEC/BTC -10.25% | | Worst Trade | ZEC/BTC -10.25% |
| Best day | 25.27% | | Best day | 25.27% |
| Worst day | -30.67% | | Worst day | -30.67% |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 | | Avg. Duration Loser | 6:55:00 |
| | | | | |
@ -328,7 +332,10 @@ It contains some useful key metrics about performance of your strategy on backte
| Max open trades | 3 | | Max open trades | 3 |
| | | | | |
| Total trades | 429 | | Total trades | 429 |
| Total Profit % | 152.41% | | Starting capital | 0.01000000 BTC |
| End capital | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
| Total Profit % | 76.2% |
| Trades per day | 3.575 | | Trades per day | 3.575 |
| | | | | |
| Best Pair | LSK/BTC 26.26% | | Best Pair | LSK/BTC 26.26% |
@ -337,6 +344,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Worst Trade | ZEC/BTC -10.25% | | Worst Trade | ZEC/BTC -10.25% |
| Best day | 25.27% | | Best day | 25.27% |
| Worst day | -30.67% | | Worst day | -30.67% |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 | | Avg. Duration Loser | 6:55:00 |
| | | | | |
@ -351,11 +359,15 @@ It contains some useful key metrics about performance of your strategy on backte
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower). - `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
- `Total trades`: Identical to the total trades of the backtest output table. - `Total trades`: Identical to the total trades of the backtest output table.
- `Total Profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. - `Starting capital`: Start capital - as given by dry-run-wallet (config or command line).
- `End capital`: Final capital - starting capital + absolute profit.
- `Absolute profit`: Profit made in stake currency.
- `Total Profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital Starting capital) / Starting capital`.
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`. - `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade - `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade
- `Best day` / `Worst day`: Best and worst day based on daily profit. - `Best day` / `Worst day`: Best and worst day based on daily profit.
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). - `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).

View File

@ -56,12 +56,13 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
'Wins', 'Draws', 'Losses'] 'Wins', 'Draws', 'Losses']
def _generate_result_line(result: DataFrame, max_open_trades: int, first_column: str) -> Dict: def _generate_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
""" """
Generate one result dict, with "first_column" as key. Generate one result dict, with "first_column" as key.
""" """
profit_sum = result['profit_ratio'].sum() profit_sum = result['profit_ratio'].sum()
profit_total = profit_sum / max_open_trades # (end-capital - starting capital) / starting capital
profit_total = result['profit_abs'].sum() / starting_balance
return { return {
'key': first_column, 'key': first_column,
@ -88,13 +89,13 @@ def _generate_result_line(result: DataFrame, max_open_trades: int, first_column:
} }
def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, max_open_trades: int, def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_balance: int,
results: DataFrame, skip_nan: bool = False) -> List[Dict]: results: DataFrame, skip_nan: bool = False) -> List[Dict]:
""" """
Generates and returns a list for the given backtest data and the results dataframe Generates and returns a list for the given backtest data and the results dataframe
:param data: Dict of <pair: dataframe> containing data that was used during backtesting. :param data: Dict of <pair: dataframe> containing data that was used during backtesting.
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:param max_open_trades: Maximum allowed open trades :param starting_balance: Starting balance
:param results: Dataframe containing the backtest results :param results: Dataframe containing the backtest results
:param skip_nan: Print "left open" open trades :param skip_nan: Print "left open" open trades
:return: List of Dicts containing the metrics per pair :return: List of Dicts containing the metrics per pair
@ -107,10 +108,10 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, max_open_t
if skip_nan and result['profit_abs'].isnull().all(): if skip_nan and result['profit_abs'].isnull().all():
continue continue
tabular_data.append(_generate_result_line(result, max_open_trades, pair)) tabular_data.append(_generate_result_line(result, starting_balance, pair))
# Append Total # Append Total
tabular_data.append(_generate_result_line(results, max_open_trades, 'TOTAL')) tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL'))
return tabular_data return tabular_data
@ -159,7 +160,7 @@ def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
tabular_data = [] tabular_data = []
for strategy, results in all_results.items(): for strategy, results in all_results.items():
tabular_data.append(_generate_result_line( tabular_data.append(_generate_result_line(
results['results'], results['config']['max_open_trades'], strategy) results['results'], results['config']['dry_run_wallet'], strategy)
) )
return tabular_data return tabular_data
@ -246,15 +247,16 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
continue continue
config = content['config'] config = content['config']
max_open_trades = min(config['max_open_trades'], len(btdata.keys())) max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
starting_balance = config['dry_run_wallet']
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades, starting_balance=starting_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
results=results) results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades, starting_balance=starting_balance,
results=results.loc[results['is_open']], results=results.loc[results['is_open']],
skip_nan=True) skip_nan=True)
daily_stats = generate_daily_stats(results) daily_stats = generate_daily_stats(results)
@ -276,7 +278,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'left_open_trades': left_open_results, 'left_open_trades': left_open_results,
'total_trades': len(results), 'total_trades': len(results),
'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0, 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
'profit_total': results['profit_ratio'].sum() / max_open_trades, 'profit_total': results['profit_abs'].sum() / starting_balance,
'profit_total_abs': results['profit_abs'].sum(), 'profit_total_abs': results['profit_abs'].sum(),
'backtest_start': min_date.datetime, 'backtest_start': min_date.datetime,
'backtest_start_ts': min_date.int_timestamp * 1000, 'backtest_start_ts': min_date.int_timestamp * 1000,
@ -292,6 +294,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'pairlist': list(btdata.keys()), 'pairlist': list(btdata.keys()),
'stake_amount': config['stake_amount'], 'stake_amount': config['stake_amount'],
'stake_currency': config['stake_currency'], 'stake_currency': config['stake_currency'],
'starting_balance': starting_balance,
'dry_run_wallet': starting_balance,
'final_balance': content['final_balance'],
'max_open_trades': max_open_trades, 'max_open_trades': max_open_trades,
'max_open_trades_setting': (config['max_open_trades'] 'max_open_trades_setting': (config['max_open_trades']
if config['max_open_trades'] != float('inf') else -1), if config['max_open_trades'] != float('inf') else -1),
@ -431,6 +436,13 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('Max open trades', strat_results['max_open_trades']), ('Max open trades', strat_results['max_open_trades']),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Total trades', strat_results['total_trades']), ('Total trades', strat_results['total_trades']),
('Starting capital', round_coin_value(strat_results['starting_balance'],
strat_results['stake_currency'])),
('End capital', round_coin_value(strat_results['final_balance'],
strat_results['stake_currency'])),
('Absolute profit ', round_coin_value(strat_results['profit_total_abs'],
strat_results['stake_currency'])),
('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), ('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"),
('Trades per day', strat_results['trades_per_day']), ('Trades per day', strat_results['trades_per_day']),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability

View File

@ -261,6 +261,7 @@ def get_default_conf(testdatadir):
"20": 0.02, "20": 0.02,
"0": 0.04 "0": 0.04
}, },
"dry_run_wallet": 1000,
"stoploss": -0.10, "stoploss": -0.10,
"unfilledtimeout": { "unfilledtimeout": {
"buy": 10, "buy": 10,

View File

@ -48,7 +48,7 @@ def test_text_table_bt_results():
) )
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
max_open_trades=2, results=results) starting_balance=4, results=results)
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
@ -78,6 +78,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
}), }),
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'final_balance': 1000.02,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp,
} }
@ -189,7 +190,7 @@ def test_generate_pair_metrics():
) )
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
max_open_trades=2, results=results) starting_balance=2, results=results)
assert isinstance(pair_results, list) assert isinstance(pair_results, list)
assert len(pair_results) == 2 assert len(pair_results) == 2
assert pair_results[-1]['key'] == 'TOTAL' assert pair_results[-1]['key'] == 'TOTAL'
@ -291,6 +292,7 @@ def test_generate_sell_reason_stats():
def test_text_table_strategy(default_conf): def test_text_table_strategy(default_conf):
default_conf['max_open_trades'] = 2 default_conf['max_open_trades'] = 2
default_conf['dry_run_wallet'] = 3
results = {} results = {}
results['TestStrategy1'] = {'results': pd.DataFrame( results['TestStrategy1'] = {'results': pd.DataFrame(
{ {
@ -323,9 +325,9 @@ def test_text_table_strategy(default_conf):
'|---------------+--------+----------------+----------------+------------------+' '|---------------+--------+----------------+----------------+------------------+'
'----------------+----------------+--------+---------+----------|\n' '----------------+----------------+--------+---------+----------|\n'
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
' 30.00 | 0:17:00 | 3 | 0 | 0 |\n' ' 36.67 | 0:17:00 | 3 | 0 | 0 |\n'
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
' 45.00 | 0:20:00 | 3 | 0 | 0 |' ' 43.33 | 0:20:00 | 3 | 0 | 0 |'
) )
strategy_results = generate_strategy_metrics(all_results=results) strategy_results = generate_strategy_metrics(all_results=results)