This commit is contained in:
Guitheg 2021-12-13 11:03:32 +01:00
parent d08b0918ad
commit 44843910c6
3 changed files with 32 additions and 31 deletions

View File

@ -390,8 +390,9 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
low_val = max_drawdown_df.loc[idxmin, 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative']
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val
# TODO : is supposed to work only with long positions # TODO : is supposed to work only with long positions
def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float : def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float:
""" """
Calculate Trades MDD (Max DrawDown) : Calculate Trades MDD (Max DrawDown) :
Give the max drawdown given each trades and the history candles. Give the max drawdown given each trades and the history candles.
@ -399,7 +400,6 @@ def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float :
Args: Args:
:param data: (dict) dictionnary of candle dataframe per pair used to calculate the mdd. :param data: (dict) dictionnary of candle dataframe per pair used to calculate the mdd.
:param trades: (pd.DataFrame) trades used to find the intervals dates. :param trades: (pd.DataFrame) trades used to find the intervals dates.
Returns: Returns:
:return: (float) Give the maximum drawdown among each trades. :return: (float) Give the maximum drawdown among each trades.
:raise: (ValueError) if trade-dataframe was found empty. :raise: (ValueError) if trade-dataframe was found empty.
@ -407,16 +407,14 @@ def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float :
""" """
if len(trades) == 0: if len(trades) == 0:
raise ValueError("Trade dataframe empty") raise ValueError("Trade dataframe empty")
trades_mdd_pair_list = [] trades_mdd_pair_list = []
for pair, df in data.items(): for pair, df in data.items():
if isinstance(df, pd.DataFrame): if isinstance(df, pd.DataFrame):
# Gather the opening and closing trade dates into one Dates DataFrame # Gather the opening and closing trade dates into one Dates DataFrame
open_close_trades = trades.loc[trades['pair']==pair][["open_date","close_date"]] open_close_trades = trades.loc[trades['pair'] == pair][["open_date", "close_date"]]
open_close_trades = pd.concat( open_close_trades = pd.concat(
[open_close_trades.rename(columns={'open_date':'date'})[['date']], [open_close_trades.rename(columns={'open_date': 'date'})[['date']],
open_close_trades.rename(columns={'close_date':'date'})[['date']]] open_close_trades.rename(columns={'close_date': 'date'})[['date']]]
).sort_values(by='date') ).sort_values(by='date')
# Mark the dates and join it to the current candle dataframe. # Mark the dates and join it to the current candle dataframe.
@ -430,18 +428,18 @@ def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float :
# dataframe. # dataframe.
# This allow to classify trades using the cumsum and split by classes # This allow to classify trades using the cumsum and split by classes
# with groupby in order to process a cummax on each trades independantly. # with groupby in order to process a cummax on each trades independantly.
open_trades = trades.loc[trades['pair']==pair][["open_date"]] open_trades = trades.loc[trades['pair'] == pair][["open_date"]]
open_trades = open_trades.rename(columns={'open_date':'date'}) open_trades = open_trades.rename(columns={'open_date': 'date'})
open_trades['open_mark'] = 1 open_trades['open_mark'] = 1
data_join = data_join.join(open_trades.set_index('date')) data_join = data_join.join(open_trades.set_index('date'))
del open_trades del open_trades
# Set all unmarked date to 0 # Set all unmarked date to 0
data_join[["open_close_mark",'open_mark']] = data_join[ data_join[["open_close_mark", 'open_mark']] = data_join[
["open_close_mark",'open_mark']].fillna(0).astype(int) ["open_close_mark", 'open_mark']].fillna(0).astype(int)
# Mark with one all dates between an opening date trades and a closing date trades. # Mark with one all dates between an opening date trades and a closing date trades.
data_join['is_in_trade'] = data_join.open_close_mark.cumsum()&1 # &1 <=> %2 data_join['is_in_trade'] = data_join.open_close_mark.cumsum() & 1 # &1 <=> %2
data_join.loc[data_join['open_close_mark'] == 1, 'is_in_trade'] = 1 data_join.loc[data_join['open_close_mark'] == 1, 'is_in_trade'] = 1
# Perform a cummax in each trades independtly # Perform a cummax in each trades independtly
@ -452,10 +450,10 @@ def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float :
data_join.loc[data_join['is_in_trade'] == 0, 'close_cummax'] = 0 data_join.loc[data_join['is_in_trade'] == 0, 'close_cummax'] = 0
# Compute the drawdown at each time of each trades # Compute the drawdown at each time of each trades
data_join = data_join.rename(columns={'open_mark':'drawdown'}) data_join = data_join.rename(columns={'open_mark': 'drawdown'})
data_join.loc[data_join['is_in_trade'] == 1, 'drawdown'] = \ data_join.loc[data_join['is_in_trade'] == 1, 'drawdown'] = ((
(data_join['close_cummax'] - data_join['close']) \ data_join['close_cummax'] - data_join['close'])
/ data_join['close_cummax'] / data_join['close_cummax'])
mdd_pair = data_join['drawdown'].max() mdd_pair = data_join['drawdown'].max()
trades_mdd_pair_list.append(mdd_pair) trades_mdd_pair_list.append(mdd_pair)
@ -466,6 +464,7 @@ def calculate_trades_mdd(data: dict, trades: pd.DataFrame) -> float :
trades_mdd_pair_list = np.array(trades_mdd_pair_list) trades_mdd_pair_list = np.array(trades_mdd_pair_list)
return trades_mdd_pair_list.max() return trades_mdd_pair_list.max()
def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]:
""" """
Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane

