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 |
| | |
| 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 |
| | |
| Best Pair | LSK/BTC 26.26% |
@ -261,6 +264,7 @@ A backtesting result will look like that:
| Worst Trade | ZEC/BTC -10.25% |
| Best day | 25.27% |
| Worst day | -30.67% |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23: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 |
| | |
| 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 |
| | |
| 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% |
| Best day | 25.27% |
| Worst day | -30.67% |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23: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).
- `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 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).
- `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 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.
- `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).

View File

@ -56,12 +56,13 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
'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.
"""
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 {
'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]:
"""
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 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 skip_nan: Print "left open" open trades
: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():
continue
tabular_data.append(_generate_result_line(result, max_open_trades, pair))
tabular_data.append(_generate_result_line(result, starting_balance, pair))
# 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
@ -159,7 +160,7 @@ def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
tabular_data = []
for strategy, results in all_results.items():
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
@ -246,15 +247,16 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
continue
config = content['config']
max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
starting_balance = config['dry_run_wallet']
stake_currency = config['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)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
results=results)
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']],
skip_nan=True)
daily_stats = generate_daily_stats(results)
@ -276,7 +278,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'left_open_trades': left_open_results,
'total_trades': len(results),
'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(),
'backtest_start': min_date.datetime,
'backtest_start_ts': min_date.int_timestamp * 1000,
@ -292,6 +294,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'pairlist': list(btdata.keys()),
'stake_amount': config['stake_amount'],
'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_setting': (config['max_open_trades']
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']),
('', ''), # Empty line to improve readability
('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)}%"),
('Trades per day', strat_results['trades_per_day']),
('', ''), # Empty line to improve readability

View File

@ -261,6 +261,7 @@ def get_default_conf(testdatadir):
"20": 0.02,
"0": 0.04
},
"dry_run_wallet": 1000,
"stoploss": -0.10,
"unfilledtimeout": {
"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',
max_open_trades=2, results=results)
starting_balance=4, results=results)
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,
'locks': [],
'final_balance': 1000.02,
'backtest_start_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',
max_open_trades=2, results=results)
starting_balance=2, results=results)
assert isinstance(pair_results, list)
assert len(pair_results) == 2
assert pair_results[-1]['key'] == 'TOTAL'
@ -291,6 +292,7 @@ def test_generate_sell_reason_stats():
def test_text_table_strategy(default_conf):
default_conf['max_open_trades'] = 2
default_conf['dry_run_wallet'] = 3
results = {}
results['TestStrategy1'] = {'results': pd.DataFrame(
{
@ -323,9 +325,9 @@ def test_text_table_strategy(default_conf):
'|---------------+--------+----------------+----------------+------------------+'
'----------------+----------------+--------+---------+----------|\n'
'| 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 |'
' 45.00 | 0:20:00 | 3 | 0 | 0 |'
' 43.33 | 0:20:00 | 3 | 0 | 0 |'
)
strategy_results = generate_strategy_metrics(all_results=results)