Use absolute drawdown calc
This commit is contained in:
parent
74fc4bdab5
commit
0d2f877e77
@ -360,13 +360,14 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
|
|||||||
|
|
||||||
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
||||||
value_col: str = 'profit_ratio'
|
value_col: str = 'profit_ratio'
|
||||||
) -> Tuple[float, pd.Timestamp, pd.Timestamp]:
|
) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]:
|
||||||
"""
|
"""
|
||||||
Calculate max drawdown and the corresponding close dates
|
Calculate max drawdown and the corresponding close dates
|
||||||
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
|
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
|
||||||
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
|
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
|
||||||
:param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio')
|
:param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio')
|
||||||
:return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time
|
:return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown,
|
||||||
|
high and low time and high and low value.
|
||||||
:raise: ValueError if trade-dataframe was found empty.
|
:raise: ValueError if trade-dataframe was found empty.
|
||||||
"""
|
"""
|
||||||
if len(trades) == 0:
|
if len(trades) == 0:
|
||||||
@ -382,7 +383,10 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
|
|||||||
raise ValueError("No losing trade, therefore no drawdown.")
|
raise ValueError("No losing trade, therefore no drawdown.")
|
||||||
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
||||||
low_date = profit_results.loc[idxmin, date_col]
|
low_date = profit_results.loc[idxmin, date_col]
|
||||||
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date
|
high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin]
|
||||||
|
['high_value'].idxmax(), 'cumulative']
|
||||||
|
low_val = max_drawdown_df.loc[idxmin, 'cumulative']
|
||||||
|
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val
|
||||||
|
|
||||||
|
|
||||||
def calculate_csum(trades: pd.DataFrame) -> Tuple[float, float]:
|
def calculate_csum(trades: pd.DataFrame) -> Tuple[float, float]:
|
||||||
|
@ -322,14 +322,20 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
result['strategy'][strategy] = strat_stats
|
result['strategy'][strategy] = strat_stats
|
||||||
|
|
||||||
try:
|
try:
|
||||||
max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown(
|
max_drawdown, _, _, _, _ = calculate_max_drawdown(
|
||||||
results, value_col='profit_ratio')
|
results, value_col='profit_ratio')
|
||||||
|
drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
|
||||||
|
results, value_col='profit_abs')
|
||||||
strat_stats.update({
|
strat_stats.update({
|
||||||
'max_drawdown': max_drawdown,
|
'max_drawdown': max_drawdown,
|
||||||
|
'max_drawdown_abs': drawdown_abs,
|
||||||
'drawdown_start': drawdown_start,
|
'drawdown_start': drawdown_start,
|
||||||
'drawdown_start_ts': drawdown_start.timestamp() * 1000,
|
'drawdown_start_ts': drawdown_start.timestamp() * 1000,
|
||||||
'drawdown_end': drawdown_end,
|
'drawdown_end': drawdown_end,
|
||||||
'drawdown_end_ts': drawdown_end.timestamp() * 1000,
|
'drawdown_end_ts': drawdown_end.timestamp() * 1000,
|
||||||
|
|
||||||
|
'max_drawdown_low': low_val,
|
||||||
|
'max_drawdown_high': high_val,
|
||||||
})
|
})
|
||||||
|
|
||||||
csum_min, csum_max = calculate_csum(results)
|
csum_min, csum_max = calculate_csum(results)
|
||||||
@ -341,6 +347,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
strat_stats.update({
|
strat_stats.update({
|
||||||
'max_drawdown': 0.0,
|
'max_drawdown': 0.0,
|
||||||
|
'max_drawdown_abs': 0.0,
|
||||||
|
'max_drawdown_low': 0.0,
|
||||||
|
'max_drawdown_high': 0.0,
|
||||||
'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc),
|
'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc),
|
||||||
'drawdown_start_ts': 0,
|
'drawdown_start_ts': 0,
|
||||||
'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc),
|
'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc),
|
||||||
@ -471,6 +480,12 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
strat_results['stake_currency'])),
|
strat_results['stake_currency'])),
|
||||||
|
|
||||||
('Max Drawdown', f"{round(strat_results['max_drawdown'] * 100, 2)}%"),
|
('Max Drawdown', f"{round(strat_results['max_drawdown'] * 100, 2)}%"),
|
||||||
|
('Max Drawdown', round_coin_value(strat_results['max_drawdown_abs'],
|
||||||
|
strat_results['stake_currency'])),
|
||||||
|
('Max Drawdown high', round_coin_value(strat_results['max_drawdown_high'],
|
||||||
|
strat_results['stake_currency'])),
|
||||||
|
('Max Drawdown low', round_coin_value(strat_results['max_drawdown_low'],
|
||||||
|
strat_results['stake_currency'])),
|
||||||
('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)),
|
('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)),
|
||||||
('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)),
|
('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)),
|
||||||
('Market change', f"{round(strat_results['market_change'] * 100, 2)}%"),
|
('Market change', f"{round(strat_results['market_change'] * 100, 2)}%"),
|
||||||
|
@ -145,7 +145,7 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame,
|
|||||||
Add scatter points indicating max drawdown
|
Add scatter points indicating max drawdown
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
max_drawdown, highdate, lowdate = calculate_max_drawdown(trades)
|
max_drawdown, highdate, lowdate, _, _ = calculate_max_drawdown(trades)
|
||||||
|
|
||||||
drawdown = go.Scatter(
|
drawdown = go.Scatter(
|
||||||
x=[highdate, lowdate],
|
x=[highdate, lowdate],
|
||||||
|
@ -55,7 +55,7 @@ class MaxDrawdown(IProtection):
|
|||||||
|
|
||||||
# Drawdown is always positive
|
# Drawdown is always positive
|
||||||
try:
|
try:
|
||||||
drawdown, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit')
|
drawdown, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False, None, None
|
return False, None, None
|
||||||
|
|
||||||
|
@ -274,15 +274,17 @@ def test_create_cum_profit1(testdatadir):
|
|||||||
def test_calculate_max_drawdown(testdatadir):
|
def test_calculate_max_drawdown(testdatadir):
|
||||||
filename = testdatadir / "backtest-result_test.json"
|
filename = testdatadir / "backtest-result_test.json"
|
||||||
bt_data = load_backtest_data(filename)
|
bt_data = load_backtest_data(filename)
|
||||||
drawdown, h, low = calculate_max_drawdown(bt_data)
|
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(bt_data)
|
||||||
assert isinstance(drawdown, float)
|
assert isinstance(drawdown, float)
|
||||||
assert pytest.approx(drawdown) == 0.21142322
|
assert pytest.approx(drawdown) == 0.21142322
|
||||||
assert isinstance(h, Timestamp)
|
assert isinstance(hdate, Timestamp)
|
||||||
assert isinstance(low, Timestamp)
|
assert isinstance(lowdate, Timestamp)
|
||||||
assert h == Timestamp('2018-01-24 14:25:00', tz='UTC')
|
assert isinstance(hval, float)
|
||||||
assert low == Timestamp('2018-01-30 04:45:00', tz='UTC')
|
assert isinstance(lval, float)
|
||||||
|
assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC')
|
||||||
|
assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC')
|
||||||
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
||||||
drawdown, h, low = calculate_max_drawdown(DataFrame())
|
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame())
|
||||||
|
|
||||||
|
|
||||||
def test_calculate_csum(testdatadir):
|
def test_calculate_csum(testdatadir):
|
||||||
@ -310,13 +312,16 @@ def test_calculate_max_drawdown2():
|
|||||||
# sort by profit and reset index
|
# sort by profit and reset index
|
||||||
df = df.sort_values('profit').reset_index(drop=True)
|
df = df.sort_values('profit').reset_index(drop=True)
|
||||||
df1 = df.copy()
|
df1 = df.copy()
|
||||||
drawdown, h, low = calculate_max_drawdown(df, date_col='open_date', value_col='profit')
|
drawdown, hdate, ldate, hval, lval = calculate_max_drawdown(
|
||||||
|
df, date_col='open_date', value_col='profit')
|
||||||
# Ensure df has not been altered.
|
# Ensure df has not been altered.
|
||||||
assert df.equals(df1)
|
assert df.equals(df1)
|
||||||
|
|
||||||
assert isinstance(drawdown, float)
|
assert isinstance(drawdown, float)
|
||||||
# High must be before low
|
# High must be before low
|
||||||
assert h < low
|
assert hdate < ldate
|
||||||
|
# High value must be higher than low value
|
||||||
|
assert hval > lval
|
||||||
assert drawdown == 0.091755
|
assert drawdown == 0.091755
|
||||||
|
|
||||||
df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date'])
|
df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date'])
|
||||||
|
Loading…
Reference in New Issue
Block a user