View File

@ -468,7 +468,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown( drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
results, value_col='profit_abs') results, value_col='profit_abs')
strat_stats.update({ strat_stats.update({
'trades_mdd' : trades_mdd, 'trades_mdd': trades_mdd,
'max_drawdown': max_drawdown, 'max_drawdown': max_drawdown,
'max_drawdown_abs': drawdown_abs, 'max_drawdown_abs': drawdown_abs,
'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT), 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT),

View File

@ -10,7 +10,8 @@ from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD, from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD,
analyze_trade_parallelism, calculate_csum, analyze_trade_parallelism, calculate_csum,
calculate_market_change, calculate_max_drawdown, calculate_trades_mdd, calculate_market_change, calculate_max_drawdown,
calculate_trades_mdd,
combine_dataframes_with_mean, create_cum_profit, combine_dataframes_with_mean, create_cum_profit,
extract_trades_of_period, get_latest_backtest_filename, extract_trades_of_period, get_latest_backtest_filename,
get_latest_hyperopt_file, load_backtest_data, load_trades, get_latest_hyperopt_file, load_backtest_data, load_trades,
@ -333,13 +334,14 @@ def test_calculate_max_drawdown2():
with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'): with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'):
calculate_max_drawdown(df, date_col='open_date', value_col='profit') calculate_max_drawdown(df, date_col='open_date', value_col='profit')
def test_calculate_trades_mdd(testdatadir): def test_calculate_trades_mdd(testdatadir):
backtest_file = testdatadir / "backtest-result_test.json" backtest_file = testdatadir / "backtest-result_test.json"
trades = load_backtest_data(backtest_file) trades = load_backtest_data(backtest_file)
pairlist = set(trades["pair"]) pairlist = set(trades["pair"])
with pytest.raises(ValueError, match='All dataframe in candle data are None'): with pytest.raises(ValueError, match='All dataframe in candle data are None'):
calculate_trades_mdd({"BTC/BUSD" : None}, trades) calculate_trades_mdd({"BTC/BUSD": None}, trades)
data = load_data(datadir=testdatadir, pairs=pairlist, timeframe='5m') data = load_data(datadir=testdatadir, pairs=pairlist, timeframe='5m')
trades_mdd = calculate_trades_mdd(data, trades) trades_mdd = calculate_trades_mdd(data, trades)