Merge branch 'develop' into feat/short
This commit is contained in:
commit
501f473164
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.23-cp310-cp310-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.23-cp310-cp310-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.23-cp37-cp37m-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.23-cp38-cp38-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.23-cp38-cp38-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.23-cp39-cp39-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.23-cp39-cp39-win_amd64.whl
Normal file
Binary file not shown.
@ -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 .
|
||||||
|
@ -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 |
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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_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.
|
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.
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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}'
|
||||||
|
@ -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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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}")
|
||||||
|
Loading…
Reference in New Issue
Block a user