commit
7d1e487be5
@ -127,10 +127,9 @@ class Backtesting:
|
||||
self.config['startup_candle_count'] = self.required_startup
|
||||
self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe)
|
||||
|
||||
# TODO-lev: This should come from the configuration setting or better a
|
||||
# TODO-lev: combination of config/strategy "use_shorts"(?) and "can_short" from the exchange
|
||||
self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
||||
self.margin_mode: MarginMode = config.get('margin_mode', MarginMode.NONE)
|
||||
# strategies which define "can_short=True" will fail to load in Spot mode.
|
||||
self._can_short = self.trading_mode != TradingMode.SPOT
|
||||
|
||||
self.progress = BTProgress()
|
||||
|
@ -12,6 +12,7 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from freqtrade.configuration.config_validation import validate_migrated_strategy_settings
|
||||
from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.resolvers import IResolver
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
@ -160,7 +161,7 @@ class StrategyResolver(IResolver):
|
||||
return strategy
|
||||
|
||||
@staticmethod
|
||||
def _strategy_sanity_validations(strategy):
|
||||
def _strategy_sanity_validations(strategy: IStrategy):
|
||||
# Ensure necessary migrations are performed first.
|
||||
validate_migrated_strategy_settings(strategy.config)
|
||||
|
||||
@ -170,6 +171,15 @@ class StrategyResolver(IResolver):
|
||||
if not all(k in strategy.order_time_in_force for k in REQUIRED_ORDERTIF):
|
||||
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
|
||||
f"Order-time-in-force mapping is incomplete.")
|
||||
trading_mode = strategy.config.get('trading_mode', TradingMode.SPOT)
|
||||
|
||||
if (strategy.can_short and trading_mode == TradingMode.SPOT):
|
||||
raise ImportError(
|
||||
"Short strategies cannot run in spot markets. Please make sure that this "
|
||||
"is the correct strategy and that your trading mode configuration is correct. "
|
||||
"You can run this strategy in spot markets by setting `can_short=False`"
|
||||
" in your strategy. Please note that short signals will be ignored in that case."
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _load_strategy(strategy_name: str,
|
||||
|
@ -81,6 +81,9 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
trailing_only_offset_is_reached = False
|
||||
use_custom_stoploss: bool = False
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# associated timeframe
|
||||
ticker_interval: str # DEPRECATED
|
||||
timeframe: str
|
||||
@ -766,6 +769,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
enter_signal = SignalDirection.LONG
|
||||
enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
|
||||
if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT
|
||||
and self.can_short
|
||||
and enter_short == 1 and not any([exit_short, enter_long])):
|
||||
enter_signal = SignalDirection.SHORT
|
||||
enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
|
||||
|
@ -40,6 +40,9 @@ class {{ strategy }}(IStrategy):
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
|
@ -38,6 +38,9 @@ class SampleShortStrategy(IStrategy):
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 2
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = True
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
|
@ -37,6 +37,9 @@ class SampleStrategy(IStrategy):
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 2
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
@ -55,12 +58,6 @@ class SampleStrategy(IStrategy):
|
||||
# trailing_stop_positive = 0.01
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
@ -72,6 +69,12 @@ class SampleStrategy(IStrategy):
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
||||
|
@ -1382,6 +1382,7 @@ def test_api_strategies(botclient):
|
||||
'InformativeDecoratorTest',
|
||||
'StrategyTestV2',
|
||||
'StrategyTestV3',
|
||||
'StrategyTestV3Futures',
|
||||
'TestStrategyLegacyV1',
|
||||
]}
|
||||
|
||||
|
@ -187,3 +187,7 @@ class StrategyTestV3(IStrategy):
|
||||
return round(orders[0].cost, 0)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class StrategyTestV3Futures(StrategyTestV3):
|
||||
can_short = True
|
||||
|
@ -78,6 +78,11 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
|
||||
|
||||
_STRATEGY.config['trading_mode'] = 'futures'
|
||||
# Short signal get's ignored as can_short is not set.
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
|
||||
|
||||
_STRATEGY.can_short = True
|
||||
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
|
||||
@ -93,6 +98,7 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
assert _STRATEGY.get_exit_signal(
|
||||
'ETH/BTC', '5m', mocked_history, True) == (False, True, 'sell_signal_02')
|
||||
|
||||
_STRATEGY.can_short = False
|
||||
_STRATEGY.config['trading_mode'] = 'spot'
|
||||
|
||||
|
||||
|
@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 5
|
||||
assert len(strategies) == 6
|
||||
assert isinstance(strategies[0], dict)
|
||||
|
||||
|
||||
@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 6
|
||||
assert len(strategies) == 7
|
||||
# with enum_failed=True search_all_objects() shall find 2 good strategies
|
||||
# and 1 which fails to load
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 5
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 6
|
||||
assert len([x for x in strategies if x['class'] is None]) == 1
|
||||
|
||||
|
||||
@ -128,6 +128,22 @@ def test_strategy_pre_v3(result, default_conf, strategy_name):
|
||||
assert 'exit_long' in dataframe.columns
|
||||
|
||||
|
||||
def test_strategy_can_short(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
})
|
||||
strat = StrategyResolver.load_strategy(default_conf)
|
||||
assert isinstance(strat, IStrategy)
|
||||
default_conf['strategy'] = 'StrategyTestV3Futures'
|
||||
with pytest.raises(ImportError, match=""):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
strat = StrategyResolver.load_strategy(default_conf)
|
||||
assert isinstance(strat, IStrategy)
|
||||
|
||||
|
||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
|
Loading…
Reference in New Issue
Block a user