diff --git a/build_helpers/TA_Lib-0.4.22-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.22-cp310-cp310-win_amd64.whl deleted file mode 100644 index d3477abd1..000000000 Binary files a/build_helpers/TA_Lib-0.4.22-cp310-cp310-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.22-cp37-cp37m-win_amd64.whl b/build_helpers/TA_Lib-0.4.22-cp37-cp37m-win_amd64.whl deleted file mode 100644 index b8dd7f396..000000000 Binary files a/build_helpers/TA_Lib-0.4.22-cp37-cp37m-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.22-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.22-cp38-cp38-win_amd64.whl deleted file mode 100644 index 0ac516db3..000000000 Binary files a/build_helpers/TA_Lib-0.4.22-cp38-cp38-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.22-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.22-cp39-cp39-win_amd64.whl deleted file mode 100644 index c69b68e16..000000000 Binary files a/build_helpers/TA_Lib-0.4.22-cp39-cp39-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.23-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.23-cp310-cp310-win_amd64.whl new file mode 100644 index 000000000..35d35d956 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.23-cp310-cp310-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl b/build_helpers/TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl new file mode 100644 index 000000000..b06b09a7d Binary files /dev/null and b/build_helpers/TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.23-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.23-cp38-cp38-win_amd64.whl new file mode 100644 index 000000000..b51557cac Binary files /dev/null and b/build_helpers/TA_Lib-0.4.23-cp38-cp38-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.23-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.23-cp39-cp39-win_amd64.whl new file mode 100644 index 000000000..eb5453f34 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.23-cp39-cp39-win_amd64.whl differ diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index f04869780..c982b3a05 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -6,16 +6,16 @@ python -m pip install --upgrade pip wheel $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" if ($pyv -eq '3.7') { - pip install build_helpers\TA_Lib-0.4.22-cp37-cp37m-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl } if ($pyv -eq '3.8') { - pip install build_helpers\TA_Lib-0.4.22-cp38-cp38-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.23-cp38-cp38-win_amd64.whl } if ($pyv -eq '3.9') { - pip install build_helpers\TA_Lib-0.4.22-cp39-cp39-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.23-cp39-cp39-win_amd64.whl } if ($pyv -eq '3.10') { - pip install build_helpers\TA_Lib-0.4.22-cp310-cp310-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.23-cp310-cp310-win_amd64.whl } pip install -r requirements-dev.txt pip install -e . diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 228a08a02..81a034a21 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -18,6 +18,7 @@ "sell_profit_only": false, "sell_profit_offset": 0.0, "ignore_roi_if_buy_signal": false, + "ignore_buying_expired_candle_after": 300, "minimal_roi": { "40": 0.0, "30": 0.01, diff --git a/docs/assets/plot-profit.png b/docs/assets/plot-profit.png index 88d69a2d4..e9fe6c341 100644 Binary files a/docs/assets/plot-profit.png and b/docs/assets/plot-profit.png differ diff --git a/docs/deprecated.md b/docs/deprecated.md index be1d51837..81eed3bcf 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -15,8 +15,8 @@ This command line option was deprecated in 2019.7-dev (develop branch) and remov ### The **--dynamic-whitelist** command line option -This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) -and in freqtrade 2019.7. +This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) and in freqtrade 2019.7. +Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead. ### the `--live` command line option diff --git a/docs/plotting.md b/docs/plotting.md index b2d7654f6..315dbc236 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -283,6 +283,8 @@ The `plot-profit` subcommand shows an interactive graph with three plots: * The summarized profit made by backtesting. Note that this is not the real-world profit, but more of an estimate. * Profit for each individual pair. +* Parallelism of trades. +* Underwater (Periods of drawdown). The first graph is good to get a grip of how the overall market progresses. @@ -292,6 +294,8 @@ This graph will also highlight the start (and end) of the Max drawdown period. The third graph can be useful to spot outliers, events in pairs that cause profit spikes. +The forth graph can help you analyze trade parallelism, showing how often max_open_trades have been maxed out. + Possible options for the `freqtrade plot-profit` subcommand: ``` diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 715f1793d..0dccfa17a 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==8.1.3 +mkdocs-material==8.1.4 mdx_truly_sane_lists==1.2 pymdown-extensions==9.1 diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 22dc57172..ec91c91a7 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -222,9 +222,9 @@ should be rewritten to ```python frames = [dataframe] for val in self.buy_ema_short.range: - frames.append({ + frames.append(DataFrame({ f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val) - }) + })) # Append columns to existing dataframe merged_frame = pd.concat(frames, axis=1) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index f4be06db3..6f51dbf8f 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -23,7 +23,7 @@ git clone https://github.com/freqtrade/freqtrade.git Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows). -As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib‑0.4.22‑cp38‑cp38‑win_amd64.whl` (make sure to use the version matching your python version). +As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.23-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). Freqtrade provides these dependencies for the latest 3 Python versions (3.7, 3.8, 3.9 and 3.10) and for 64bit Windows. Other versions must be downloaded from the above link. diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index ec98917ed..11f21bd55 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -364,6 +364,36 @@ 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: + 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] + return max_drawdown_df + + +def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_ratio' + ): + """ + Calculate max drawdown and the corresponding close dates + :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 value_col: Column in DataFrame to use for values (defaults to 'profit_ratio') + :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. + """ + 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) + + return max_drawdown_df + + def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', value_col: str = 'profit_ratio' ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]: @@ -379,10 +409,7 @@ 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 = 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 = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) idxmin = max_drawdown_df['drawdown'].idxmin() if idxmin == 0: diff --git a/freqtrade/optimize/bt_progress.py b/freqtrade/optimize/bt_progress.py index d295956c7..c3b105915 100644 --- a/freqtrade/optimize/bt_progress.py +++ b/freqtrade/optimize/bt_progress.py @@ -12,7 +12,7 @@ class BTProgress: def init_step(self, action: BacktestState, max_steps: float): self._action = action self._max_steps = max_steps - self._proress = 0 + self._progress = 0 def set_new_value(self, new_value: float): self._progress = new_value diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 1204320da..ca4b43e11 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -299,8 +299,7 @@ class HyperoptTools(): f"Objective: {results['loss']:.5f}") @staticmethod - def prepare_trials_columns(trials: pd.DataFrame, legacy_mode: bool, - has_drawdown: bool) -> pd.DataFrame: + def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataFrame: trials['Best'] = '' if 'results_metrics.winsdrawslosses' not in trials.columns: @@ -312,26 +311,17 @@ class HyperoptTools(): trials['results_metrics.max_drawdown_abs'] = None trials['results_metrics.max_drawdown'] = None - if not legacy_mode: - # New mode, using backtest result for metrics - trials['results_metrics.winsdrawslosses'] = trials.apply( - lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " - f"{x['results_metrics.losses']:>4}", axis=1) - trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', - 'results_metrics.winsdrawslosses', - 'results_metrics.profit_mean', 'results_metrics.profit_total_abs', - 'results_metrics.profit_total', 'results_metrics.holding_avg', - 'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs', - 'loss', 'is_initial_point', 'is_best']] + # New mode, using backtest result for metrics + trials['results_metrics.winsdrawslosses'] = trials.apply( + lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " + f"{x['results_metrics.losses']:>4}", axis=1) - else: - # Legacy mode - trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', - 'results_metrics.winsdrawslosses', 'results_metrics.avg_profit', - 'results_metrics.total_profit', 'results_metrics.profit', - 'results_metrics.duration', 'results_metrics.max_drawdown', - 'results_metrics.max_drawdown_abs', 'loss', 'is_initial_point', - 'is_best']] + trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', + 'results_metrics.winsdrawslosses', + 'results_metrics.profit_mean', 'results_metrics.profit_total_abs', + 'results_metrics.profit_total', 'results_metrics.holding_avg', + 'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs', + 'loss', 'is_initial_point', 'is_best']] trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', 'Total profit', 'Profit', 'Avg duration', 'Max Drawdown', @@ -351,10 +341,9 @@ class HyperoptTools(): tabulate.PRESERVE_WHITESPACE = True trials = json_normalize(results, max_level=1) - legacy_mode = 'results_metrics.total_trades' not in trials has_drawdown = 'results_metrics.max_drawdown_abs' in trials.columns - trials = HyperoptTools.prepare_trials_columns(trials, legacy_mode, has_drawdown) + trials = HyperoptTools.prepare_trials_columns(trials, has_drawdown) trials['is_profit'] = False trials.loc[trials['is_initial_point'], 'Best'] = '* ' @@ -362,12 +351,12 @@ class HyperoptTools(): trials.loc[trials['is_initial_point'] & trials['is_best'], 'Best'] = '* Best' trials.loc[trials['Total profit'] > 0, 'is_profit'] = True trials['Trades'] = trials['Trades'].astype(str) - perc_multi = 1 if legacy_mode else 100 + # perc_multi = 1 if legacy_mode else 100 trials['Epoch'] = trials['Epoch'].apply( lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: f'{x * perc_multi:,.2f}%'.rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: f'{x:,.2%}'.rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Avg duration'] = trials['Avg duration'].apply( lambda x: f'{x:,.1f} m'.rjust(7, ' ') if isinstance(x, float) else f"{x}" @@ -383,7 +372,7 @@ class HyperoptTools(): trials['Max Drawdown'] = trials.apply( lambda x: '{} {}'.format( round_coin_value(x['max_drawdown_abs'], stake_currency), - '({:,.2f}%)'.format(x['Max Drawdown'] * perc_multi).rjust(10, ' ') + f"({x['Max Drawdown']:,.2%})".rjust(10, ' ') ).rjust(25 + len(stake_currency)) if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)), axis=1 @@ -396,7 +385,7 @@ class HyperoptTools(): trials['Profit'] = trials.apply( lambda x: '{} {}'.format( round_coin_value(x['Total profit'], stake_currency), - '({:,.2f}%)'.format(x['Profit'] * perc_multi).rjust(10, ' ') + f"({x['Profit']:,.2%})".rjust(10, ' ') ).rjust(25+len(stake_currency)) if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), axis=1 diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 16bab9745..5aaa504f0 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -5,7 +5,8 @@ from typing import Any, Dict, List import pandas as pd from freqtrade.configuration import TimeRange -from freqtrade.data.btanalysis import (calculate_max_drawdown, combine_dataframes_with_mean, +from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown, + calculate_underwater, combine_dataframes_with_mean, create_cum_profit, extract_trades_of_period, load_trades) from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider @@ -185,6 +186,48 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, return fig +def add_underwater(fig, row, trades: pd.DataFrame) -> make_subplots: + """ + Add underwater plot + """ + try: + underwater = calculate_underwater(trades, value_col="profit_abs") + + underwater = go.Scatter( + x=underwater['date'], + y=underwater['drawdown'], + name="Underwater Plot", + fill='tozeroy', + fillcolor='#cc362b', + line={'color': '#cc362b'}, + ) + fig.add_trace(underwater, row, 1) + except ValueError: + logger.warning("No trades found - not plotting underwater plot") + return fig + + +def add_parallelism(fig, row, trades: pd.DataFrame, timeframe: str) -> make_subplots: + """ + Add Chart showing trade parallelism + """ + try: + result = analyze_trade_parallelism(trades, timeframe) + + drawdown = go.Scatter( + x=result.index, + y=result['open_trades'], + name="Parallel trades", + fill='tozeroy', + fillcolor='#242222', + line={'color': '#242222'}, + ) + fig.add_trace(drawdown, row, 1) + except ValueError: + logger.warning("No trades found - not plotting Parallelism.") + return fig + + def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: """ Add trades to "fig" @@ -483,20 +526,30 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], name='Avg close price', ) - fig = make_subplots(rows=3, cols=1, shared_xaxes=True, - row_width=[1, 1, 1], + fig = make_subplots(rows=5, cols=1, shared_xaxes=True, + row_heights=[1, 1, 1, 0.5, 1], vertical_spacing=0.05, - subplot_titles=["AVG Close Price", "Combined Profit", "Profit per pair"]) + subplot_titles=[ + "AVG Close Price", + "Combined Profit", + "Profit per pair", + "Parallelism", + "Underwater", + ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis3'].update(title=f'Profit {stake_currency}') + fig['layout']['yaxis4'].update(title='Trade count') + fig['layout']['yaxis5'].update(title='Underwater Plot') fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) fig.add_trace(avgclose, 1, 1) fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe) + fig = add_parallelism(fig, 4, trades, timeframe) + fig = add_underwater(fig, 5, trades) for pair in pairs: profit_col = f'cum_profit_{pair}' diff --git a/requirements-dev.txt b/requirements-dev.txt index 82cb6b7fc..b2fad4e03 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,10 +20,10 @@ time-machine==2.5.0 nbconvert==6.3.0 # mypy types -types-cachetools==4.2.6 +types-cachetools==4.2.7 types-filelock==3.2.1 -types-requests==2.26.2 -types-tabulate==0.8.3 +types-requests==2.26.3 +types-tabulate==0.8.4 # Extensions to datetime library types-python-dateutil==2.8.4 \ No newline at end of file diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index bd234dd73..b785e73e9 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -7,5 +7,4 @@ scikit-learn==1.0.2 scikit-optimize==0.9.0 filelock==3.4.2 joblib==1.1.0 -psutil==5.8.0 progressbar2==3.55.0 diff --git a/requirements.txt b/requirements.txt index 945418cd5..188b18560 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ numpy==1.22.0; python_version > '3.7' pandas==1.3.5 pandas-ta==0.3.14b -ccxt==1.65.25 +ccxt==1.66.20 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 @@ -13,8 +13,8 @@ arrow==1.2.1 cachetools==4.2.2 requests==2.26.0 urllib3==1.26.7 -jsonschema==4.3.2 -TA-Lib==0.4.22 +jsonschema==4.3.3 +TA-Lib==0.4.23 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 @@ -36,7 +36,7 @@ fastapi==0.70.1 uvicorn==0.16.0 pyjwt==2.3.0 aiofiles==0.8.0 -psutil==5.8.0 +psutil==5.9.0 # Support for colorized terminal output colorama==0.4.4 diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 5f34438f3..a21cef021 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -11,10 +11,10 @@ from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD, analyze_trade_parallelism, calculate_csum, calculate_market_change, calculate_max_drawdown, - combine_dataframes_with_mean, create_cum_profit, - extract_trades_of_period, get_latest_backtest_filename, - get_latest_hyperopt_file, load_backtest_data, load_trades, - load_trades_from_db) + calculate_underwater, combine_dataframes_with_mean, + create_cum_profit, extract_trades_of_period, + get_latest_backtest_filename, get_latest_hyperopt_file, + load_backtest_data, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades from tests.conftest_trades import MOCK_TRADE_COUNT @@ -292,9 +292,16 @@ def test_calculate_max_drawdown(testdatadir): 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') + + underwater = calculate_underwater(bt_data) + assert isinstance(underwater, DataFrame) + with pytest.raises(ValueError, match='Trade dataframe empty.'): drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame()) + with pytest.raises(ValueError, match='Trade dataframe empty.'): + calculate_underwater(DataFrame()) + def test_calculate_csum(testdatadir): filename = testdatadir / "backtest-result_test.json" diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index bece6648d..25a6d249e 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -1,5 +1,5 @@ # pragma pylint: disable=missing-docstring,W0212,C0103 -from datetime import datetime +from datetime import datetime, timedelta from pathlib import Path from unittest.mock import ANY, MagicMock @@ -22,6 +22,29 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re patched_configuration_load_config_file) +def generate_result_metrics(): + return { + 'trade_count': 1, + 'total_trades': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 0.01, + 'duration': 20.0, + 'wins': 1, + 'draws': 0, + 'losses': 0, + 'profit_mean': 0.01, + 'profit_total_abs': 0.001, + 'profit_total': 0.01, + 'holding_avg': timedelta(minutes=20), + 'max_drawdown': 0.001, + 'max_drawdown_abs': 0.001, + 'loss': 0.001, + 'is_initial_point': 0.001, + 'is_best': 1, + } + + def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) @@ -222,14 +245,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.print_results( { 'loss': 1, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - }, + 'results_metrics': generate_result_metrics(), 'total_profit': 0, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'is_initial_point': False, @@ -238,7 +254,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: ) out, err = capsys.readouterr() assert all(x in out - for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "20.0 m"]) + for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"]) def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: @@ -295,14 +311,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - }, + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -530,14 +539,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: 'roi': {}, 'stoploss': {'stoploss': None}, 'trailing': {'trailing_stop': None} }, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -586,14 +588,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: 'sell': {'sell-mfi-value': None}, 'roi': {}, 'stoploss': {'stoploss': None} }, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -631,14 +626,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -678,14 +666,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -758,14 +739,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -807,14 +781,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index bcc779373..f0a4ab388 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -336,15 +336,20 @@ def test_generate_profit_graph(testdatadir): assert fig.layout.yaxis3.title.text == "Profit BTC" figure = fig.layout.figure - assert len(figure.data) == 5 + assert len(figure.data) == 7 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scatter) profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scatter) - profit = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") - assert isinstance(profit, go.Scatter) + drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") + assert isinstance(drawdown, go.Scatter) + parallel = find_trace_in_fig_data(figure.data, "Parallel trades") + assert isinstance(parallel, go.Scatter) + + underwater = find_trace_in_fig_data(figure.data, "Underwater Plot") + assert isinstance(underwater, go.Scatter) for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")