Merge branch 'develop' into feat/short

This commit is contained in:
Matthias 2022-01-07 10:09:17 +01:00
commit 46809f08fe
27 changed files with 211 additions and 216 deletions

View File

@ -196,7 +196,7 @@ jobs:
uses: rjstone/discord-webhook-notify@v1 uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with: with:
severity: error severity: info
details: Test Succeeded! details: Test Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -312,7 +312,7 @@ A backtesting result will look like that:
| | | | | |
| Min balance | 0.00945123 BTC | | Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC | | Max balance | 0.01846651 BTC |
| Drawdown | 50.63% | | Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC | | Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC | | Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC | | Drawdown low | -0.0002 BTC |
@ -405,7 +405,7 @@ It contains some useful key metrics about performance of your strategy on backte
| | | | | |
| Min balance | 0.00945123 BTC | | Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC | | Max balance | 0.01846651 BTC |
| Drawdown | 50.63% | | Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC | | Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC | | Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC | | Drawdown low | -0.0002 BTC |
@ -432,7 +432,8 @@ It contains some useful key metrics about performance of your strategy on backte
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached. - `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). - `Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as $(Absolute Drawdown) / (DrawdownHigh + startingBalance)$.
- `Drawdown`: Maximum, absolute drawdown experienced. Difference between Drawdown High and Subsequent Low point.
- `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost.
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.

View File

@ -9,21 +9,13 @@ import numpy as np
import pandas as pd import pandas as pd
from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.exceptions import OperationalException
from freqtrade.misc import json_load from freqtrade.misc import json_load
from freqtrade.persistence import LocalTrade, Trade, init_db from freqtrade.persistence import LocalTrade, Trade, init_db
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Old format - maybe remove?
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
# Mid-term format, created by BacktestResult Named Tuple
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
# Newest format # Newest format
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_rate', 'close_rate', 'open_rate', 'close_rate',
@ -170,23 +162,9 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
) )
else: else:
# old format - only with lists. # old format - only with lists.
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD) raise OperationalException(
"Backtest-results with only trades data are no longer supported.")
if not df.empty: if not df.empty:
df['open_date'] = pd.to_datetime(df['open_date'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['close_date'] = pd.to_datetime(df['close_date'],
unit='s',
utc=True,
infer_datetime_format=True
)
# Create compatibility with new format
df['profit_abs'] = df['close_rate'] - df['open_rate']
if not df.empty:
if 'profit_ratio' not in df.columns:
df['profit_ratio'] = df['profit_percent']
df = df.sort_values("open_date").reset_index(drop=True) df = df.sort_values("open_date").reset_index(drop=True)
return df return df
@ -395,15 +373,17 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date',
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_abs', starting_balance: float = 0
) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]: ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]:
""" """
Calculate max drawdown and the corresponding close dates Calculate max drawdown and the corresponding close dates
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio) :param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date') :param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
:param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio') :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs')
:return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown, :param starting_balance: Portfolio starting balance - properly calculate relative drawdown.
high and low time and high and low value. :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown)
with absolute max drawdown, high and low time and high and low value,
and the relative account drawdown
:raise: ValueError if trade-dataframe was found empty. :raise: ValueError if trade-dataframe was found empty.
""" """
if len(trades) == 0: if len(trades) == 0:
@ -419,7 +399,18 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin]
['high_value'].idxmax(), 'cumulative'] ['high_value'].idxmax(), 'cumulative']
low_val = max_drawdown_df.loc[idxmin, 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative']
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val max_drawdown_rel = 0.0
if high_val + starting_balance != 0:
max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance)
return (
abs(min(max_drawdown_df['drawdown'])),
high_date,
low_date,
high_val,
low_val,
max_drawdown_rel
)
def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]:

View File

@ -65,6 +65,8 @@ class Exchange:
"ohlcv_params": {}, "ohlcv_params": {},
"ohlcv_candle_limit": 500, "ohlcv_candle_limit": 500,
"ohlcv_partial_candle": True, "ohlcv_partial_candle": True,
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote"
"trades_pagination": "time", # Possible are "time" or "id" "trades_pagination": "time", # Possible are "time" or "id"
"trades_pagination_arg": "since", "trades_pagination_arg": "since",
"l2_limit_range": None, "l2_limit_range": None,
@ -797,7 +799,8 @@ class Exchange:
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage)) max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
remaining_amount = amount remaining_amount = amount
filled_amount = 0 filled_amount = 0.0
book_entry_price = 0.0
for book_entry in ob[ob_type]: for book_entry in ob[ob_type]:
book_entry_price = book_entry[0] book_entry_price = book_entry[0]
book_entry_coin_volume = book_entry[1] book_entry_coin_volume = book_entry[1]

View File

@ -20,6 +20,7 @@ class Ftx(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
"ohlcv_volume_currency": "quote",
"mark_ohlcv_price": "index", "mark_ohlcv_price": "index",
"mark_ohlcv_timeframe": "1h", "mark_ohlcv_timeframe": "1h",
} }

View File

@ -22,6 +22,7 @@ class Gateio(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"ohlcv_volume_currency": "quote",
} }
_headers = {'X-Gate-Channel-Id': 'freqtrade'} _headers = {'X-Gate-Channel-Id': 'freqtrade'}

View File

@ -15,7 +15,7 @@ class Okex(Exchange):
""" """
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 100, "ohlcv_candle_limit": 300,
"mark_ohlcv_timeframe": "4h", "mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h", "funding_fee_timeframe": "8h",
} }

View File

@ -448,8 +448,10 @@ class Backtesting:
detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX] detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX]
detail_data.loc[:, 'enter_long'] = sell_row[LONG_IDX] detail_data.loc[:, 'enter_long'] = sell_row[LONG_IDX]
detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX] detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX]
detail_data.loc[:, 'enter_tag'] = sell_row[ENTER_TAG_IDX]
detail_data.loc[:, 'exit_tag'] = sell_row[EXIT_TAG_IDX]
headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short'] 'enter_short', 'exit_short', 'enter_tag', 'exit_tag']
for det_row in detail_data[headers].values.tolist(): for det_row in detail_data[headers].values.tolist():
res = self._get_sell_trade_entry_for_candle(trade, det_row) res = self._get_sell_trade_entry_for_candle(trade, det_row)
if res: if res:

View File

@ -76,6 +76,7 @@ class Hyperopt:
self.config = config self.config = config
self.backtesting = Backtesting(self.config) self.backtesting = Backtesting(self.config)
self.pairlist = self.backtesting.pairlists.whitelist
if not self.config.get('hyperopt'): if not self.config.get('hyperopt'):
self.custom_hyperopt = HyperOptAuto(self.config) self.custom_hyperopt = HyperOptAuto(self.config)
@ -332,7 +333,7 @@ class Hyperopt:
params_details = self._get_params_details(params_dict) params_details = self._get_params_details(params_dict)
strat_stats = generate_strategy_stats( strat_stats = generate_strategy_stats(
processed, self.backtesting.strategy.get_strategy_name(), self.pairlist, self.backtesting.strategy.get_strategy_name(),
backtesting_results, min_date, max_date, market_change=0 backtesting_results, min_date, max_date, market_change=0
) )
results_explanation = HyperoptTools.format_results_explanation_string( results_explanation = HyperoptTools.format_results_explanation_string(

View File

@ -47,10 +47,9 @@ class CalmarHyperOptLoss(IHyperOptLoss):
# calculate max drawdown # calculate max drawdown
try: try:
_, _, _, high_val, low_val = calculate_max_drawdown( _, _, _, _, _, max_drawdown = calculate_max_drawdown(
results, value_col="profit_abs" results, value_col="profit_abs"
) )
max_drawdown = (high_val - low_val) / high_val
except ValueError: except ValueError:
max_drawdown = 0 max_drawdown = 0

View File

@ -308,8 +308,7 @@ class HyperoptTools():
if not has_drawdown: if not has_drawdown:
# Ensure compatibility with older versions of hyperopt results # Ensure compatibility with older versions of hyperopt results
trials['results_metrics.max_drawdown_abs'] = None trials['results_metrics.max_drawdown_account'] = None
trials['results_metrics.max_drawdown'] = None
# 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(
@ -320,12 +319,15 @@ class HyperoptTools():
'results_metrics.winsdrawslosses', 'results_metrics.winsdrawslosses',
'results_metrics.profit_mean', 'results_metrics.profit_total_abs', 'results_metrics.profit_mean', 'results_metrics.profit_total_abs',
'results_metrics.profit_total', 'results_metrics.holding_avg', 'results_metrics.profit_total', 'results_metrics.holding_avg',
'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs', 'results_metrics.max_drawdown',
'results_metrics.max_drawdown_account', 'results_metrics.max_drawdown_abs',
'loss', 'is_initial_point', 'is_best']] 'loss', 'is_initial_point', 'is_best']]
trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', trials.columns = [
'Total profit', 'Profit', 'Avg duration', 'Max Drawdown', 'Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best'] 'Total profit', 'Profit', 'Avg duration', 'max_drawdown', 'max_drawdown_account',
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best'
]
return trials return trials
@ -341,9 +343,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)
has_drawdown = 'results_metrics.max_drawdown_abs' in trials.columns has_account_drawdown = 'results_metrics.max_drawdown_account' in trials.columns
trials = HyperoptTools.prepare_trials_columns(trials, has_drawdown) trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown)
trials['is_profit'] = False trials['is_profit'] = False
trials.loc[trials['is_initial_point'], 'Best'] = '* ' trials.loc[trials['is_initial_point'], 'Best'] = '* '
@ -368,19 +370,20 @@ class HyperoptTools():
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
if has_drawdown: trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = 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),
f"({x['Max Drawdown']:,.2%})".rjust(10, ' ') (f"({x['max_drawdown_account']:,.2%})"
if has_account_drawdown
else 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 or x['max_drawdown_account'] != 0.0
else '--'.rjust(25 + len(stake_currency)),
axis=1 axis=1
) )
else:
trials = trials.drop(columns=['Max Drawdown'])
trials = trials.drop(columns=['max_drawdown_abs']) trials = trials.drop(columns=['max_drawdown_abs', 'max_drawdown', 'max_drawdown_account'])
trials['Profit'] = trials.apply( trials['Profit'] = trials.apply(
lambda x: '{} {}'.format( lambda x: '{} {}'.format(

View File

@ -1,4 +1,5 @@
import logging import logging
from copy import deepcopy
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
@ -98,11 +99,11 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column
} }
def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_balance: int, def generate_pair_metrics(pairlist: List[str], stake_currency: str, starting_balance: int,
results: DataFrame, skip_nan: bool = False) -> List[Dict]: results: DataFrame, skip_nan: bool = False) -> List[Dict]:
""" """
Generates and returns a list for the given backtest data and the results dataframe Generates and returns a list for the given backtest data and the results dataframe
:param data: Dict of <pair: dataframe> containing data that was used during backtesting. :param pairlist: Pairlist used
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:param starting_balance: Starting balance :param starting_balance: Starting balance
:param results: Dataframe containing the backtest results :param results: Dataframe containing the backtest results
@ -112,7 +113,7 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_b
tabular_data = [] tabular_data = []
for pair in data: for pair in pairlist:
result = results[results['pair'] == pair] result = results[results['pair'] == pair]
if skip_nan and result['profit_abs'].isnull().all(): if skip_nan and result['profit_abs'].isnull().all():
continue continue
@ -194,29 +195,21 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
return tabular_data return tabular_data
def generate_strategy_comparison(all_results: Dict) -> List[Dict]: def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]:
""" """
Generate summary per strategy Generate summary per strategy
:param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies :param bt_stats: Dict of <Strategyname: DataFrame> containing results for all strategies
:return: List of Dicts containing the metrics per Strategy :return: List of Dicts containing the metrics per Strategy
""" """
tabular_data = [] tabular_data = []
for strategy, results in all_results.items(): for strategy, result in bt_stats.items():
tabular_data.append(_generate_result_line( tabular_data.append(deepcopy(result['results_per_pair'][-1]))
results['results'], results['config']['dry_run_wallet'], strategy) # Update "key" to strategy (results_per_pair has it as "Total").
) tabular_data[-1]['key'] = strategy
try: tabular_data[-1]['max_drawdown_account'] = result['max_drawdown_account']
max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'], tabular_data[-1]['max_drawdown_abs'] = round_coin_value(
value_col='profit_ratio') result['max_drawdown_abs'], result['stake_currency'], False)
max_drawdown_abs, _, _, _, _ = calculate_max_drawdown(results['results'],
value_col='profit_abs')
except ValueError:
max_drawdown_per = 0
max_drawdown_abs = 0
tabular_data[-1]['max_drawdown_per'] = round(max_drawdown_per * 100, 2)
tabular_data[-1]['max_drawdown_abs'] = \
round_coin_value(max_drawdown_abs, results['config']['stake_currency'], False)
return tabular_data return tabular_data
@ -352,14 +345,14 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
} }
def generate_strategy_stats(btdata: Dict[str, DataFrame], def generate_strategy_stats(pairlist: List[str],
strategy: str, strategy: str,
content: Dict[str, Any], content: Dict[str, Any],
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
market_change: float market_change: float
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
:param btdata: Backtest data :param pairlist: List of pairs to backtest
:param strategy: Strategy name :param strategy: Strategy name
:param content: Backtest result data in the format: :param content: Backtest result data in the format:
{'results: results, 'config: config}}. {'results: results, 'config: config}}.
@ -372,11 +365,11 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
if not isinstance(results, DataFrame): if not isinstance(results, DataFrame):
return {} return {}
config = content['config'] config = content['config']
max_open_trades = min(config['max_open_trades'], len(btdata.keys())) max_open_trades = min(config['max_open_trades'], len(pairlist))
start_balance = config['dry_run_wallet'] start_balance = config['dry_run_wallet']
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, pair_results = generate_pair_metrics(pairlist, stake_currency=stake_currency,
starting_balance=start_balance, starting_balance=start_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
@ -385,7 +378,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
results=results) results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency,
starting_balance=start_balance, starting_balance=start_balance,
results=results.loc[results['is_open']], results=results.loc[results['is_open']],
skip_nan=True) skip_nan=True)
@ -435,7 +428,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'trades_per_day': round(len(results) / backtest_days, 2), 'trades_per_day': round(len(results) / backtest_days, 2),
'market_change': market_change, 'market_change': market_change,
'pairlist': list(btdata.keys()), 'pairlist': pairlist,
'stake_amount': config['stake_amount'], 'stake_amount': config['stake_amount'],
'stake_currency': config['stake_currency'], 'stake_currency': config['stake_currency'],
'stake_currency_decimals': decimals_per_coin(config['stake_currency']), 'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
@ -468,12 +461,14 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
} }
try: try:
max_drawdown, _, _, _, _ = calculate_max_drawdown( max_drawdown_legacy, _, _, _, _, _ = calculate_max_drawdown(
results, value_col='profit_ratio') results, value_col='profit_ratio')
drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown( (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val,
results, value_col='profit_abs') max_drawdown) = calculate_max_drawdown(
results, value_col='profit_abs', starting_balance=start_balance)
strat_stats.update({ strat_stats.update({
'max_drawdown': max_drawdown, 'max_drawdown': max_drawdown_legacy, # Deprecated - do not use
'max_drawdown_account': max_drawdown,
'max_drawdown_abs': drawdown_abs, 'max_drawdown_abs': drawdown_abs,
'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT), 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT),
'drawdown_start_ts': drawdown_start.timestamp() * 1000, 'drawdown_start_ts': drawdown_start.timestamp() * 1000,
@ -493,6 +488,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
except ValueError: except ValueError:
strat_stats.update({ strat_stats.update({
'max_drawdown': 0.0, 'max_drawdown': 0.0,
'max_drawdown_account': 0.0,
'max_drawdown_abs': 0.0, 'max_drawdown_abs': 0.0,
'max_drawdown_low': 0.0, 'max_drawdown_low': 0.0,
'max_drawdown_high': 0.0, 'max_drawdown_high': 0.0,
@ -521,13 +517,13 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
""" """
result: Dict[str, Any] = {'strategy': {}} result: Dict[str, Any] = {'strategy': {}}
market_change = calculate_market_change(btdata, 'close') market_change = calculate_market_change(btdata, 'close')
pairlist = list(btdata.keys())
for strategy, content in all_results.items(): for strategy, content in all_results.items():
strat_stats = generate_strategy_stats(btdata, strategy, content, strat_stats = generate_strategy_stats(pairlist, strategy, content,
min_date, max_date, market_change=market_change) min_date, max_date, market_change=market_change)
result['strategy'][strategy] = strat_stats result['strategy'][strategy] = strat_stats
strategy_results = generate_strategy_comparison(all_results=all_results) strategy_results = generate_strategy_comparison(bt_stats=result['strategy'])
result['strategy_comparison'] = strategy_results result['strategy_comparison'] = strategy_results
@ -652,7 +648,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
headers.append('Drawdown') headers.append('Drawdown')
# Align drawdown string on the center two space separator. # Align drawdown string on the center two space separator.
drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results] drawdown = [f'{t["max_drawdown_account"] * 100:.2f}' for t in strategy_results]
dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results]) dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results])
dd_pad_per = max([len(dd) for dd in drawdown]) dd_pad_per = max([len(dd) for dd in drawdown])
drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%' drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%'
@ -737,7 +733,10 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('Max balance', round_coin_value(strat_results['csum_max'], ('Max balance', round_coin_value(strat_results['csum_max'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Drawdown', f"{strat_results['max_drawdown']:.2%}"), # Compatibility to show old hyperopt results
('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}")
if 'max_drawdown_account' in strat_results else (
'Drawdown', f"{strat_results['max_drawdown']:.2%}"),
('Drawdown', round_coin_value(strat_results['max_drawdown_abs'], ('Drawdown', round_coin_value(strat_results['max_drawdown_abs'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'],

View File

@ -161,7 +161,7 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame,
Add scatter points indicating max drawdown Add scatter points indicating max drawdown
""" """
try: try:
max_drawdown, highdate, lowdate, _, _ = calculate_max_drawdown(trades) _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades)
drawdown = go.Scatter( drawdown = go.Scatter(
x=[highdate, lowdate], x=[highdate, lowdate],

View File

@ -4,7 +4,6 @@ Volume PairList provider
Provides dynamic pair list based on trade volumes Provides dynamic pair list based on trade volumes
""" """
import logging import logging
from functools import partial
from typing import Any, Dict, List from typing import Any, Dict, List
import arrow import arrow
@ -122,10 +121,17 @@ class VolumePairList(IPairList):
else: else:
# Use fresh pairlist # Use fresh pairlist
# Check if pair quote currency equals to the stake currency. # Check if pair quote currency equals to the stake currency.
_pairlist = [k for k in self._exchange.get_markets(
quote_currencies=[self._stake_currency],
tradable_only=True, active_only=True).keys()]
# No point in testing for blacklisted pairs...
_pairlist = self.verify_blacklist(_pairlist, logger.info)
filtered_tickers = [ filtered_tickers = [
v for k, v in tickers.items() v for k, v in tickers.items()
if (self._exchange.get_pair_quote_currency(k) == self._stake_currency if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
and (self._use_range or v[self._sort_key] is not None))] and (self._use_range or v[self._sort_key] is not None)
and v['symbol'] in _pairlist)]
pairlist = [s['symbol'] for s in filtered_tickers] pairlist = [s['symbol'] for s in filtered_tickers]
pairlist = self.filter_pairlist(pairlist, tickers) pairlist = self.filter_pairlist(pairlist, tickers)
@ -181,12 +187,16 @@ class VolumePairList(IPairList):
) in candles else None ) in candles else None
# in case of candle data calculate typical price and quoteVolume for candle # in case of candle data calculate typical price and quoteVolume for candle
if pair_candles is not None and not pair_candles.empty: if pair_candles is not None and not pair_candles.empty:
if self._exchange._ft_has["ohlcv_volume_currency"] == "base":
pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low'] pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low']
+ pair_candles['close']) / 3 + pair_candles['close']) / 3
pair_candles['quoteVolume'] = ( pair_candles['quoteVolume'] = (
pair_candles['volume'] * pair_candles['typical_price'] pair_candles['volume'] * pair_candles['typical_price']
) )
else:
# Exchange ohlcv data is in quote volume already.
pair_candles['quoteVolume'] = pair_candles['volume']
# ensure that a rolling sum over the lookback_period is built # ensure that a rolling sum over the lookback_period is built
# if pair_candles contains more candles than lookback_period # if pair_candles contains more candles than lookback_period
quoteVolume = (pair_candles['quoteVolume'] quoteVolume = (pair_candles['quoteVolume']
@ -207,7 +217,7 @@ class VolumePairList(IPairList):
# Validate whitelist to only have active market pairs # Validate whitelist to only have active market pairs
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
pairs = self.verify_blacklist(pairs, partial(self.log_once, logmethod=logger.info)) pairs = self.verify_blacklist(pairs, logmethod=logger.info)
# Limit pairlist to the requested number of pairs # Limit pairlist to the requested number of pairs
pairs = pairs[:self._number_pairs] pairs = pairs[:self._number_pairs]

View File

@ -55,7 +55,8 @@ class MaxDrawdown(IProtection):
# Drawdown is always positive # Drawdown is always positive
try: try:
drawdown, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit') # TODO: This should use absolute profit calculation, considering account balance.
drawdown, _, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit')
except ValueError: except ValueError:
return False, None, None return False, None, None

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.66.20 ccxt==1.66.32
# 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

View File

@ -43,7 +43,7 @@ setup(
], ],
install_requires=[ install_requires=[
# from requirements.txt # from requirements.txt
'ccxt>=1.66.20', 'ccxt>=1.66.32',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot>=13.4', 'python-telegram-bot>=13.4',
'arrow>=0.17.0', 'arrow>=0.17.0',

View File

@ -2222,7 +2222,7 @@ def saved_hyperopt_results():
'params_dict': { 'params_dict': {
'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501 'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'holding_avg': timedelta(minutes=3930.0), 'stake_currency': 'BTC', 'strategy_name': 'SampleStrategy'}, # noqa: E501 'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'max_drawdown': 0.23, 'max_drawdown_abs': -0.00125625, 'holding_avg': timedelta(minutes=3930.0), 'stake_currency': 'BTC', 'strategy_name': 'SampleStrategy'}, # noqa: E501
'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
'total_profit': -0.00125625, 'total_profit': -0.00125625,
'current_epoch': 1, 'current_epoch': 1,
@ -2238,7 +2238,7 @@ def saved_hyperopt_results():
'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
'stoploss': {'stoploss': -0.338070047333259}}, 'stoploss': {'stoploss': -0.338070047333259}},
'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'max_drawdown': 0.23, 'max_drawdown_abs': -0.00125625, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501
'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
'total_profit': 6.185e-05, 'total_profit': 6.185e-05,
'current_epoch': 2, 'current_epoch': 2,
@ -2248,7 +2248,7 @@ def saved_hyperopt_results():
'loss': 14.241196856510731, 'loss': 14.241196856510731,
'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501
'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501
'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501 'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'max_drawdown': 0.25, 'max_drawdown_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501
'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501
'total_profit': -0.13639474, 'total_profit': -0.13639474,
'current_epoch': 3, 'current_epoch': 3,
@ -2265,7 +2265,7 @@ def saved_hyperopt_results():
'loss': 0.22195522184191518, 'loss': 0.22195522184191518,
'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501
'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501
'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501 'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'max_drawdown': 0.34, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501
'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501
'total_profit': -0.002480140000000001, 'total_profit': -0.002480140000000001,
'current_epoch': 5, 'current_epoch': 5,
@ -2275,7 +2275,7 @@ def saved_hyperopt_results():
'loss': 0.545315889154162, 'loss': 0.545315889154162,
'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501
'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501 'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'max_drawdown': 0.45, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501
'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501
'total_profit': -0.0041773, 'total_profit': -0.0041773,
'current_epoch': 6, 'current_epoch': 6,
@ -2287,7 +2287,7 @@ def saved_hyperopt_results():
'params_details': { 'params_details': {
'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501 'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501 'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501 'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'max_drawdown': 0.50, 'max_drawdown_abs': -200.955321, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501
'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
'total_profit': -0.06339929, 'total_profit': -0.06339929,
'current_epoch': 7, 'current_epoch': 7,
@ -2297,7 +2297,7 @@ def saved_hyperopt_results():
'loss': 20.0, # noqa: E501 'loss': 20.0, # noqa: E501
'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501
'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501
'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'max_drawdown': 0.0, 'max_drawdown_abs': 0.52, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501
'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501
'total_profit': 0.0, 'total_profit': 0.0,
'current_epoch': 8, 'current_epoch': 8,
@ -2307,7 +2307,7 @@ def saved_hyperopt_results():
'loss': 2.4731817780991223, 'loss': 2.4731817780991223,
'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501
'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501 'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'max_drawdown': 0.41, 'max_drawdown_abs': -150.955321, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501
'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501
'total_profit': -0.044050070000000004, # noqa: E501 'total_profit': -0.044050070000000004, # noqa: E501
'current_epoch': 9, 'current_epoch': 9,
@ -2317,7 +2317,7 @@ def saved_hyperopt_results():
'loss': -0.2604606005845212, # noqa: E501 'loss': -0.2604606005845212, # noqa: E501
'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501
'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501
'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501 'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'max_drawdown': 0.13, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501
'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501
'total_profit': 0.00021629, 'total_profit': 0.00021629,
'current_epoch': 10, 'current_epoch': 10,
@ -2328,7 +2328,7 @@ def saved_hyperopt_results():
'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501
'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501
# New Hyperopt mode! # New Hyperopt mode!
'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501 'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'max_drawdown': 0.52, 'max_drawdown_abs': -224.955321, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501
'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501
'total_profit': -0.07436117, 'total_profit': -0.07436117,
'current_epoch': 11, 'current_epoch': 11,
@ -2338,7 +2338,7 @@ def saved_hyperopt_results():
'loss': 100000, 'loss': 100000,
'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501
'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501
'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'max_drawdown': 0.0, 'max_drawdown_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501
'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
'total_profit': 0, 'total_profit': 0,
'current_epoch': 12, 'current_epoch': 12,

View File

@ -8,14 +8,14 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD, from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_csum,
analyze_trade_parallelism, calculate_csum,
calculate_market_change, calculate_max_drawdown, calculate_market_change, calculate_max_drawdown,
calculate_underwater, combine_dataframes_with_mean, calculate_underwater, combine_dataframes_with_mean,
create_cum_profit, extract_trades_of_period, create_cum_profit, extract_trades_of_period,
get_latest_backtest_filename, get_latest_hyperopt_file, get_latest_backtest_filename, get_latest_hyperopt_file,
load_backtest_data, load_trades, 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 freqtrade.exceptions import OperationalException
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
@ -51,20 +51,14 @@ def test_get_latest_hyperopt_file(testdatadir, mocker):
assert res == testdatadir.parent / "hyperopt_results.pickle" assert res == testdatadir.parent / "hyperopt_results.pickle"
def test_load_backtest_data_old_format(testdatadir): def test_load_backtest_data_old_format(testdatadir, mocker):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_test222.json"
bt_data = load_backtest_data(filename) mocker.patch('freqtrade.data.btanalysis.load_backtest_stats', return_value=[])
assert isinstance(bt_data, DataFrame)
assert list(bt_data.columns) == BT_DATA_COLUMNS_OLD + ['profit_abs', 'profit_ratio']
assert len(bt_data) == 179
# Test loading from string (must yield same result) with pytest.raises(OperationalException,
bt_data2 = load_backtest_data(str(filename)) match=r"Backtest-results with only trades data are no longer supported."):
assert bt_data.equals(bt_data2) load_backtest_data(filename)
with pytest.raises(ValueError, match=r"File .* does not exist\."):
load_backtest_data(str("filename") + "nofile")
def test_load_backtest_data_new_format(testdatadir): def test_load_backtest_data_new_format(testdatadir):
@ -72,7 +66,7 @@ def test_load_backtest_data_new_format(testdatadir):
filename = testdatadir / "backtest-result_new.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
assert isinstance(bt_data, DataFrame) assert isinstance(bt_data, DataFrame)
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp'])
assert len(bt_data) == 179 assert len(bt_data) == 179
# Test loading from string (must yield same result) # Test loading from string (must yield same result)
@ -96,7 +90,7 @@ def test_load_backtest_data_multi(testdatadir):
for strategy in ('StrategyTestV2', 'TestStrategy'): for strategy in ('StrategyTestV2', 'TestStrategy'):
bt_data = load_backtest_data(filename, strategy=strategy) bt_data = load_backtest_data(filename, strategy=strategy)
assert isinstance(bt_data, DataFrame) assert isinstance(bt_data, DataFrame)
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp'])
assert len(bt_data) == 179 assert len(bt_data) == 179
# Test loading from string (must yield same result) # Test loading from string (must yield same result)
@ -168,8 +162,8 @@ def test_extract_trades_of_period(testdatadir):
assert trades1.iloc[-1].close_date == Arrow(2017, 11, 14, 15, 25, 0).datetime assert trades1.iloc[-1].close_date == Arrow(2017, 11, 14, 15, 25, 0).datetime
def test_analyze_trade_parallelism(default_conf, mocker, testdatadir): def test_analyze_trade_parallelism(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
res = analyze_trade_parallelism(bt_data, "5m") res = analyze_trade_parallelism(bt_data, "5m")
@ -243,7 +237,7 @@ def test_combine_dataframes_with_mean_no_data(testdatadir):
def test_create_cum_profit(testdatadir): def test_create_cum_profit(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
@ -259,7 +253,7 @@ def test_create_cum_profit(testdatadir):
def test_create_cum_profit1(testdatadir): def test_create_cum_profit1(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
# Move close-time to "off" the candle, to make sure the logic still works # Move close-time to "off" the candle, to make sure the logic still works
bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20) bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20)
@ -281,30 +275,31 @@ def test_create_cum_profit1(testdatadir):
def test_calculate_max_drawdown(testdatadir): def test_calculate_max_drawdown(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(bt_data) _, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown(
bt_data, value_col="profit_abs")
assert isinstance(drawdown, float) assert isinstance(drawdown, float)
assert pytest.approx(drawdown) == 0.21142322 assert pytest.approx(drawdown) == 0.12071099
assert isinstance(hdate, Timestamp) assert isinstance(hdate, Timestamp)
assert isinstance(lowdate, Timestamp) assert isinstance(lowdate, Timestamp)
assert isinstance(hval, float) assert isinstance(hval, float)
assert isinstance(lval, float) assert isinstance(lval, float)
assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC') assert hdate == Timestamp('2018-01-25 01:30:00', tz='UTC')
assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC') assert lowdate == Timestamp('2018-01-25 03:50:00', tz='UTC')
underwater = calculate_underwater(bt_data) underwater = calculate_underwater(bt_data)
assert isinstance(underwater, DataFrame) 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()) calculate_max_drawdown(DataFrame())
with pytest.raises(ValueError, match='Trade dataframe empty.'): with pytest.raises(ValueError, match='Trade dataframe empty.'):
calculate_underwater(DataFrame()) calculate_underwater(DataFrame())
def test_calculate_csum(testdatadir): def test_calculate_csum(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
csum_min, csum_max = calculate_csum(bt_data) csum_min, csum_max = calculate_csum(bt_data)
@ -332,12 +327,13 @@ def test_calculate_max_drawdown2():
# sort by profit and reset index # sort by profit and reset index
df = df.sort_values('profit').reset_index(drop=True) df = df.sort_values('profit').reset_index(drop=True)
df1 = df.copy() df1 = df.copy()
drawdown, hdate, ldate, hval, lval = calculate_max_drawdown( drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown(
df, date_col='open_date', value_col='profit') df, date_col='open_date', value_col='profit')
# Ensure df has not been altered. # Ensure df has not been altered.
assert df.equals(df1) assert df.equals(df1)
assert isinstance(drawdown, float) assert isinstance(drawdown, float)
assert isinstance(drawdown_rel, float)
# High must be before low # High must be before low
assert hdate < ldate assert hdate < ldate
# High value must be higher than low value # High value must be higher than low value

View File

@ -61,6 +61,11 @@ EXCHANGES = {
'futures_pair': 'BTC/USDT:USDT', 'futures_pair': 'BTC/USDT:USDT',
'futures': True, 'futures': True,
}, },
'bitvavo': {
'pair': 'BTC/EUR',
'hasQuoteVolume': True,
'timeframe': '5m',
},
} }

View File

@ -369,7 +369,7 @@ def test_hyperopt_format_results(hyperopt):
'backtest_start_time': 1619718665, 'backtest_start_time': 1619718665,
'backtest_end_time': 1619718665, 'backtest_end_time': 1619718665,
} }
results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result, results_metrics = generate_strategy_stats(['XRP/BTC'], '', bt_result,
Arrow(2017, 11, 14, 19, 32, 00), Arrow(2017, 11, 14, 19, 32, 00),
Arrow(2017, 12, 14, 19, 32, 00), market_change=0) Arrow(2017, 12, 14, 19, 32, 00), market_change=0)

View File

@ -1,4 +1,3 @@
import datetime
import re import re
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
@ -50,7 +49,7 @@ def test_text_table_bt_results():
' 0:20:00 | 2 0 1 66.7 |' ' 0:20:00 | 2 0 1 66.7 |'
) )
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
starting_balance=4, results=results) starting_balance=4, results=results)
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
@ -105,7 +104,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT) assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats['total_trades'] == len(results['DefStrat']['results']) assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
# Above sample had no loosing trade # Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0 assert strat_stats['max_drawdown_account'] == 0.0
# Retry with losing trade # Retry with losing trade
results = {'DefStrat': { results = {'DefStrat': {
@ -146,7 +145,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
assert 'strategy_comparison' in stats assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat'] strat_stats = stats['strategy']['DefStrat']
assert strat_stats['max_drawdown'] == 0.013803 assert pytest.approx(strat_stats['max_drawdown_account']) == 1.399999e-08
assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00' assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00'
assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00' assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00'
assert strat_stats['drawdown_end_ts'] == 1510699380000 assert strat_stats['drawdown_end_ts'] == 1510699380000
@ -168,7 +167,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
filename1 = Path(tmpdir / last_fn) filename1 = Path(tmpdir / last_fn)
assert filename1.is_file() assert filename1.is_file()
content = filename1.read_text() content = filename1.read_text()
assert 'max_drawdown' in content assert 'max_drawdown_account' in content
assert 'strategy' in content assert 'strategy' in content
assert 'pairlist' in content assert 'pairlist' in content
@ -211,7 +210,7 @@ def test_generate_pair_metrics():
} }
) )
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
starting_balance=2, results=results) starting_balance=2, results=results)
assert isinstance(pair_results, list) assert isinstance(pair_results, list)
assert len(pair_results) == 2 assert len(pair_results) == 2
@ -230,9 +229,9 @@ def test_generate_daily_stats(testdatadir):
assert isinstance(res, dict) assert isinstance(res, dict)
assert round(res['backtest_best_day'], 4) == 0.1796 assert round(res['backtest_best_day'], 4) == 0.1796
assert round(res['backtest_worst_day'], 4) == -0.1468 assert round(res['backtest_worst_day'], 4) == -0.1468
assert res['winning_days'] == 14 assert res['winning_days'] == 19
assert res['draw_days'] == 4 assert res['draw_days'] == 0
assert res['losing_days'] == 3 assert res['losing_days'] == 2
# Select empty dataframe! # Select empty dataframe!
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :]) res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
@ -327,51 +326,25 @@ def test_generate_sell_reason_stats():
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
def test_text_table_strategy(default_conf): def test_text_table_strategy(testdatadir):
default_conf['max_open_trades'] = 2 filename = testdatadir / "backtest-result_multistrat.json"
default_conf['dry_run_wallet'] = 3 bt_res_data = load_backtest_stats(filename)
results = {}
date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30) bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
delta = datetime.timedelta(days=1)
results['TestStrategy1'] = {'results': pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
), 'config': default_conf}
results['TestStrategy2'] = {'results': pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
'wins': [4, 1, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
), 'config': default_conf}
result_str = ( result_str = (
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
'|---------------+--------+----------------+----------------+------------------+' '|----------------+--------+----------------+----------------+------------------+'
'----------------+----------------+-------------------------+-----------------------|\n' '----------------+----------------+-------------------------+-----------------------|\n'
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' '| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |'
' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n' ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n'
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' '| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |'
' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |' ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |'
) )
strategy_results = generate_strategy_comparison(all_results=results) strategy_results = generate_strategy_comparison(bt_stats=bt_res_data['strategy'])
assert strategy_results == bt_res_data_comparison
assert text_table_strategy(strategy_results, 'BTC') == result_str assert text_table_strategy(strategy_results, 'BTC') == result_str
@ -421,6 +394,6 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
show_sorted_pairlist(default_conf, bt_data) show_sorted_pairlist(default_conf, bt_data)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Pairs for Strategy StrategyTestV2: \n[' in out assert 'Pairs for Strategy StrategyTestV3: \n[' in out
assert 'TOTAL' not in out assert 'TOTAL' not in out
assert '"ETH/BTC", // ' in out assert '"ETH/BTC", // ' in out

View File

@ -565,36 +565,41 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
assert log_has_re(r'^Removed .* from whitelist, because volatility.*$', caplog) assert log_has_re(r'^Removed .* from whitelist, because volatility.*$', caplog)
@pytest.mark.parametrize("pairlists,base_currency,volumefilter_result", [ @pytest.mark.parametrize("pairlists,base_currency,exchange,volumefilter_result", [
# default refresh of 1800 to small for daily candle lookback # default refresh of 1800 to small for daily candle lookback
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_days": 1}], "lookback_days": 1}],
"BTC", "default_refresh_too_short"), # OperationalException expected "BTC", "binance", "default_refresh_too_short"), # OperationalException expected
# ambigous configuration with lookback days and period # ambigous configuration with lookback days and period
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_days": 1, "lookback_period": 1}], "lookback_days": 1, "lookback_period": 1}],
"BTC", "lookback_days_and_period"), # OperationalException expected "BTC", "binance", "lookback_days_and_period"), # OperationalException expected
# negative lookback period # negative lookback period
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1d", "lookback_period": -1}], "lookback_timeframe": "1d", "lookback_period": -1}],
"BTC", "lookback_period_negative"), # OperationalException expected "BTC", "binance", "lookback_period_negative"), # OperationalException expected
# lookback range exceedes exchange limit # lookback range exceedes exchange limit
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1m", "lookback_period": 2000, "refresh_period": 3600}], "lookback_timeframe": "1m", "lookback_period": 2000, "refresh_period": 3600}],
"BTC", 'lookback_exceeds_exchange_request_size'), # OperationalException expected "BTC", "binance", "lookback_exceeds_exchange_request_size"), # OperationalException expected
# expecing pairs as given # expecing pairs as given
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
"BTC", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), "BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']),
# expecting pairs from default tickers, because 1h candles are not available # expecting pairs from default tickers, because 1h candles are not available
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}], "lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), "BTC", "binance", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
# ftx data is already in Quote currency, therefore won't require conversion
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
"BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']),
]) ])
def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history,
pairlists, base_currency, volumefilter_result, caplog) -> None: pairlists, base_currency, exchange, volumefilter_result) -> None:
whitelist_conf['pairlists'] = pairlists whitelist_conf['pairlists'] = pairlists
whitelist_conf['stake_currency'] = base_currency whitelist_conf['stake_currency'] = base_currency
whitelist_conf['exchange']['name'] = exchange
ohlcv_history_high_vola = ohlcv_history.copy() ohlcv_history_high_vola = ohlcv_history.copy()
ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090 ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090
@ -603,9 +608,14 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers,
ohlcv_history_medium_volume = ohlcv_history.copy() ohlcv_history_medium_volume = ohlcv_history.copy()
ohlcv_history_medium_volume.loc[ohlcv_history_medium_volume.index == 2, 'volume'] = 5 ohlcv_history_medium_volume.loc[ohlcv_history_medium_volume.index == 2, 'volume'] = 5
# create candles for high volume with all candles high volume # create candles for high volume with all candles high volume, but very low price.
ohlcv_history_high_volume = ohlcv_history.copy() ohlcv_history_high_volume = ohlcv_history.copy()
ohlcv_history_high_volume.loc[:, 'volume'] = 10 ohlcv_history_high_volume.loc[:, 'volume'] = 10
ohlcv_history_high_volume.loc[:, 'low'] = ohlcv_history_high_volume.loc[:, 'low'] * 0.01
ohlcv_history_high_volume.loc[:, 'high'] = ohlcv_history_high_volume.loc[:, 'high'] * 0.01
ohlcv_history_high_volume.loc[:, 'close'] = ohlcv_history_high_volume.loc[:, 'close'] * 0.01
mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True)
ohlcv_data = { ohlcv_data = {
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,

View File

@ -45,7 +45,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir):
default_conf['trade_source'] = "file" default_conf['trade_source'] = "file"
default_conf['timeframe'] = "5m" default_conf['timeframe'] = "5m"
default_conf["datadir"] = testdatadir default_conf["datadir"] = testdatadir
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
supported_markets = ["TRX/BTC", "ADA/BTC"] supported_markets = ["TRX/BTC", "ADA/BTC"]
ret = init_plotscript(default_conf, supported_markets) ret = init_plotscript(default_conf, supported_markets)
assert "ohlcv" in ret assert "ohlcv" in ret
@ -157,7 +157,7 @@ def test_plot_trades(testdatadir, caplog):
assert fig == fig1 assert fig == fig1
assert log_has("No trades found.", caplog) assert log_has("No trades found.", caplog)
pair = "ADA/BTC" pair = "ADA/BTC"
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
trades = load_backtest_data(filename) trades = load_backtest_data(filename)
trades = trades.loc[trades['pair'] == pair] trades = trades.loc[trades['pair'] == pair]
@ -294,7 +294,7 @@ def test_generate_plot_file(mocker, caplog):
def test_add_profit(testdatadir): def test_add_profit(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
@ -314,7 +314,7 @@ def test_add_profit(testdatadir):
def test_generate_profit_graph(testdatadir): def test_generate_profit_graph(testdatadir):
filename = testdatadir / "backtest-result_test.json" filename = testdatadir / "backtest-result_new.json"
trades = load_backtest_data(filename) trades = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
pairs = ["TRX/BTC", "XLM/BTC"] pairs = ["TRX/BTC", "XLM/BTC"]
@ -343,7 +343,7 @@ def test_generate_profit_graph(testdatadir):
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)
drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 35.69%")
assert isinstance(drawdown, go.Scatter) assert isinstance(drawdown, go.Scatter)
parallel = find_trace_in_fig_data(figure.data, "Parallel trades") parallel = find_trace_in_fig_data(figure.data, "Parallel trades")
assert isinstance(parallel, go.Scatter) assert isinstance(parallel, go.Scatter)
@ -381,7 +381,7 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
default_conf['trade_source'] = 'file' default_conf['trade_source'] = 'file'
default_conf["datadir"] = testdatadir default_conf["datadir"] = testdatadir
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
default_conf['indicators1'] = ["sma5", "ema10"] default_conf['indicators1'] = ["sma5", "ema10"]
default_conf['indicators2'] = ["macd"] default_conf['indicators2'] = ["macd"]
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"] default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
@ -452,7 +452,7 @@ def test_plot_profit(default_conf, mocker, testdatadir):
match=r"No trades found, cannot generate Profit-plot.*"): match=r"No trades found, cannot generate Profit-plot.*"):
plot_profit(default_conf) plot_profit(default_conf)
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
plot_profit(default_conf) plot_profit(default_conf)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long