Merge branch 'develop' into feat/short

This commit is contained in:
Sam Germain 2022-01-04 22:47:33 -06:00
commit 501f473164
26 changed files with 179 additions and 127 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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}')" $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.7') { 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') { 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') { 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') { 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 -r requirements-dev.txt
pip install -e . pip install -e .

View File

@ -18,6 +18,7 @@
"sell_profit_only": false, "sell_profit_only": false,
"sell_profit_offset": 0.0, "sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": false, "ignore_roi_if_buy_signal": false,
"ignore_buying_expired_candle_after": 300,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -15,8 +15,8 @@ This command line option was deprecated in 2019.7-dev (develop branch) and remov
### The **--dynamic-whitelist** command line option ### The **--dynamic-whitelist** command line option
This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) and in freqtrade 2019.7.
and in freqtrade 2019.7. Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead.
### the `--live` command line option ### the `--live` command line option

View File

@ -283,6 +283,8 @@ The `plot-profit` subcommand shows an interactive graph with three plots:
* The summarized profit made by backtesting. * The summarized profit made by backtesting.
Note that this is not the real-world profit, but more of an estimate. Note that this is not the real-world profit, but more of an estimate.
* Profit for each individual pair. * 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. 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 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: Possible options for the `freqtrade plot-profit` subcommand:
``` ```

View File

@ -1,4 +1,4 @@
mkdocs==1.2.3 mkdocs==1.2.3
mkdocs-material==8.1.3 mkdocs-material==8.1.4
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2
pymdown-extensions==9.1 pymdown-extensions==9.1

View File

@ -222,9 +222,9 @@ should be rewritten to
```python ```python
frames = [dataframe] frames = [dataframe]
for val in self.buy_ema_short.range: for val in self.buy_ema_short.range:
frames.append({ frames.append(DataFrame({
f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val) f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val)
}) }))
# Append columns to existing dataframe # Append columns to existing dataframe
merged_frame = pd.concat(frames, axis=1) merged_frame = pd.concat(frames, axis=1)

View File

@ -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). 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_Lib0.4.22cp38cp38win_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. 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. Other versions must be downloaded from the above link.

View File

@ -364,6 +364,36 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
return df 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', 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, float, float]: ) -> 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: if len(trades) == 0:
raise ValueError("Trade dataframe empty.") raise ValueError("Trade dataframe empty.")
profit_results = trades.sort_values(date_col).reset_index(drop=True) profit_results = trades.sort_values(date_col).reset_index(drop=True)
max_drawdown_df = pd.DataFrame() max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col)
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']
idxmin = max_drawdown_df['drawdown'].idxmin() idxmin = max_drawdown_df['drawdown'].idxmin()
if idxmin == 0: if idxmin == 0:

View File

@ -12,7 +12,7 @@ class BTProgress:
def init_step(self, action: BacktestState, max_steps: float): def init_step(self, action: BacktestState, max_steps: float):
self._action = action self._action = action
self._max_steps = max_steps self._max_steps = max_steps
self._proress = 0 self._progress = 0
def set_new_value(self, new_value: float): def set_new_value(self, new_value: float):
self._progress = new_value self._progress = new_value

View File

