Merge pull request #7976 from adarkforce/max-open-trades

Hyperopt Max open trades
This commit is contained in:
Matthias 2023-01-17 20:41:48 +01:00 committed by GitHub
commit 4aaa439221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 364 additions and 84 deletions

View File

@ -75,7 +75,7 @@ This function needs to return a floating point number (`float`). Smaller numbers
## Overriding pre-defined spaces ## Overriding pre-defined spaces
To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_space`, `trailing_space`), define a nested class called Hyperopt and define the required spaces as follows: To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_space`, `trailing_space`, `max_open_trades_space`), define a nested class called Hyperopt and define the required spaces as follows:
```python ```python
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal
@ -123,6 +123,12 @@ class MyAwesomeStrategy(IStrategy):
Categorical([True, False], name='trailing_only_offset_is_reached'), Categorical([True, False], name='trailing_only_offset_is_reached'),
] ]
# Define a custom max_open_trades space
def max_open_trades_space(self) -> List[Dimension]:
return [
Integer(-1, 10, name='max_open_trades'),
]
``` ```
!!! Note !!! Note

View File

@ -134,7 +134,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| Parameter | Description | | Parameter | Description |
|------------|-------------| |------------|-------------|
| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your pairlist is another limitation that can apply. If -1 then it is ignored (i.e. potentially unlimited open trades, limited by the pairlist). [More information below](#configuring-amount-per-trade).<br> **Datatype:** Positive integer or -1. | `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your pairlist is another limitation that can apply. If -1 then it is ignored (i.e. potentially unlimited open trades, limited by the pairlist). [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Positive integer or -1.
| `stake_currency` | **Required.** Crypto-currency used for trading. <br> **Datatype:** String | `stake_currency` | **Required.** Crypto-currency used for trading. <br> **Datatype:** String
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). <br> **Datatype:** Positive float or `"unlimited"`. | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). <br> **Datatype:** Positive float or `"unlimited"`.
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **Datatype:** Positive float between `0.1` and `1.0`. | `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **Datatype:** Positive float between `0.1` and `1.0`.
@ -263,6 +263,7 @@ Values set in the configuration file always overwrite values set in the strategy
* `minimal_roi` * `minimal_roi`
* `timeframe` * `timeframe`
* `stoploss` * `stoploss`
* `max_open_trades`
* `trailing_stop` * `trailing_stop`
* `trailing_stop_positive` * `trailing_stop_positive`
* `trailing_stop_positive_offset` * `trailing_stop_positive_offset`

View File

@ -50,7 +50,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--eps] [--dmmp] [--enable-protections] [--eps] [--dmmp] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET] [--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT] [--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]] [--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
[--print-all] [--no-color] [--print-json] [-j JOBS] [--print-all] [--no-color] [--print-json] [-j JOBS]
[--random-state INT] [--min-trades INT] [--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME] [--disable-param-export] [--hyperopt-loss NAME] [--disable-param-export]
@ -96,7 +96,7 @@ optional arguments:
Specify detail timeframe for backtesting (`1m`, `5m`, Specify detail timeframe for backtesting (`1m`, `5m`,
`30m`, `1h`, `1d`). `30m`, `1h`, `1d`).
-e INT, --epochs INT Specify number of epochs (default: 100). -e INT, --epochs INT Specify number of epochs (default: 100).
--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...] --spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]
Specify which parameters to hyperopt. Space-separated Specify which parameters to hyperopt. Space-separated
list. list.
--print-all Print all results, not only the best ones. --print-all Print all results, not only the best ones.
@ -180,6 +180,7 @@ Rarely you may also need to create a [nested class](advanced-hyperopt.md#overrid
* `generate_roi_table` - for custom ROI optimization (if you need the ranges for the values in the ROI table that differ from default or the number of entries (steps) in the ROI table which differs from the default 4 steps) * `generate_roi_table` - for custom ROI optimization (if you need the ranges for the values in the ROI table that differ from default or the number of entries (steps) in the ROI table which differs from the default 4 steps)
* `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default) * `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default)
* `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stop parameters in the optimization hyperspace that differ from default) * `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stop parameters in the optimization hyperspace that differ from default)
* `max_open_trades_space` - for custom max_open_trades optimization (if you need the ranges for the max_open_trades parameter in the optimization hyperspace that differ from default)
!!! Tip "Quickly optimize ROI, stoploss and trailing stoploss" !!! Tip "Quickly optimize ROI, stoploss and trailing stoploss"
You can quickly optimize the spaces `roi`, `stoploss` and `trailing` without changing anything in your strategy. You can quickly optimize the spaces `roi`, `stoploss` and `trailing` without changing anything in your strategy.
@ -643,6 +644,7 @@ Legal values are:
* `roi`: just optimize the minimal profit table for your strategy * `roi`: just optimize the minimal profit table for your strategy
* `stoploss`: search for the best stoploss value * `stoploss`: search for the best stoploss value
* `trailing`: search for the best trailing stop values * `trailing`: search for the best trailing stop values
* `trades`: search for the best max open trades values
* `protection`: search for the best protection parameters (read the [protections section](#optimizing-protections) on how to properly define these) * `protection`: search for the best protection parameters (read the [protections section](#optimizing-protections) on how to properly define these)
* `default`: `all` except `trailing` and `protection` * `default`: `all` except `trailing` and `protection`
* space-separated list of any of the above values for example `--spaces roi stoploss` * space-separated list of any of the above values for example `--spaces roi stoploss`
@ -916,5 +918,5 @@ Once the optimized strategy has been implemented into your strategy, you should
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting. To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
Should results not match, please double-check to make sure you transferred all conditions correctly. Should results not match, please double-check to make sure you transferred all conditions correctly.
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy. Pay special care to the stoploss, max_open_trades and trailing stoploss parameters, as these are often set in configuration files, which override changes to the strategy.
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`). You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss`, `max_open_trades` or `trailing_stop`).

View File

@ -251,7 +251,8 @@ AVAILABLE_CLI_OPTIONS = {
"spaces": Arg( "spaces": Arg(
'--spaces', '--spaces',
help='Specify which parameters to hyperopt. Space-separated list.', help='Specify which parameters to hyperopt. Space-separated list.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection', 'default'], choices=['all', 'buy', 'sell', 'roi', 'stoploss',
'trailing', 'protection', 'trades', 'default'],
nargs='+', nargs='+',
default='default', default='default',
), ),

View File

@ -636,7 +636,6 @@ SCHEMA_TRADE_REQUIRED = [
SCHEMA_BACKTEST_REQUIRED = [ SCHEMA_BACKTEST_REQUIRED = [
'exchange', 'exchange',
'max_open_trades',
'stake_currency', 'stake_currency',
'stake_amount', 'stake_amount',
'dry_run_wallet', 'dry_run_wallet',
@ -646,6 +645,7 @@ SCHEMA_BACKTEST_REQUIRED = [
SCHEMA_BACKTEST_REQUIRED_FINAL = SCHEMA_BACKTEST_REQUIRED + [ SCHEMA_BACKTEST_REQUIRED_FINAL = SCHEMA_BACKTEST_REQUIRED + [
'stoploss', 'stoploss',
'minimal_roi', 'minimal_roi',
'max_open_trades'
] ]
SCHEMA_MINIMAL_REQUIRED = [ SCHEMA_MINIMAL_REQUIRED = [
@ -681,3 +681,4 @@ MakerTaker = Literal['maker', 'taker']
BidAsk = Literal['bid', 'ask'] BidAsk = Literal['bid', 'ask']
Config = Dict[str, Any] Config = Dict[str, Any]
IntOrInf = float

View File

@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Union
import numpy as np 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, IntOrInf
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import json_load from freqtrade.misc import json_load
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
@ -332,7 +332,7 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
def evaluate_result_multi(results: pd.DataFrame, timeframe: str, def evaluate_result_multi(results: pd.DataFrame, timeframe: str,
max_open_trades: int) -> pd.DataFrame: max_open_trades: IntOrInf) -> pd.DataFrame:
""" """
Find overlapping trades by expanding each trade once per period it was open Find overlapping trades by expanding each trade once per period it was open
and then counting overlaps and then counting overlaps

View File

@ -15,7 +15,7 @@ from pandas import DataFrame
from freqtrade import constants from freqtrade import constants
from freqtrade.configuration import TimeRange, validate_config_consistency from freqtrade.configuration import TimeRange, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config, LongShort from freqtrade.constants import DATETIME_PRINT_FORMAT, Config, IntOrInf, LongShort
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe
from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.converter import trim_dataframe, trim_dataframes
@ -922,8 +922,9 @@ class Backtesting:
trade.close(exit_row[OPEN_IDX], show_msg=False) trade.close(exit_row[OPEN_IDX], show_msg=False)
LocalTrade.close_bt_trade(trade) LocalTrade.close_bt_trade(trade)
def trade_slot_available(self, max_open_trades: int, open_trade_count: int) -> bool: def trade_slot_available(self, open_trade_count: int) -> bool:
# Always allow trades when max_open_trades is enabled. # Always allow trades when max_open_trades is enabled.
max_open_trades: IntOrInf = self.config['max_open_trades']
if max_open_trades <= 0 or open_trade_count < max_open_trades: if max_open_trades <= 0 or open_trade_count < max_open_trades:
return True return True
# Rejected trade # Rejected trade
@ -1053,7 +1054,7 @@ class Backtesting:
def backtest_loop( def backtest_loop(
self, row: Tuple, pair: str, current_time: datetime, end_date: datetime, self, row: Tuple, pair: str, current_time: datetime, end_date: datetime,
max_open_trades: int, open_trade_count_start: int, trade_dir: Optional[LongShort], open_trade_count_start: int, trade_dir: Optional[LongShort],
is_first: bool = True) -> int: is_first: bool = True) -> int:
""" """
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized. NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
@ -1076,7 +1077,7 @@ class Backtesting:
if ( if (
(self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0) (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0)
and is_first and is_first
and self.trade_slot_available(max_open_trades, open_trade_count_start) and self.trade_slot_available(open_trade_count_start)
and current_time != end_date and current_time != end_date
and trade_dir is not None and trade_dir is not None
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir) and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
@ -1123,8 +1124,7 @@ class Backtesting:
return open_trade_count_start return open_trade_count_start
def backtest(self, processed: Dict, def backtest(self, processed: Dict,
start_date: datetime, end_date: datetime, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
max_open_trades: int = 0) -> Dict[str, Any]:
""" """
Implement backtesting functionality Implement backtesting functionality
@ -1136,7 +1136,6 @@ class Backtesting:
optimize memory usage! optimize memory usage!
:param start_date: backtesting timerange start datetime :param start_date: backtesting timerange start datetime
:param end_date: backtesting timerange end datetime :param end_date: backtesting timerange end datetime
:param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited
:return: DataFrame with trades (results of backtesting) :return: DataFrame with trades (results of backtesting)
""" """
self.prepare_backtest(self.enable_protections) self.prepare_backtest(self.enable_protections)
@ -1185,7 +1184,7 @@ class Backtesting:
if len(detail_data) == 0: if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle # Fall back to "regular" data if no detail data was found for this candle
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, max_open_trades, row, pair, current_time, end_date,
open_trade_count_start, trade_dir) open_trade_count_start, trade_dir)
continue continue
detail_data.loc[:, 'enter_long'] = row[LONG_IDX] detail_data.loc[:, 'enter_long'] = row[LONG_IDX]
@ -1198,13 +1197,13 @@ class Backtesting:
current_time_det = current_time current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist(): for det_row in detail_data[HEADERS].values.tolist():
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
det_row, pair, current_time_det, end_date, max_open_trades, det_row, pair, current_time_det, end_date,
open_trade_count_start, trade_dir, is_first) open_trade_count_start, trade_dir, is_first)
current_time_det += timedelta(minutes=self.timeframe_detail_min) current_time_det += timedelta(minutes=self.timeframe_detail_min)
is_first = False is_first = False
else: else:
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, max_open_trades, row, pair, current_time, end_date,
open_trade_count_start, trade_dir) open_trade_count_start, trade_dir)
# Move time one configured time_interval ahead. # Move time one configured time_interval ahead.
@ -1237,13 +1236,11 @@ class Backtesting:
self._set_strategy(strat) self._set_strategy(strat)
# Use max_open_trades in backtesting, except --disable-max-market-positions is set # Use max_open_trades in backtesting, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
# Must come from strategy config, as the strategy may modify this setting.
max_open_trades = self.strategy.config['max_open_trades']
else:
logger.info( logger.info(
'Ignoring max_open_trades (--disable-max-market-positions was used) ...') 'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0 self.strategy.max_open_trades = float('inf')
self.config.update({'max_open_trades': self.strategy.max_open_trades})
# need to reprocess data every time to populate signals # need to reprocess data every time to populate signals
preprocessed = self.strategy.advise_all_indicators(data) preprocessed = self.strategy.advise_all_indicators(data)
@ -1266,7 +1263,6 @@ class Backtesting:
processed=preprocessed, processed=preprocessed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=max_open_trades,
) )
backtest_end_time = datetime.now(timezone.utc) backtest_end_time = datetime.now(timezone.utc)
results.update({ results.update({

View File

@ -74,6 +74,7 @@ class Hyperopt:
self.roi_space: List[Dimension] = [] self.roi_space: List[Dimension] = []
self.stoploss_space: List[Dimension] = [] self.stoploss_space: List[Dimension] = []
self.trailing_space: List[Dimension] = [] self.trailing_space: List[Dimension] = []
self.max_open_trades_space: List[Dimension] = []
self.dimensions: List[Dimension] = [] self.dimensions: List[Dimension] = []
self.config = config self.config = config
@ -117,11 +118,10 @@ class Hyperopt:
self.current_best_epoch: Optional[Dict[str, Any]] = None self.current_best_epoch: Optional[Dict[str, Any]] = None
# Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
self.max_open_trades = self.config['max_open_trades']
else:
logger.debug('Ignoring max_open_trades (--disable-max-market-positions was used) ...') logger.debug('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
self.max_open_trades = 0 self.backtesting.strategy.max_open_trades = float('inf')
config.update({'max_open_trades': self.backtesting.strategy.max_open_trades})
if HyperoptTools.has_space(self.config, 'sell'): if HyperoptTools.has_space(self.config, 'sell'):
# Make sure use_exit_signal is enabled # Make sure use_exit_signal is enabled
@ -209,6 +209,10 @@ class Hyperopt:
result['stoploss'] = {p.name: params.get(p.name) for p in self.stoploss_space} result['stoploss'] = {p.name: params.get(p.name) for p in self.stoploss_space}
if HyperoptTools.has_space(self.config, 'trailing'): if HyperoptTools.has_space(self.config, 'trailing'):
result['trailing'] = self.custom_hyperopt.generate_trailing_params(params) result['trailing'] = self.custom_hyperopt.generate_trailing_params(params)
if HyperoptTools.has_space(self.config, 'trades'):
result['max_open_trades'] = {
'max_open_trades': self.backtesting.strategy.max_open_trades
if self.backtesting.strategy.max_open_trades != float('inf') else -1}
return result return result
@ -229,6 +233,8 @@ class Hyperopt:
'trailing_stop_positive_offset': strategy.trailing_stop_positive_offset, 'trailing_stop_positive_offset': strategy.trailing_stop_positive_offset,
'trailing_only_offset_is_reached': strategy.trailing_only_offset_is_reached, 'trailing_only_offset_is_reached': strategy.trailing_only_offset_is_reached,
} }
if not HyperoptTools.has_space(self.config, 'trades'):
result['max_open_trades'] = {'max_open_trades': strategy.max_open_trades}
return result return result
def print_results(self, results) -> None: def print_results(self, results) -> None:
@ -280,8 +286,13 @@ class Hyperopt:
logger.debug("Hyperopt has 'trailing' space") logger.debug("Hyperopt has 'trailing' space")
self.trailing_space = self.custom_hyperopt.trailing_space() self.trailing_space = self.custom_hyperopt.trailing_space()
if HyperoptTools.has_space(self.config, 'trades'):
logger.debug("Hyperopt has 'trades' space")
self.max_open_trades_space = self.custom_hyperopt.max_open_trades_space()
self.dimensions = (self.buy_space + self.sell_space + self.protection_space self.dimensions = (self.buy_space + self.sell_space + self.protection_space
+ self.roi_space + self.stoploss_space + self.trailing_space) + self.roi_space + self.stoploss_space + self.trailing_space
+ self.max_open_trades_space)
def assign_params(self, params_dict: Dict, category: str) -> None: def assign_params(self, params_dict: Dict, category: str) -> None:
""" """
@ -328,6 +339,20 @@ class Hyperopt:
self.backtesting.strategy.trailing_only_offset_is_reached = \ self.backtesting.strategy.trailing_only_offset_is_reached = \
d['trailing_only_offset_is_reached'] d['trailing_only_offset_is_reached']
if HyperoptTools.has_space(self.config, 'trades'):
if self.config["stake_amount"] == "unlimited" and \
(params_dict['max_open_trades'] == -1 or params_dict['max_open_trades'] == 0):
# Ignore unlimited max open trades if stake amount is unlimited
params_dict.update({'max_open_trades': self.config['max_open_trades']})
updated_max_open_trades = int(params_dict['max_open_trades']) \
if (params_dict['max_open_trades'] != -1
and params_dict['max_open_trades'] != 0) else float('inf')
self.config.update({'max_open_trades': updated_max_open_trades})
self.backtesting.strategy.max_open_trades = updated_max_open_trades
with self.data_pickle_file.open('rb') as f: with self.data_pickle_file.open('rb') as f:
processed = load(f, mmap_mode='r') processed = load(f, mmap_mode='r')
if self.analyze_per_epoch: if self.analyze_per_epoch:
@ -337,8 +362,7 @@ class Hyperopt:
bt_results = self.backtesting.backtest( bt_results = self.backtesting.backtest(
processed=processed, processed=processed,
start_date=self.min_date, start_date=self.min_date,
end_date=self.max_date, end_date=self.max_date
max_open_trades=self.max_open_trades,
) )
backtest_end_time = datetime.now(timezone.utc) backtest_end_time = datetime.now(timezone.utc)
bt_results.update({ bt_results.update({

View File

@ -91,5 +91,8 @@ class HyperOptAuto(IHyperOpt):
def trailing_space(self) -> List['Dimension']: def trailing_space(self) -> List['Dimension']:
return self._get_func('trailing_space')() return self._get_func('trailing_space')()
def max_open_trades_space(self) -> List['Dimension']:
return self._get_func('max_open_trades_space')()
def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType: def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType:
return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs) return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs)

View File

@ -191,6 +191,16 @@ class IHyperOpt(ABC):
Categorical([True, False], name='trailing_only_offset_is_reached'), Categorical([True, False], name='trailing_only_offset_is_reached'),
] ]
def max_open_trades_space(self) -> List[Dimension]:
"""
Create a max open trades space.
You may override it in your custom Hyperopt class.
"""
return [
Integer(-1, 10, name='max_open_trades'),
]
# This is needed for proper unpickling the class attribute timeframe # This is needed for proper unpickling the class attribute timeframe
# which is set to the actual value by the resolver. # which is set to the actual value by the resolver.
# Why do I still need such shamanic mantras in modern python? # Why do I still need such shamanic mantras in modern python?

View File

@ -96,7 +96,7 @@ class HyperoptTools():
Tell if the space value is contained in the configuration Tell if the space value is contained in the configuration
""" """
# 'trailing' and 'protection spaces are not included in the 'default' set of spaces # 'trailing' and 'protection spaces are not included in the 'default' set of spaces
if space in ('trailing', 'protection'): if space in ('trailing', 'protection', 'trades'):
return any(s in config['spaces'] for s in [space, 'all']) return any(s in config['spaces'] for s in [space, 'all'])
else: else:
return any(s in config['spaces'] for s in [space, 'all', 'default']) return any(s in config['spaces'] for s in [space, 'all', 'default'])
@ -187,7 +187,8 @@ class HyperoptTools():
if print_json: if print_json:
result_dict: Dict = {} result_dict: Dict = {}
for s in ['buy', 'sell', 'protection', 'roi', 'stoploss', 'trailing']: for s in ['buy', 'sell', 'protection',
'roi', 'stoploss', 'trailing', 'max_open_trades']:
HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s) HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s)
print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE)) print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE))
@ -201,6 +202,8 @@ class HyperoptTools():
HyperoptTools._params_pretty_print(params, 'roi', "ROI table:", non_optimized) HyperoptTools._params_pretty_print(params, 'roi', "ROI table:", non_optimized)
HyperoptTools._params_pretty_print(params, 'stoploss', "Stoploss:", non_optimized) HyperoptTools._params_pretty_print(params, 'stoploss', "Stoploss:", non_optimized)
HyperoptTools._params_pretty_print(params, 'trailing', "Trailing stop:", non_optimized) HyperoptTools._params_pretty_print(params, 'trailing', "Trailing stop:", non_optimized)
HyperoptTools._params_pretty_print(
params, 'max_open_trades', "Max Open Trades:", non_optimized)
@staticmethod @staticmethod
def _params_update_for_json(result_dict, params, non_optimized, space: str) -> None: def _params_update_for_json(result_dict, params, non_optimized, space: str) -> None:
@ -239,7 +242,9 @@ class HyperoptTools():
if space == "stoploss": if space == "stoploss":
stoploss = safe_value_fallback2(space_params, no_params, space, space) stoploss = safe_value_fallback2(space_params, no_params, space, space)
result += (f"stoploss = {stoploss}{appendix}") result += (f"stoploss = {stoploss}{appendix}")
elif space == "max_open_trades":
max_open_trades = safe_value_fallback2(space_params, no_params, space, space)
result += (f"max_open_trades = {max_open_trades}{appendix}")
elif space == "roi": elif space == "roi":
result = result[:-1] + f'{appendix}\n' result = result[:-1] + f'{appendix}\n'
minimal_roi_result = rapidjson.dumps({ minimal_roi_result = rapidjson.dumps({

View File

@ -8,7 +8,7 @@ from pandas import DataFrame, to_datetime
from tabulate import tabulate from tabulate import tabulate
from freqtrade.constants import (DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT, from freqtrade.constants import (DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT,
Config) Config, IntOrInf)
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum, from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
calculate_expectancy, calculate_market_change, calculate_expectancy, calculate_market_change,
calculate_max_drawdown, calculate_sharpe, calculate_sortino) calculate_max_drawdown, calculate_sharpe, calculate_sortino)
@ -191,7 +191,7 @@ def generate_tag_metrics(tag_type: str,
return [] return []
def generate_exit_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]: def generate_exit_reason_stats(max_open_trades: IntOrInf, results: DataFrame) -> List[Dict]:
""" """
Generate small table outlining Backtest results Generate small table outlining Backtest results
:param max_open_trades: Max_open_trades parameter :param max_open_trades: Max_open_trades parameter

View File

@ -76,6 +76,7 @@ class StrategyResolver(IResolver):
("ignore_buying_expired_candle_after", 0), ("ignore_buying_expired_candle_after", 0),
("position_adjustment_enable", False), ("position_adjustment_enable", False),
("max_entry_position_adjustment", -1), ("max_entry_position_adjustment", -1),
("max_open_trades", -1)
] ]
for attribute, default in attributes: for attribute, default in attributes:
StrategyResolver._override_attribute_helper(strategy, config, StrategyResolver._override_attribute_helper(strategy, config,
@ -110,7 +111,11 @@ class StrategyResolver(IResolver):
val = getattr(strategy, attribute) val = getattr(strategy, attribute)
# None's cannot exist in the config, so do not copy them # None's cannot exist in the config, so do not copy them
if val is not None: if val is not None:
config[attribute] = val # max_open_trades set to -1 in the strategy will be copied as infinity in the config
if attribute == 'max_open_trades' and val == -1:
config[attribute] = float('inf')
else:
config[attribute] = val
# Explicitly check for None here as other "falsy" values are possible # Explicitly check for None here as other "falsy" values are possible
elif default is not None: elif default is not None:
setattr(strategy, attribute, default) setattr(strategy, attribute, default)
@ -128,6 +133,8 @@ class StrategyResolver(IResolver):
key=lambda t: t[0])) key=lambda t: t[0]))
if hasattr(strategy, 'stoploss'): if hasattr(strategy, 'stoploss'):
strategy.stoploss = float(strategy.stoploss) strategy.stoploss = float(strategy.stoploss)
if hasattr(strategy, 'max_open_trades') and strategy.max_open_trades < 0:
strategy.max_open_trades = float('inf')
return strategy return strategy
@staticmethod @staticmethod

View File

@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT, IntOrInf
from freqtrade.enums import OrderTypeValues, SignalDirection, TradingMode from freqtrade.enums import OrderTypeValues, SignalDirection, TradingMode
@ -165,7 +165,7 @@ class ShowConfig(BaseModel):
stake_amount: str stake_amount: str
available_capital: Optional[float] available_capital: Optional[float]
stake_currency_decimals: int stake_currency_decimals: int
max_open_trades: int max_open_trades: IntOrInf
minimal_roi: Dict[str, Any] minimal_roi: Dict[str, Any]
stoploss: Optional[float] stoploss: Optional[float]
trailing_stop: Optional[bool] trailing_stop: Optional[bool]
@ -422,7 +422,7 @@ class BacktestRequest(BaseModel):
timeframe: Optional[str] timeframe: Optional[str]
timeframe_detail: Optional[str] timeframe_detail: Optional[str]
timerange: Optional[str] timerange: Optional[str]
max_open_trades: Optional[int] max_open_trades: Optional[IntOrInf]
stake_amount: Optional[str] stake_amount: Optional[str]
enable_protections: bool enable_protections: bool
dry_run_wallet: Optional[float] dry_run_wallet: Optional[float]

View File

@ -673,6 +673,7 @@ class RPC:
if self._freqtrade.state == State.RUNNING: if self._freqtrade.state == State.RUNNING:
# Set 'max_open_trades' to 0 # Set 'max_open_trades' to 0
self._freqtrade.config['max_open_trades'] = 0 self._freqtrade.config['max_open_trades'] = 0
self._freqtrade.strategy.max_open_trades = 0
return {'status': 'No more entries will occur from now. Run /reload_config to reset.'} return {'status': 'No more entries will occur from now. Run /reload_config to reset.'}

View File

@ -80,6 +80,8 @@ class HyperStrategyMixin:
self.stoploss = params.get('stoploss', {}).get( self.stoploss = params.get('stoploss', {}).get(
'stoploss', getattr(self, 'stoploss', -0.1)) 'stoploss', getattr(self, 'stoploss', -0.1))
self.max_open_trades = params.get('max_open_trades', {}).get(
'max_open_trades', getattr(self, 'max_open_trades', -1))
trailing = params.get('trailing', {}) trailing = params.get('trailing', {})
self.trailing_stop = trailing.get( self.trailing_stop = trailing.get(
'trailing_stop', getattr(self, 'trailing_stop', False)) 'trailing_stop', getattr(self, 'trailing_stop', False))

View File

@ -10,7 +10,7 @@ from typing import Dict, List, Optional, Tuple, Union
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config, ListPairsWithTimeframes from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection,
SignalTagType, SignalType, TradingMode) SignalTagType, SignalType, TradingMode)
@ -54,6 +54,9 @@ class IStrategy(ABC, HyperStrategyMixin):
# associated stoploss # associated stoploss
stoploss: float stoploss: float
# max open trades for the strategy
max_open_trades: IntOrInf
# trailing stoploss # trailing stoploss
trailing_stop: bool = False trailing_stop: bool = False
trailing_stop_positive: Optional[float] = None trailing_stop_positive: Optional[float] = None

View File

@ -919,6 +919,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
default_conf["trailing_stop_positive"] = data.trailing_stop_positive default_conf["trailing_stop_positive"] = data.trailing_stop_positive
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
default_conf["use_exit_signal"] = data.use_exit_signal default_conf["use_exit_signal"] = data.use_exit_signal
default_conf["max_open_trades"] = 10
mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
@ -951,7 +952,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
processed=data_processed, processed=data_processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']

View File

@ -96,7 +96,6 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
'processed': processed, 'processed': processed,
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 10,
} }
@ -685,6 +684,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -702,7 +703,6 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty
@ -786,6 +786,8 @@ def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_de
def custom_entry_price(proposed_rate, **kwargs): def custom_entry_price(proposed_rate, **kwargs):
return proposed_rate * 0.997 return proposed_rate * 0.997
default_conf_usdt['max_open_trades'] = 10
backtesting = Backtesting(default_conf_usdt) backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.populate_entry_trend = advise_entry backtesting.strategy.populate_entry_trend = advise_entry
@ -806,7 +808,6 @@ def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_de
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty
@ -860,6 +861,7 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
patch_exchange(mocker) patch_exchange(mocker)
default_conf['max_open_trades'] = 1
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
# Testing dataframe contains 11 candles. Expecting 10 timed out orders. # Testing dataframe contains 11 candles. Expecting 10 timed out orders.
@ -872,7 +874,6 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
processed=deepcopy(data), processed=deepcopy(data),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert result['timedout_entry_orders'] == 10 assert result['timedout_entry_orders'] == 10
@ -880,6 +881,7 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 1
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -897,7 +899,6 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert not results['results'].empty assert not results['results'].empty
assert len(results['results']) == 1 assert len(results['results']) == 1
@ -905,6 +906,8 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -928,7 +931,6 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
@ -949,6 +951,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000)
@ -982,7 +985,6 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
assert count == 5 assert count == 5
@ -999,6 +1001,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
default_conf['enable_protections'] = True default_conf['enable_protections'] = True
default_conf['timeframe'] = '1m' default_conf['timeframe'] = '1m'
default_conf['max_open_trades'] = 1
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -1025,7 +1028,6 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert len(results['results']) == numres assert len(results['results']) == numres
@ -1063,11 +1065,12 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict) assert isinstance(processed, dict)
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
results = backtesting.backtest( results = backtesting.backtest(
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert len(results['results']) == expected assert len(results['results']) == expected
@ -1078,7 +1081,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
buy_value = 1 buy_value = 1
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1095,6 +1098,7 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1108,6 +1112,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir) pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m' default_conf['timeframe'] = '1m'
@ -1166,6 +1171,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
if tres > 0: if tres > 0:
data[pair] = data[pair][tres:].reset_index() data[pair] = data[pair][tres:].reset_index()
default_conf['timeframe'] = '5m' default_conf['timeframe'] = '5m'
default_conf['max_open_trades'] = 3
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1174,11 +1180,11 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
backtest_conf = { backtest_conf = {
'processed': deepcopy(processed), 'processed': deepcopy(processed),
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 3,
} }
results = backtesting.backtest(**backtest_conf) results = backtesting.backtest(**backtest_conf)
@ -1196,11 +1202,12 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0] backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count ) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
backtest_conf = { backtest_conf = {
'processed': deepcopy(processed), 'processed': deepcopy(processed),
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 1,
} }
results = backtesting.backtest(**backtest_conf) results = backtesting.backtest(**backtest_conf)
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0 assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0

View File

@ -17,6 +17,7 @@ from tests.conftest import patch_exchange
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision', mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision',
lambda x, *args, **kwargs: round(x, 8)) lambda x, *args, **kwargs: round(x, 8))
@ -41,7 +42,6 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty

View File

@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring,W0212,C0103 # pragma pylint: disable=missing-docstring,W0212,C0103
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import wraps
from pathlib import Path from pathlib import Path
from unittest.mock import ANY, MagicMock, PropertyMock from unittest.mock import ANY, MagicMock, PropertyMock
@ -7,6 +8,7 @@ import pandas as pd
import pytest import pytest
from arrow import Arrow from arrow import Arrow
from filelock import Timeout from filelock import Timeout
from skopt.space import Integer
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
@ -292,6 +294,8 @@ def test_params_no_optimize_details(hyperopt) -> None:
assert res['roi']['0'] == 0.04 assert res['roi']['0'] == 0.04
assert "stoploss" in res assert "stoploss" in res
assert res['stoploss']['stoploss'] == -0.1 assert res['stoploss']['stoploss'] == -0.1
assert "max_open_trades" in res
assert res['max_open_trades']['max_open_trades'] == 1
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
@ -334,8 +338,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -474,6 +477,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing_stop_positive': 0.02, 'trailing_stop_positive': 0.02,
'trailing_stop_positive_offset_p1': 0.05, 'trailing_stop_positive_offset_p1': 0.05,
'trailing_only_offset_is_reached': False, 'trailing_only_offset_is_reached': False,
'max_open_trades': 3,
} }
response_expected = { response_expected = {
'loss': 1.9147239021396234, 'loss': 1.9147239021396234,
@ -499,7 +503,9 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing': {'trailing_only_offset_is_reached': False, 'trailing': {'trailing_only_offset_is_reached': False,
'trailing_stop': True, 'trailing_stop': True,
'trailing_stop_positive': 0.02, 'trailing_stop_positive': 0.02,
'trailing_stop_positive_offset': 0.07}}, 'trailing_stop_positive_offset': 0.07},
'max_open_trades': {'max_open_trades': 3}
},
'params_dict': optimizer_param, 'params_dict': optimizer_param,
'params_not_optimized': {'buy': {}, 'protection': {}, 'sell': {}}, 'params_not_optimized': {'buy': {}, 'protection': {}, 'sell': {}},
'results_metrics': ANY, 'results_metrics': ANY,
@ -548,7 +554,8 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
'buy': {'mfi-value': None}, 'buy': {'mfi-value': None},
'sell': {'sell-mfi-value': None}, 'sell': {'sell-mfi-value': None},
'roi': {}, 'stoploss': {'stoploss': None}, 'roi': {}, 'stoploss': {'stoploss': None},
'trailing': {'trailing_stop': None} 'trailing': {'trailing_stop': None},
'max_open_trades': {'max_open_trades': None}
}, },
'results_metrics': generate_result_metrics(), 'results_metrics': generate_result_metrics(),
}]) }])
@ -571,7 +578,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
result_str = ( result_str = (
'{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"' '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"'
':{},"stoploss":null,"trailing_stop":null}' ':{},"stoploss":null,"trailing_stop":null,"max_open_trades":null}'
) )
assert result_str in out # noqa: E501 assert result_str in out # noqa: E501
# Should be called for historical candle data # Should be called for historical candle data
@ -702,8 +709,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -776,8 +782,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -819,8 +824,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -874,6 +878,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
assert hyperopt.backtesting.strategy.buy_rsi.value == 35 assert hyperopt.backtesting.strategy.buy_rsi.value == 35
assert hyperopt.backtesting.strategy.sell_rsi.value == 74 assert hyperopt.backtesting.strategy.sell_rsi.value == 74
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
assert hyperopt.backtesting.strategy.max_open_trades == 1
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range) assert isinstance(buy_rsi_range, range)
# Range from 0 - 50 (inclusive) # Range from 0 - 50 (inclusive)
@ -884,6 +889,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30 assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30
assert hyperopt.backtesting.strategy.buy_rsi.value != 35 assert hyperopt.backtesting.strategy.buy_rsi.value != 35
assert hyperopt.backtesting.strategy.sell_rsi.value != 74 assert hyperopt.backtesting.strategy.sell_rsi.value != 74
assert hyperopt.backtesting.strategy.max_open_trades != 1
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1' hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1'
with pytest.raises(OperationalException, match="Estimator ET1 not supported."): with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
@ -984,3 +990,124 @@ def test_SKDecimal():
assert space.transform([2.0]) == [200] assert space.transform([2.0]) == [200]
assert space.transform([1.0]) == [100] assert space.transform([1.0]) == [100]
assert space.transform([1.5, 1.6]) == [150, 160] assert space.transform([1.5, 1.6]) == [150, 160]
def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmpdir, fee) -> None:
# This test is to ensure that unlimited max_open_trades are ignored for the backtesting
# if we have an unlimited stake amount
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
hyperopt_conf.update({
'strategy': 'HyperoptableStrategy',
'user_data_dir': Path(tmpdir),
'hyperopt_random_state': 42,
'spaces': ['trades'],
'stake_amount': 'unlimited'
})
hyperopt = Hyperopt(hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._get_params_dict',
return_value={
'max_open_trades': -1
})
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert hyperopt.backtesting.strategy.max_open_trades == 1
hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 1
def test_max_open_trades_dump(mocker, hyperopt_conf, tmpdir, fee, capsys) -> None:
# This test is to ensure that after hyperopting, max_open_trades is never
# saved as inf in the output json params
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
hyperopt_conf.update({
'strategy': 'HyperoptableStrategy',
'user_data_dir': Path(tmpdir),
'hyperopt_random_state': 42,
'spaces': ['trades'],
})
hyperopt = Hyperopt(hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._get_params_dict',
return_value={
'max_open_trades': -1
})
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
hyperopt.start()
out, err = capsys.readouterr()
assert 'max_open_trades = -1' in out
assert 'max_open_trades = inf' not in out
##############
hyperopt_conf.update({'print_json': True})
hyperopt = Hyperopt(hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._get_params_dict',
return_value={
'max_open_trades': -1
})
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
hyperopt.start()
out, err = capsys.readouterr()
assert '"max_open_trades":-1' in out
def test_max_open_trades_consistency(mocker, hyperopt_conf, tmpdir, fee) -> None:
# This test is to ensure that max_open_trades is the same across all functions needing it
# after it has been changed from the hyperopt
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
hyperopt_conf.update({
'strategy': 'HyperoptableStrategy',
'user_data_dir': Path(tmpdir),
'hyperopt_random_state': 42,
'spaces': ['trades'],
'stake_amount': 'unlimited',
'dry_run_wallet': 8,
'available_capital': 8,
'dry_run': True,
'epochs': 1
})
hyperopt = Hyperopt(hyperopt_conf)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
hyperopt.custom_hyperopt.max_open_trades_space = lambda: [
Integer(1, 10, name='max_open_trades')]
first_time_evaluated = False
def stake_amount_interceptor(func):
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal first_time_evaluated
stake_amount = func(*args, **kwargs)
if first_time_evaluated is False:
assert stake_amount == 1
first_time_evaluated = True
return stake_amount
return wrapper
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount = stake_amount_interceptor(
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount)
hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 8
assert hyperopt.config['max_open_trades'] == 8

View File

@ -66,52 +66,58 @@ def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
@pytest.mark.parametrize("spaces, expected_results", [ @pytest.mark.parametrize("spaces, expected_results", [
(['buy'], (['buy'],
{'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False, {'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['sell'], (['sell'],
{'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False, {'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['roi'], (['roi'],
{'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False, {'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['stoploss'], (['stoploss'],
{'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False, {'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['trailing'], (['trailing'],
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True, {'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True,
'protection': False}), 'protection': False, 'trades': False}),
(['buy', 'sell', 'roi', 'stoploss'], (['buy', 'sell', 'roi', 'stoploss'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['buy', 'sell', 'roi', 'stoploss', 'trailing'], (['buy', 'sell', 'roi', 'stoploss', 'trailing'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
'protection': False}), 'protection': False, 'trades': False}),
(['buy', 'roi'], (['buy', 'roi'],
{'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False, {'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['all'], (['all'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
'protection': True}), 'protection': True, 'trades': True}),
(['default'], (['default'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['default', 'trailing'], (['default', 'trailing'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
'protection': False}), 'protection': False, 'trades': False}),
(['all', 'buy'], (['all', 'buy'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
'protection': True}), 'protection': True, 'trades': True}),
(['default', 'buy'], (['default', 'buy'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
'protection': False}), 'protection': False, 'trades': False}),
(['all'], (['all'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
'protection': True}), 'protection': True, 'trades': True}),
(['protection'], (['protection'],
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False, {'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False,
'protection': True}), 'protection': True, 'trades': False}),
(['trades'],
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False,
'protection': False, 'trades': True}),
(['default', 'trades'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
'protection': False, 'trades': True}),
]) ])
def test_has_space(hyperopt_conf, spaces, expected_results): def test_has_space(hyperopt_conf, spaces, expected_results):
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection']: for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection', 'trades']:
hyperopt_conf.update({'spaces': spaces}) hyperopt_conf.update({'spaces': spaces})
assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s] assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
@ -193,6 +199,9 @@ def test_export_params(tmpdir):
"346": 0.08499, "346": 0.08499,
"507": 0.049, "507": 0.049,
"1595": 0 "1595": 0
},
"max_open_trades": {
"max_open_trades": 5
} }
}, },
"params_not_optimized": { "params_not_optimized": {
@ -219,6 +228,7 @@ def test_export_params(tmpdir):
assert "roi" in content["params"] assert "roi" in content["params"]
assert "stoploss" in content["params"] assert "stoploss" in content["params"]
assert "trailing" in content["params"] assert "trailing" in content["params"]
assert "max_open_trades" in content["params"]
def test_try_export_params(default_conf, tmpdir, caplog, mocker): def test_try_export_params(default_conf, tmpdir, caplog, mocker):
@ -297,6 +307,9 @@ def test_params_print(capsys):
"trailing_stop_positive_offset": 0.1, "trailing_stop_positive_offset": 0.1,
"trailing_only_offset_is_reached": True "trailing_only_offset_is_reached": True
}, },
"max_open_trades": {
"max_open_trades": 5
}
} }
HyperoptTools._params_pretty_print(params, 'buy', 'No header', non_optimized) HyperoptTools._params_pretty_print(params, 'buy', 'No header', non_optimized)
@ -327,6 +340,13 @@ def test_params_print(capsys):
assert re.search('trailing_stop_positive_offset = 0.1 # value loaded.*\n', captured.out) assert re.search('trailing_stop_positive_offset = 0.1 # value loaded.*\n', captured.out)
assert re.search('trailing_only_offset_is_reached = True # value loaded.*\n', captured.out) assert re.search('trailing_only_offset_is_reached = True # value loaded.*\n', captured.out)
HyperoptTools._params_pretty_print(
params, 'max_open_trades', "Max Open Trades:", non_optimized)
captured = capsys.readouterr()
assert re.search("# Max Open Trades:", captured.out)
assert re.search('max_open_trades = 5 # value loaded.*\n', captured.out)
def test_hyperopt_serializer(): def test_hyperopt_serializer():

View File

@ -30,6 +30,9 @@ class StrategyTestV3(IStrategy):
"0": 0.04 "0": 0.04
} }
# Optimal max_open_trades for the strategy
max_open_trades = -1
# Optimal stoploss designed for the strategy # Optimal stoploss designed for the strategy
stoploss = -0.10 stoploss = -0.10

View File

@ -6,6 +6,7 @@ from pathlib import Path
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import Configuration
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@ -175,6 +176,18 @@ def test_strategy_override_stoploss(caplog, default_conf):
assert log_has("Override strategy 'stoploss' with value in config file: -0.5.", caplog) assert log_has("Override strategy 'stoploss' with value in config file: -0.5.", caplog)
def test_strategy_override_max_open_trades(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
'max_open_trades': 7
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.max_open_trades == 7
assert log_has("Override strategy 'max_open_trades' with value in config file: 7.", caplog)
def test_strategy_override_trailing_stop(caplog, default_conf): def test_strategy_override_trailing_stop(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
@ -349,6 +362,38 @@ def test_strategy_override_use_exit_profit_only(caplog, default_conf):
assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog) assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog)
def test_strategy_max_open_trades_infinity_from_strategy(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
})
del default_conf['max_open_trades']
strategy = StrategyResolver.load_strategy(default_conf)
# this test assumes -1 set to 'max_open_trades' in CURRENT_TEST_STRATEGY
assert strategy.max_open_trades == float('inf')
assert default_conf['max_open_trades'] == float('inf')
def test_strategy_max_open_trades_infinity_from_config(caplog, default_conf, mocker):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
'max_open_trades': -1,
'exchange': 'binance'
})
configuration = Configuration(args=default_conf)
parsed_config = configuration.get_config()
assert parsed_config['max_open_trades'] == float('inf')
strategy = StrategyResolver.load_strategy(parsed_config)
assert strategy.max_open_trades == float('inf')
@ pytest.mark.filterwarnings("ignore:deprecated") @ pytest.mark.filterwarnings("ignore:deprecated")
def test_missing_implements(default_conf, caplog): def test_missing_implements(default_conf, caplog):
@ -438,3 +483,19 @@ def test_strategy_interface_versioning(dataframe_1m, default_conf):
assert isinstance(exitdf, DataFrame) assert isinstance(exitdf, DataFrame)
assert 'sell' not in exitdf assert 'sell' not in exitdf
assert 'exit_long' in exitdf assert 'exit_long' in exitdf
def test_strategy_ft_load_params_from_file(mocker, default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
del default_conf['max_open_trades']
mocker.patch('freqtrade.strategy.hyper.HyperStrategyMixin.load_params_from_file',
return_value={
'params': {
'max_open_trades': {
'max_open_trades': -1
}
}
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.max_open_trades == float('inf')
assert strategy.config['max_open_trades'] == float('inf')