Max relative drawdown
This commit is contained in:
parent
d054916439
commit
47a6ef4f00
@ -116,7 +116,9 @@ optional arguments:
|
||||
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
|
||||
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
|
||||
SortinoHyperOptLoss, SortinoHyperOptLossDaily,
|
||||
CalmarHyperOptLoss, MaxDrawDownHyperOptLoss, ProfitDrawDownHyperOptLoss
|
||||
CalmarHyperOptLoss, MaxDrawDownHyperOptLoss,
|
||||
MaxDrawDownRelativeHyperOptLoss,
|
||||
ProfitDrawDownHyperOptLoss
|
||||
--disable-param-export
|
||||
Disable automatic hyperopt parameter export.
|
||||
--ignore-missing-spaces, --ignore-unparameterized-spaces
|
||||
@ -563,7 +565,8 @@ Currently, the following loss functions are builtin:
|
||||
* `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation.
|
||||
* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
|
||||
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
|
||||
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown.
|
||||
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown.
|
||||
* `MaxDrawDownRelativeHyperOptLoss` - Similar as the above, but also optimizes Maximum relative drawdown.
|
||||
* `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown.
|
||||
* `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes.
|
||||
|
||||
|
@ -28,7 +28,8 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
|
||||
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
|
||||
'CalmarHyperOptLoss',
|
||||
'MaxDrawDownHyperOptLoss', 'ProfitDrawDownHyperOptLoss']
|
||||
'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss',
|
||||
'ProfitDrawDownHyperOptLoss']
|
||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
||||
'AgeFilter', 'OffsetFilter', 'PerformanceFilter',
|
||||
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
|
||||
|
@ -441,18 +441,22 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
|
||||
return df
|
||||
|
||||
|
||||
def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str
|
||||
) -> pd.DataFrame:
|
||||
def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str,
|
||||
starting_balance : Optional[float] = 0.0) -> pd.DataFrame:
|
||||
max_drawdown_df = pd.DataFrame()
|
||||
max_drawdown_df['cumulative'] = profit_results[value_col].cumsum()
|
||||
max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
|
||||
max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
|
||||
max_drawdown_df['date'] = profit_results.loc[:, date_col]
|
||||
if starting_balance:
|
||||
cumulative_balance = starting_balance + max_drawdown_df['cumulative']
|
||||
max_balance = starting_balance + max_drawdown_df['high_value']
|
||||
max_drawdown_df['drawdown_relative'] = ((max_balance - cumulative_balance) / max_balance)
|
||||
return max_drawdown_df
|
||||
|
||||
|
||||
def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
||||
value_col: str = 'profit_ratio'
|
||||
value_col: str = 'profit_ratio', starting_balance : Optional[float] = 0.0
|
||||
):
|
||||
"""
|
||||
Calculate max drawdown and the corresponding close dates
|
||||
@ -466,13 +470,14 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
||||
if len(trades) == 0:
|
||||
raise ValueError("Trade dataframe empty.")
|
||||
profit_results = trades.sort_values(date_col).reset_index(drop=True)
|
||||
max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col)
|
||||
max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance)
|
||||
|
||||
return max_drawdown_df
|
||||
|
||||
|
||||
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
||||
value_col: str = 'profit_abs', starting_balance: float = 0
|
||||
value_col: str = 'profit_abs', starting_balance: float = 0,
|
||||
relative: bool = False
|
||||
) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]:
|
||||
"""
|
||||
Calculate max drawdown and the corresponding close dates
|
||||
@ -488,9 +493,9 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
|
||||
if len(trades) == 0:
|
||||
raise ValueError("Trade dataframe empty.")
|
||||
profit_results = trades.sort_values(date_col).reset_index(drop=True)
|
||||
max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col)
|
||||
max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance)
|
||||
|
||||
idxmin = max_drawdown_df['drawdown'].idxmin()
|
||||
idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative else max_drawdown_df['drawdown'].idxmin()
|
||||
if idxmin == 0:
|
||||
raise ValueError("No losing trade, therefore no drawdown.")
|
||||
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
||||
@ -499,8 +504,8 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
|
||||
['high_value'].idxmax(), 'cumulative']
|
||||
low_val = max_drawdown_df.loc[idxmin, 'cumulative']
|
||||
max_drawdown_rel = 0.0
|
||||
if high_val + starting_balance != 0:
|
||||
max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance)
|
||||
if starting_balance != 0:
|
||||
max_drawdown_rel = max_drawdown_df.loc[idxmin, 'drawdown_relative']
|
||||
|
||||
return (
|
||||
abs(min(max_drawdown_df['drawdown'])),
|
||||
|
45
freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py
Normal file
45
freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""
|
||||
MaxDrawDownRelativeHyperOptLoss
|
||||
|
||||
This module defines the alternative HyperOptLoss class which can be used for
|
||||
Hyperoptimization.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.data.btanalysis import calculate_underwater, calculate_max_drawdown
|
||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||
|
||||
|
||||
class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss):
|
||||
|
||||
"""
|
||||
Defines the loss function for hyperopt.
|
||||
|
||||
This implementation optimizes for max draw down and profit
|
||||
Less max drawdown more profit -> Lower return value
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def hyperopt_loss_function(results: DataFrame, config: Dict,
|
||||
*args, **kwargs) -> float:
|
||||
|
||||
"""
|
||||
Objective function.
|
||||
|
||||
Uses profit ratio weighted max_drawdown when drawdown is available.
|
||||
Otherwise directly optimizes profit ratio.
|
||||
"""
|
||||
total_profit = results['profit_abs'].sum()
|
||||
try:
|
||||
drawdown_df = calculate_underwater(results, value_col='profit_abs', starting_balance=config['available_capital'])
|
||||
max_drawdown = abs(min(drawdown_df['drawdown']))
|
||||
relative_drawdown = max(drawdown_df['drawdown_relative'])
|
||||
if max_drawdown == 0:
|
||||
return -total_profit
|
||||
return -total_profit / max_drawdown / relative_drawdown
|
||||
except (Exception, ValueError):
|
||||
return -total_profit
|
||||
|
@ -474,9 +474,12 @@ def generate_strategy_stats(pairlist: List[str],
|
||||
(drawdown_abs, drawdown_start, drawdown_end, high_val, low_val,
|
||||
max_drawdown) = calculate_max_drawdown(
|
||||
results, value_col='profit_abs', starting_balance=start_balance)
|
||||
(_, _, _, _, _, max_relative_drawdown) = calculate_max_drawdown(
|
||||
results, value_col='profit_abs', starting_balance=start_balance, relative=True)
|
||||
strat_stats.update({
|
||||
'max_drawdown': max_drawdown_legacy, # Deprecated - do not use
|
||||
'max_drawdown_account': max_drawdown,
|
||||
'max_relative_drawdown': max_relative_drawdown,
|
||||
'max_drawdown_abs': drawdown_abs,
|
||||
'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT),
|
||||
'drawdown_start_ts': drawdown_start.timestamp() * 1000,
|
||||
@ -497,6 +500,7 @@ def generate_strategy_stats(pairlist: List[str],
|
||||
strat_stats.update({
|
||||
'max_drawdown': 0.0,
|
||||
'max_drawdown_account': 0.0,
|
||||
'max_relative_drawdown': 0.0,
|
||||
'max_drawdown_abs': 0.0,
|
||||
'max_drawdown_low': 0.0,
|
||||
'max_drawdown_high': 0.0,
|
||||
@ -760,10 +764,11 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
||||
strat_results['stake_currency'])),
|
||||
|
||||
# Compatibility to show old hyperopt results
|
||||
('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}")
|
||||
('Max % of account underwater', f"{strat_results['max_relative_drawdown']:.2%}"),
|
||||
('Absolute Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}")
|
||||
if 'max_drawdown_account' in strat_results else (
|
||||
'Drawdown', f"{strat_results['max_drawdown']:.2%}"),
|
||||
('Drawdown', round_coin_value(strat_results['max_drawdown_abs'],
|
||||
('Absolute Drawdown', round_coin_value(strat_results['max_drawdown_abs'],
|
||||
strat_results['stake_currency'])),
|
||||
('Drawdown high', round_coin_value(strat_results['max_drawdown_high'],
|
||||
strat_results['stake_currency'])),
|
||||
|
Loading…
Reference in New Issue
Block a user