@ -299,8 +299,7 @@ class HyperoptTools():
f"Objective: {results['loss']:.5f}") f"Objective: {results['loss']:.5f}")
@staticmethod @staticmethod
def prepare_trials_columns(trials: pd.DataFrame, legacy_mode: bool, def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataFrame:
has_drawdown: bool) -> pd.DataFrame:
trials['Best'] = '' trials['Best'] = ''
if 'results_metrics.winsdrawslosses' not in trials.columns: 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_abs'] = None
trials['results_metrics.max_drawdown'] = None trials['results_metrics.max_drawdown'] = None
if not legacy_mode: # New mode, using backtest result for metrics
# New mode, using backtest result for metrics trials['results_metrics.winsdrawslosses'] = trials.apply(
trials['results_metrics.winsdrawslosses'] = trials.apply( lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} "
lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " f"{x['results_metrics.losses']:>4}", axis=1)
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']]
else: trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades',
# Legacy mode 'results_metrics.winsdrawslosses',
trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', 'results_metrics.profit_mean', 'results_metrics.profit_total_abs',
'results_metrics.winsdrawslosses', 'results_metrics.avg_profit', 'results_metrics.profit_total', 'results_metrics.holding_avg',
'results_metrics.total_profit', 'results_metrics.profit', 'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs',
'results_metrics.duration', 'results_metrics.max_drawdown', 'loss', 'is_initial_point', 'is_best']]
'results_metrics.max_drawdown_abs', 'loss', 'is_initial_point',
'is_best']]
trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
'Total profit', 'Profit', 'Avg duration', 'Max Drawdown', 'Total profit', 'Profit', 'Avg duration', 'Max Drawdown',
@ -351,10 +341,9 @@ class HyperoptTools():
tabulate.PRESERVE_WHITESPACE = True tabulate.PRESERVE_WHITESPACE = True
trials = json_normalize(results, max_level=1) 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 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['is_profit'] = False
trials.loc[trials['is_initial_point'], 'Best'] = '* ' 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['is_initial_point'] & trials['is_best'], 'Best'] = '* Best'
trials.loc[trials['Total profit'] > 0, 'is_profit'] = True trials.loc[trials['Total profit'] > 0, 'is_profit'] = True
trials['Trades'] = trials['Trades'].astype(str) 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( trials['Epoch'] = trials['Epoch'].apply(
lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)
) )
trials['Avg profit'] = trials['Avg profit'].apply( 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( trials['Avg duration'] = trials['Avg duration'].apply(
lambda x: f'{x:,.1f} m'.rjust(7, ' ') if isinstance(x, float) else f"{x}" 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( trials['Max Drawdown'] = trials.apply(
lambda x: '{} {}'.format( lambda x: '{} {}'.format(
round_coin_value(x['max_drawdown_abs'], stake_currency), 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)) ).rjust(25 + len(stake_currency))
if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)), if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)),
axis=1 axis=1
@ -396,7 +385,7 @@ class HyperoptTools():
trials['Profit'] = trials.apply( trials['Profit'] = trials.apply(
lambda x: '{} {}'.format( lambda x: '{} {}'.format(
round_coin_value(x['Total profit'], stake_currency), 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)) ).rjust(25+len(stake_currency))
if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)),
axis=1 axis=1

View File

@ -5,7 +5,8 @@ from typing import Any, Dict, List
import pandas as pd import pandas as pd
from freqtrade.configuration import TimeRange 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) create_cum_profit, extract_trades_of_period, load_trades)
from freqtrade.data.converter import trim_dataframe from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider 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 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: def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
""" """
Add trades to "fig" Add trades to "fig"
@ -483,20 +526,30 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
name='Avg close price', name='Avg close price',
) )
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, fig = make_subplots(rows=5, cols=1, shared_xaxes=True,
row_width=[1, 1, 1], row_heights=[1, 1, 1, 0.5, 1],
vertical_spacing=0.05, 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'].update(title="Freqtrade Profit plot")
fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')
fig['layout']['yaxis3'].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['layout']['xaxis']['rangeslider'].update(visible=False)
fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"])
fig.add_trace(avgclose, 1, 1) fig.add_trace(avgclose, 1, 1)
fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe) 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: for pair in pairs:
profit_col = f'cum_profit_{pair}' profit_col = f'cum_profit_{pair}'

View File

@ -20,10 +20,10 @@ time-machine==2.5.0
nbconvert==6.3.0 nbconvert==6.3.0
# mypy types # mypy types
types-cachetools==4.2.6 types-cachetools==4.2.7
types-filelock==3.2.1 types-filelock==3.2.1
types-requests==2.26.2 types-requests==2.26.3
types-tabulate==0.8.3 types-tabulate==0.8.4
# Extensions to datetime library # Extensions to datetime library
types-python-dateutil==2.8.4 types-python-dateutil==2.8.4

View File

@ -7,5 +7,4 @@ scikit-learn==1.0.2
scikit-optimize==0.9.0 scikit-optimize==0.9.0
filelock==3.4.2 filelock==3.4.2
joblib==1.1.0 joblib==1.1.0
psutil==5.8.0
progressbar2==3.55.0 progressbar2==3.55.0

View File

