Changed max_open_trades type to int or inf

This commit is contained in:
Antonio Della Fortuna 2023-01-15 11:44:10 +01:00
parent 192f75254f
commit b0f1d914c8
11 changed files with 97 additions and 30 deletions

View File

@ -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

@ -1237,8 +1237,8 @@ class Backtesting:
if not self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
logger.info( logger.info(
'Ignoring max_open_trades (--disable-max-market-positions was used) ...') 'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
self.strategy.max_open_trades = -1 self.strategy.max_open_trades = float('inf')
self.config.update({'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)

View File

@ -120,8 +120,8 @@ class Hyperopt:
# 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 not self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
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.backtesting.strategy.max_open_trades = -1 self.backtesting.strategy.max_open_trades = float('inf')
config.update({'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
@ -211,7 +211,8 @@ class Hyperopt:
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'): if HyperoptTools.has_space(self.config, 'trades'):
result['max_open_trades'] = { result['max_open_trades'] = {
'max_open_trades': self.backtesting.strategy.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
@ -344,16 +345,13 @@ class Hyperopt:
# Ignore unlimited max open trades if stake amount is unlimited # Ignore unlimited max open trades if stake amount is unlimited
params_dict.update({'max_open_trades': self.config['max_open_trades']}) params_dict.update({'max_open_trades': self.config['max_open_trades']})
updated_config_max_open_trades = int(params_dict['max_open_trades']) \ updated_max_open_trades = int(params_dict['max_open_trades']) \
if (params_dict['max_open_trades'] != -1 if (params_dict['max_open_trades'] != -1
and params_dict['max_open_trades'] != 0) else float('inf') and params_dict['max_open_trades'] != 0) else float('inf')
updated_strategy_max_open_trades = int(updated_config_max_open_trades) \ self.config.update({'max_open_trades': updated_max_open_trades})
if updated_config_max_open_trades != float('inf') else -1
self.config.update({'max_open_trades': updated_config_max_open_trades}) self.backtesting.strategy.max_open_trades = updated_max_open_trades
self.backtesting.strategy.max_open_trades = updated_strategy_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')

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

@ -104,11 +104,7 @@ class StrategyResolver(IResolver):
if (attribute in config if (attribute in config
and not isinstance(getattr(type(strategy), attribute, None), property)): and not isinstance(getattr(type(strategy), attribute, None), property)):
# Ensure Properties are not overwritten # Ensure Properties are not overwritten
val = config[attribute] setattr(strategy, attribute, config[attribute])
# max_open_trades set to float('inf') in the config will be copied as -1 in the strategy
if attribute == 'max_open_trades' and val == float('inf'):
val = -1
setattr(strategy, attribute, val)
logger.info("Override strategy '%s' with value in config file: %s.", logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute]) attribute, config[attribute])
elif hasattr(strategy, attribute): elif hasattr(strategy, attribute):
@ -137,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

@ -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)
@ -55,7 +55,7 @@ class IStrategy(ABC, HyperStrategyMixin):
stoploss: float stoploss: float
# max open trades for the strategy # max open trades for the strategy
max_open_trades: int max_open_trades: IntOrInf
# trailing stoploss # trailing stoploss
trailing_stop: bool = False trailing_stop: bool = False

View File

@ -1020,6 +1020,52 @@ def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmpdir, f
assert hyperopt.backtesting.strategy.max_open_trades == 1 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: 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 # 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 # after it has been changed from the hyperopt

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.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
@ -371,20 +372,26 @@ def test_strategy_max_open_trades_infinity_from_strategy(caplog, default_conf):
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# this test assumes -1 set to 'max_open_trades' in CURRENT_TEST_STRATEGY # this test assumes -1 set to 'max_open_trades' in CURRENT_TEST_STRATEGY
assert strategy.max_open_trades == -1 assert strategy.max_open_trades == float('inf')
assert default_conf['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): def test_strategy_max_open_trades_infinity_from_config(caplog, default_conf, mocker):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'max_open_trades': float('inf') 'max_open_trades': -1,
'exchange': 'binance'
}) })
strategy = StrategyResolver.load_strategy(default_conf) configuration = Configuration(args=default_conf)
parsed_config = configuration.get_config()
assert strategy.max_open_trades == -1 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")
@ -476,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')