@ -3,7 +3,7 @@ numpy==1.22.0; python_version > '3.7'
pandas==1.3.5 pandas==1.3.5
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==1.65.25 ccxt==1.66.20
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==36.0.1 cryptography==36.0.1
aiohttp==3.8.1 aiohttp==3.8.1
@ -13,8 +13,8 @@ arrow==1.2.1
cachetools==4.2.2 cachetools==4.2.2
requests==2.26.0 requests==2.26.0
urllib3==1.26.7 urllib3==1.26.7
jsonschema==4.3.2 jsonschema==4.3.3
TA-Lib==0.4.22 TA-Lib==0.4.23
technical==1.3.0 technical==1.3.0
tabulate==0.8.9 tabulate==0.8.9
pycoingecko==2.2.0 pycoingecko==2.2.0
@ -36,7 +36,7 @@ fastapi==0.70.1
uvicorn==0.16.0 uvicorn==0.16.0
pyjwt==2.3.0 pyjwt==2.3.0
aiofiles==0.8.0 aiofiles==0.8.0
psutil==5.8.0 psutil==5.9.0
# Support for colorized terminal output # Support for colorized terminal output
colorama==0.4.4 colorama==0.4.4

View File

@ -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, 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_market_change, calculate_max_drawdown,
combine_dataframes_with_mean, create_cum_profit, calculate_underwater, combine_dataframes_with_mean,
extract_trades_of_period, get_latest_backtest_filename, create_cum_profit, extract_trades_of_period,
get_latest_hyperopt_file, load_backtest_data, load_trades, get_latest_backtest_filename, get_latest_hyperopt_file,
load_trades_from_db) load_backtest_data, load_trades, load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history from freqtrade.data.history import load_data, load_pair_history
from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT from tests.conftest_trades import MOCK_TRADE_COUNT
@ -292,9 +292,16 @@ def test_calculate_max_drawdown(testdatadir):
assert isinstance(lval, float) assert isinstance(lval, float)
assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC') assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC')
assert lowdate == Timestamp('2018-01-30 04:45: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.'): with pytest.raises(ValueError, match='Trade dataframe empty.'):
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame()) 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): def test_calculate_csum(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_test.json"

View File

@ -1,5 +1,5 @@
# pragma pylint: disable=missing-docstring,W0212,C0103 # pragma pylint: disable=missing-docstring,W0212,C0103
from datetime import datetime from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from unittest.mock import ANY, MagicMock 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) 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: def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf) 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( hyperopt.print_results(
{ {
'loss': 1, 'loss': 1,
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
},
'total_profit': 0, 'total_profit': 0,
'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'current_epoch': 2, # This starts from 1 (in a human-friendly manner)
'is_initial_point': False, 'is_initial_point': False,
@ -238,7 +254,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
) )
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert all(x in out 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: 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=[{ MagicMock(return_value=[{
'loss': 1, 'results_explanation': 'foo result', 'loss': 1, 'results_explanation': 'foo result',
'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0},
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
},
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -530,14 +539,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
'roi': {}, 'stoploss': {'stoploss': None}, 'roi': {}, 'stoploss': {'stoploss': None},
'trailing': {'trailing_stop': None} 'trailing': {'trailing_stop': None}
}, },
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -586,14 +588,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
'sell': {'sell-mfi-value': None}, 'sell': {'sell-mfi-value': None},
'roi': {}, 'stoploss': {'stoploss': None} 'roi': {}, 'stoploss': {'stoploss': None}
}, },
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -631,14 +626,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
MagicMock(return_value=[{ MagicMock(return_value=[{
'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'loss': 1, 'results_explanation': 'foo result', 'params': {},
'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}, 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}},
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) 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', 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{ MagicMock(return_value=[{
'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0}, 'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0},
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -758,14 +739,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{ MagicMock(return_value=[{
'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'loss': 1, 'results_explanation': 'foo result', 'params': {},
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -807,14 +781,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{ MagicMock(return_value=[{
'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'loss': 1, 'results_explanation': 'foo result', 'params': {},
'results_metrics': 'results_metrics': generate_result_metrics(),
{
'trade_count': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 1.0,
'duration': 20.0
}
}]) }])
) )
patch_exchange(mocker) patch_exchange(mocker)

View File

@ -336,15 +336,20 @@ def test_generate_profit_graph(testdatadir):
assert fig.layout.yaxis3.title.text == "Profit BTC" assert fig.layout.yaxis3.title.text == "Profit BTC"
figure = fig.layout.figure 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") avgclose = find_trace_in_fig_data(figure.data, "Avg close price")
assert isinstance(avgclose, go.Scatter) assert isinstance(avgclose, go.Scatter)
profit = find_trace_in_fig_data(figure.data, "Profit") profit = find_trace_in_fig_data(figure.data, "Profit")
assert isinstance(profit, go.Scatter) assert isinstance(profit, go.Scatter)
profit = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%")
assert isinstance(profit, go.Scatter) 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: for pair in pairs:
profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")