Merge pull request #6656 from freqtrade/use_sell_signal
Use sell signal -> use_exit_signal
This commit is contained in:
commit
299dd84cfe
@ -15,10 +15,10 @@
|
||||
"trailing_stop_positive": 0.005,
|
||||
"trailing_stop_positive_offset": 0.0051,
|
||||
"trailing_only_offset_is_reached": false,
|
||||
"use_sell_signal": true,
|
||||
"sell_profit_only": false,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": false,
|
||||
"use_exit_signal": true,
|
||||
"exit_profit_only": false,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": false,
|
||||
"ignore_buying_expired_candle_after": 300,
|
||||
"trading_mode": "spot",
|
||||
"margin_mode": "",
|
||||
|
@ -116,10 +116,10 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
| `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled).
|
||||
| `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **Datatype:** Boolean
|
||||
| `exit_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Exit](#exit-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||
| `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
||||
| `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `use_exit_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||
| `exit_profit_only` | Wait until the bot reaches `exit_profit_offset` before taking an exit decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `exit_profit_offset` | Sell-signal is only active above this value. Only active in combination with `exit_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
||||
| `ignore_roi_if_entry_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_exit_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer
|
||||
| `order_types` | Configure order-types depending on the action (`"entry"`, `"exit"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
|
||||
| `order_time_in_force` | Configure time in force for entry and exit orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||
@ -198,10 +198,10 @@ Values set in the configuration file always overwrite values set in the strategy
|
||||
* `order_time_in_force`
|
||||
* `unfilledtimeout`
|
||||
* `disable_dataframe_checks`
|
||||
* `use_sell_signal`
|
||||
* `sell_profit_only`
|
||||
* `sell_profit_offset`
|
||||
* `ignore_roi_if_buy_signal`
|
||||
- `use_exit_signal`
|
||||
* `exit_profit_only`
|
||||
- `exit_profit_offset`
|
||||
- `ignore_roi_if_entry_signal`
|
||||
* `ignore_buying_expired_candle_after`
|
||||
* `position_adjustment_enable`
|
||||
* `max_entry_position_adjustment`
|
||||
|
@ -91,7 +91,7 @@ For example you could implement a 1:2 risk-reward ROI with `custom_exit()`.
|
||||
Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange.
|
||||
|
||||
!!! Note
|
||||
Returning a (none-empty) `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters.
|
||||
Returning a (none-empty) `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_exit_signal=False` or `exit_profit_only=True` while profit is below `exit_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters.
|
||||
|
||||
An example of how we can use different indicators depending on the current profit and also sell trades that were open longer than one day:
|
||||
|
||||
|
@ -264,7 +264,7 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
|
||||
### Exit signal rules
|
||||
|
||||
Edit the method `populate_exit_trend()` into your strategy file to update your sell strategy.
|
||||
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
|
||||
Please note that the exit-signal is only used if `use_exit_signal` is set to true in the configuration.
|
||||
|
||||
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
|
||||
|
||||
|
@ -61,8 +61,11 @@ You can use the quick summary as checklist. Please refer to the detailed section
|
||||
* `sell` -> `exit`
|
||||
* `sell_fill` -> `exit_fill`
|
||||
* `sell_cancel` -> `exit_cancel`
|
||||
|
||||
|
||||
* Strategy/config settings:
|
||||
* `use_sell_signal` -> `use_exit_signal`
|
||||
* `sell_profit_only` -> `exit_profit_only`
|
||||
* `sell_profit_offset` -> `exit_profit_offset`
|
||||
* `ignore_roi_if_buy_signal` -> `ignore_roi_if_entry_signal`
|
||||
|
||||
## Extensive explanation
|
||||
|
||||
@ -360,6 +363,31 @@ After:
|
||||
}
|
||||
```
|
||||
|
||||
#### Strategy level settings
|
||||
|
||||
* `use_sell_signal` -> `use_exit_signal`
|
||||
* `sell_profit_only` -> `exit_profit_only`
|
||||
* `sell_profit_offset` -> `exit_profit_offset`
|
||||
* `ignore_roi_if_buy_signal` -> `ignore_roi_if_entry_signal`
|
||||
|
||||
``` python hl_lines="2-5"
|
||||
# These values can be overridden in the config.
|
||||
use_sell_signal = True
|
||||
sell_profit_only = True
|
||||
sell_profit_offset: 0.01
|
||||
ignore_roi_if_buy_signal = False
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
``` python hl_lines="2-5"
|
||||
# These values can be overridden in the config.
|
||||
use_exit_signal = True
|
||||
exit_profit_only = True
|
||||
exit_profit_offset: 0.01
|
||||
ignore_roi_if_entry_signal = False
|
||||
```
|
||||
|
||||
#### `unfilledtimeout`
|
||||
|
||||
`unfilledtimeout` have changed all wordings from `buy` to `entry` - and `sell` to `exit`.
|
||||
|
@ -154,9 +154,9 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
|
||||
if not conf.get('edge', {}).get('enabled'):
|
||||
return
|
||||
|
||||
if not conf.get('use_sell_signal', True):
|
||||
if not conf.get('use_exit_signal', True):
|
||||
raise OperationalException(
|
||||
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
|
||||
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
|
||||
)
|
||||
|
||||
|
||||
@ -219,6 +219,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
||||
_validate_order_types(conf)
|
||||
_validate_unfilledtimeout(conf)
|
||||
_validate_pricing_rules(conf)
|
||||
_strategy_settings(conf)
|
||||
|
||||
|
||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
||||
@ -312,3 +313,12 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
||||
else:
|
||||
process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj)
|
||||
del conf['ask_strategy']
|
||||
|
||||
|
||||
def _strategy_settings(conf: Dict[str, Any]) -> None:
|
||||
|
||||
process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal')
|
||||
process_deprecated_setting(conf, None, 'sell_profit_only', None, 'exit_profit_only')
|
||||
process_deprecated_setting(conf, None, 'sell_profit_offset', None, 'exit_profit_offset')
|
||||
process_deprecated_setting(conf, None, 'ignore_roi_if_buy_signal',
|
||||
None, 'ignore_roi_if_entry_signal')
|
||||
|
@ -12,14 +12,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_conflicting_settings(config: Dict[str, Any],
|
||||
section_old: str, name_old: str,
|
||||
section_old: Optional[str], name_old: str,
|
||||
section_new: Optional[str], name_new: str) -> None:
|
||||
section_new_config = config.get(section_new, {}) if section_new else config
|
||||
section_old_config = config.get(section_old, {})
|
||||
section_old_config = config.get(section_old, {}) if section_old else config
|
||||
if name_new in section_new_config and name_old in section_old_config:
|
||||
new_name = f"{section_new}.{name_new}" if section_new else f"{name_new}"
|
||||
old_name = f"{section_old}.{name_old}" if section_old else f"{name_old}"
|
||||
raise OperationalException(
|
||||
f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` "
|
||||
f"Conflicting settings `{new_name}` and `{old_name}` "
|
||||
"(DEPRECATED) detected in the configuration file. "
|
||||
"This deprecated setting will be removed in the next versions of Freqtrade. "
|
||||
f"Please delete it from your configuration and use the `{new_name}` "
|
||||
@ -47,11 +48,11 @@ def process_removed_setting(config: Dict[str, Any],
|
||||
|
||||
|
||||
def process_deprecated_setting(config: Dict[str, Any],
|
||||
section_old: str, name_old: str,
|
||||
section_old: Optional[str], name_old: str,
|
||||
section_new: Optional[str], name_new: str
|
||||
) -> None:
|
||||
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
|
||||
section_old_config = config.get(section_old, {})
|
||||
section_old_config = config.get(section_old, {}) if section_old else config
|
||||
|
||||
if name_old in section_old_config:
|
||||
section_2 = f"{section_new}.{name_new}" if section_new else f"{name_new}"
|
||||
@ -72,14 +73,7 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
# Kept for future deprecated / moved settings
|
||||
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
|
||||
# 'experimental', 'use_sell_signal')
|
||||
process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
|
||||
None, 'use_sell_signal')
|
||||
process_deprecated_setting(config, 'ask_strategy', 'sell_profit_only',
|
||||
None, 'sell_profit_only')
|
||||
process_deprecated_setting(config, 'ask_strategy', 'sell_profit_offset',
|
||||
None, 'sell_profit_offset')
|
||||
process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal',
|
||||
None, 'ignore_roi_if_buy_signal')
|
||||
|
||||
process_deprecated_setting(config, 'ask_strategy', 'ignore_buying_expired_candle_after',
|
||||
None, 'ignore_buying_expired_candle_after')
|
||||
# New settings
|
||||
@ -109,13 +103,18 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
'webhook', 'webhookexitfill')
|
||||
|
||||
# Legacy way - having them in experimental ...
|
||||
process_removed_setting(config, 'experimental', 'use_sell_signal',
|
||||
None, 'use_sell_signal')
|
||||
process_removed_setting(config, 'experimental', 'sell_profit_only',
|
||||
None, 'sell_profit_only')
|
||||
process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
|
||||
None, 'ignore_roi_if_buy_signal')
|
||||
|
||||
process_removed_setting(config, 'experimental', 'use_sell_signal', None, 'use_exit_signal')
|
||||
process_removed_setting(config, 'experimental', 'sell_profit_only', None, 'exit_profit_only')
|
||||
process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
|
||||
None, 'ignore_roi_if_entry_signal')
|
||||
|
||||
process_removed_setting(config, 'ask_strategy', 'use_sell_signal', None, 'exit_sell_signal')
|
||||
process_removed_setting(config, 'ask_strategy', 'sell_profit_only', None, 'exit_profit_only')
|
||||
process_removed_setting(config, 'ask_strategy', 'sell_profit_offset',
|
||||
None, 'exit_profit_offset')
|
||||
process_removed_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal',
|
||||
None, 'ignore_roi_if_entry_signal')
|
||||
if (config.get('edge', {}).get('enabled', False)
|
||||
and 'capital_available_percentage' in config.get('edge', {})):
|
||||
raise OperationalException(
|
||||
|
@ -149,10 +149,10 @@ CONF_SCHEMA = {
|
||||
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||
'trailing_only_offset_is_reached': {'type': 'boolean'},
|
||||
'use_sell_signal': {'type': 'boolean'},
|
||||
'sell_profit_only': {'type': 'boolean'},
|
||||
'sell_profit_offset': {'type': 'number'},
|
||||
'ignore_roi_if_buy_signal': {'type': 'boolean'},
|
||||
'use_exit_signal': {'type': 'boolean'},
|
||||
'exit_profit_only': {'type': 'boolean'},
|
||||
'exit_profit_offset': {'type': 'number'},
|
||||
'ignore_roi_if_entry_signal': {'type': 'boolean'},
|
||||
'ignore_buying_expired_candle_after': {'type': 'number'},
|
||||
'trading_mode': {'type': 'string', 'enum': TRADING_MODES},
|
||||
'margin_mode': {'type': 'string', 'enum': MARGIN_MODES},
|
||||
|
@ -926,8 +926,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
exit_tag = None
|
||||
exit_signal_type = "exit_short" if trade.is_short else "exit_long"
|
||||
|
||||
if (self.config.get('use_sell_signal', True) or
|
||||
self.config.get('ignore_roi_if_buy_signal', False)):
|
||||
if (self.config.get('use_exit_signal', True) or
|
||||
self.config.get('ignore_roi_if_entry_signal', False)):
|
||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||
self.strategy.timeframe)
|
||||
|
||||
|
@ -114,8 +114,8 @@ class Hyperopt:
|
||||
self.position_stacking = self.config.get('position_stacking', False)
|
||||
|
||||
if HyperoptTools.has_space(self.config, 'sell'):
|
||||
# Make sure use_sell_signal is enabled
|
||||
self.config['use_sell_signal'] = True
|
||||
# Make sure use_exit_signal is enabled
|
||||
self.config['use_exit_signal'] = True
|
||||
|
||||
self.print_all = self.config.get('print_all', False)
|
||||
self.hyperopt_table_header = 0
|
||||
|
@ -460,10 +460,10 @@ def generate_strategy_stats(pairlist: List[str],
|
||||
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False),
|
||||
'use_custom_stoploss': config.get('use_custom_stoploss', False),
|
||||
'minimal_roi': config['minimal_roi'],
|
||||
'use_sell_signal': config['use_sell_signal'],
|
||||
'sell_profit_only': config['sell_profit_only'],
|
||||
'sell_profit_offset': config['sell_profit_offset'],
|
||||
'ignore_roi_if_buy_signal': config['ignore_roi_if_buy_signal'],
|
||||
'use_exit_signal': config['use_exit_signal'],
|
||||
'exit_profit_only': config['exit_profit_only'],
|
||||
'exit_profit_offset': config['exit_profit_offset'],
|
||||
'ignore_roi_if_entry_signal': config['ignore_roi_if_entry_signal'],
|
||||
**daily_stats,
|
||||
**trade_stats
|
||||
}
|
||||
|
@ -85,10 +85,10 @@ class StrategyResolver(IResolver):
|
||||
("protections", None),
|
||||
("startup_candle_count", None),
|
||||
("unfilledtimeout", None),
|
||||
("use_sell_signal", True),
|
||||
("sell_profit_only", False),
|
||||
("ignore_roi_if_buy_signal", False),
|
||||
("sell_profit_offset", 0.0),
|
||||
("use_exit_signal", True),
|
||||
("exit_profit_only", False),
|
||||
("ignore_roi_if_entry_signal", False),
|
||||
("exit_profit_offset", 0.0),
|
||||
("disable_dataframe_checks", False),
|
||||
("ignore_buying_expired_candle_after", 0),
|
||||
("position_adjustment_enable", False),
|
||||
@ -173,6 +173,12 @@ class StrategyResolver(IResolver):
|
||||
def validate_strategy(strategy: IStrategy) -> IStrategy:
|
||||
if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||
# Require new method
|
||||
warn_deprecated_setting(strategy, 'sell_profit_only', 'exit_profit_only', True)
|
||||
warn_deprecated_setting(strategy, 'sell_profit_offset', 'exit_profit_offset', True)
|
||||
warn_deprecated_setting(strategy, 'use_sell_signal', 'use_exit_signal', True)
|
||||
warn_deprecated_setting(strategy, 'ignore_roi_if_buy_signal',
|
||||
'ignore_roi_if_entry_signal', True)
|
||||
|
||||
if not check_override(strategy, IStrategy, 'populate_entry_trend'):
|
||||
raise OperationalException("`populate_entry_trend` must be implemented.")
|
||||
if not check_override(strategy, IStrategy, 'populate_exit_trend'):
|
||||
@ -187,9 +193,16 @@ class StrategyResolver(IResolver):
|
||||
if check_override(strategy, IStrategy, 'custom_sell'):
|
||||
raise OperationalException(
|
||||
"Please migrate your implementation of `custom_sell` to `custom_exit`.")
|
||||
|
||||
else:
|
||||
# TODO: Implementing one of the following methods should show a deprecation warning
|
||||
# buy_trend and sell_trend, custom_sell
|
||||
warn_deprecated_setting(strategy, 'sell_profit_only', 'exit_profit_only')
|
||||
warn_deprecated_setting(strategy, 'sell_profit_offset', 'exit_profit_offset')
|
||||
warn_deprecated_setting(strategy, 'use_sell_signal', 'use_exit_signal')
|
||||
warn_deprecated_setting(strategy, 'ignore_roi_if_buy_signal',
|
||||
'ignore_roi_if_entry_signal')
|
||||
|
||||
if (
|
||||
not check_override(strategy, IStrategy, 'populate_buy_trend')
|
||||
and not check_override(strategy, IStrategy, 'populate_entry_trend')
|
||||
@ -262,6 +275,15 @@ class StrategyResolver(IResolver):
|
||||
)
|
||||
|
||||
|
||||
def warn_deprecated_setting(strategy: IStrategy, old: str, new: str, error=False):
|
||||
if hasattr(strategy, old):
|
||||
errormsg = f"DEPRECATED: Using '{old}' moved to '{new}'."
|
||||
if error:
|
||||
raise OperationalException(errormsg)
|
||||
logger.warning(errormsg)
|
||||
setattr(strategy, new, getattr(strategy, f'{old}'))
|
||||
|
||||
|
||||
def check_override(object, parentclass, attribute):
|
||||
"""
|
||||
Checks if a object overrides the parent class attribute.
|
||||
|
@ -90,10 +90,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles: bool = False
|
||||
|
||||
use_sell_signal: bool
|
||||
sell_profit_only: bool
|
||||
sell_profit_offset: float
|
||||
ignore_roi_if_buy_signal: bool
|
||||
use_exit_signal: bool
|
||||
exit_profit_only: bool
|
||||
exit_profit_offset: float
|
||||
ignore_roi_if_entry_signal: bool
|
||||
|
||||
# Position adjustment is disabled by default
|
||||
position_adjustment_enable: bool = False
|
||||
@ -871,7 +871,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
# if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
|
||||
roi_reached = (not (enter and self.ignore_roi_if_buy_signal)
|
||||
roi_reached = (not (enter and self.ignore_roi_if_entry_signal)
|
||||
and self.min_roi_reached(trade=trade, current_profit=current_profit,
|
||||
current_time=current_time))
|
||||
|
||||
@ -881,10 +881,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_rate = rate
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
if (self.sell_profit_only and current_profit <= self.sell_profit_offset):
|
||||
# sell_profit_only and profit doesn't reach the offset - ignore sell signal
|
||||
if (self.exit_profit_only and current_profit <= self.exit_profit_offset):
|
||||
# exit_profit_only and profit doesn't reach the offset - ignore sell signal
|
||||
pass
|
||||
elif self.use_sell_signal and not enter:
|
||||
elif self.use_exit_signal and not enter:
|
||||
if exit_:
|
||||
exit_signal = ExitType.EXIT_SIGNAL
|
||||
else:
|
||||
|
@ -65,9 +65,9 @@ class {{ strategy }}(IStrategy):
|
||||
process_only_new_candles = False
|
||||
|
||||
# These values can be overridden in the config.
|
||||
use_sell_signal = True
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
use_exit_signal = True
|
||||
exit_profit_only = False
|
||||
ignore_roi_if_entry_signal = False
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
@ -65,9 +65,9 @@ class SampleStrategy(IStrategy):
|
||||
process_only_new_candles = False
|
||||
|
||||
# These values can be overridden in the config.
|
||||
use_sell_signal = True
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
use_exit_signal = True
|
||||
exit_profit_only = False
|
||||
ignore_roi_if_entry_signal = False
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
|
@ -821,7 +821,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
|
||||
if data.trailing_stop_positive is not None:
|
||||
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
||||
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
||||
default_conf["use_sell_signal"] = data.use_exit_signal
|
||||
default_conf["use_exit_signal"] = data.use_exit_signal
|
||||
|
||||
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)
|
||||
|
@ -504,7 +504,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
||||
|
||||
|
||||
def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -563,7 +563,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
|
||||
|
||||
def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
|
||||
default_conf_usdt['use_sell_signal'] = False
|
||||
default_conf_usdt['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -645,7 +645,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
|
||||
|
||||
|
||||
def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -740,7 +740,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||
|
||||
|
||||
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -807,7 +807,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
|
||||
|
||||
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -833,7 +833,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
||||
|
||||
|
||||
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
@ -878,7 +878,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
|
||||
|
||||
|
||||
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=100000)
|
||||
@ -1151,10 +1151,10 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
|
||||
default_conf.update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
backtestmock = MagicMock(return_value={
|
||||
@ -1228,10 +1228,10 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
|
||||
default_conf.update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||
@ -1346,10 +1346,10 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
|
||||
default_conf_usdt.update({
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
"strategy": CURRENT_TEST_STRATEGY,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
@ -1450,10 +1450,10 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
||||
caplog, testdatadir, capsys):
|
||||
# Tests detail-data loading
|
||||
default_conf.update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||
@ -1557,10 +1557,10 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
||||
def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testdatadir, run_id,
|
||||
start_delta, cache):
|
||||
default_conf.update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
backtestmock = MagicMock(return_value={
|
||||
|
@ -14,7 +14,7 @@ from tests.conftest import patch_exchange
|
||||
|
||||
|
||||
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
default_conf['use_exit_signal'] = False
|
||||
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_max_pair_stake_amount", return_value=float('inf'))
|
||||
|
@ -50,6 +50,8 @@ class StrategyTestV2(IStrategy):
|
||||
'entry': 'gtc',
|
||||
'exit': 'gtc',
|
||||
}
|
||||
# Test legacy use_sell_signal definition
|
||||
use_sell_signal = False
|
||||
|
||||
# By default this strategy does not use Position Adjustments
|
||||
position_adjustment_enable = False
|
||||
|
@ -143,16 +143,6 @@ def test_strategy_can_short(caplog, default_conf):
|
||||
assert isinstance(strat, IStrategy)
|
||||
|
||||
|
||||
def test_strategy_implements_populate_entry(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': "StrategyTestV2",
|
||||
})
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
with pytest.raises(OperationalException, match="`populate_entry_trend` must be implemented."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
|
||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
@ -310,50 +300,50 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
|
||||
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||
def test_strategy_override_use_exit_signal(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert strategy.use_sell_signal
|
||||
assert isinstance(strategy.use_sell_signal, bool)
|
||||
assert strategy.use_exit_signal
|
||||
assert isinstance(strategy.use_exit_signal, bool)
|
||||
# must be inserted to configuration
|
||||
assert 'use_sell_signal' in default_conf
|
||||
assert default_conf['use_sell_signal']
|
||||
assert 'use_exit_signal' in default_conf
|
||||
assert default_conf['use_exit_signal']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'use_sell_signal': False,
|
||||
'use_exit_signal': False,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
assert not strategy.use_sell_signal
|
||||
assert isinstance(strategy.use_sell_signal, bool)
|
||||
assert log_has("Override strategy 'use_sell_signal' with value in config file: False.", caplog)
|
||||
assert not strategy.use_exit_signal
|
||||
assert isinstance(strategy.use_exit_signal, bool)
|
||||
assert log_has("Override strategy 'use_exit_signal' with value in config file: False.", caplog)
|
||||
|
||||
|
||||
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
def test_strategy_override_use_exit_profit_only(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert not strategy.sell_profit_only
|
||||
assert isinstance(strategy.sell_profit_only, bool)
|
||||
assert not strategy.exit_profit_only
|
||||
assert isinstance(strategy.exit_profit_only, bool)
|
||||
# must be inserted to configuration
|
||||
assert 'sell_profit_only' in default_conf
|
||||
assert not default_conf['sell_profit_only']
|
||||
assert 'exit_profit_only' in default_conf
|
||||
assert not default_conf['exit_profit_only']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'sell_profit_only': True,
|
||||
'exit_profit_only': True,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
assert strategy.sell_profit_only
|
||||
assert isinstance(strategy.sell_profit_only, bool)
|
||||
assert log_has("Override strategy 'sell_profit_only' with value in config file: True.", caplog)
|
||||
assert strategy.exit_profit_only
|
||||
assert isinstance(strategy.exit_profit_only, bool)
|
||||
assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
@ -391,7 +381,22 @@ def test_deprecate_populate_indicators(result, default_conf):
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_missing_implements(default_conf):
|
||||
def test_missing_implements(default_conf, caplog):
|
||||
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
default_conf.update({'strategy': 'StrategyTestV2',
|
||||
'strategy_path': default_location})
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
log_has_re(r"DEPRECATED: .*use_sell_signal.*use_exit_signal.", caplog)
|
||||
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"DEPRECATED: .*use_sell_signal.*use_exit_signal."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
default_conf['trading_mode'] = 'spot'
|
||||
|
||||
default_location = Path(__file__).parent / "strats/broken_strats"
|
||||
default_conf.update({'strategy': 'TestStrategyNoImplements',
|
||||
'strategy_path': default_location})
|
||||
|
@ -868,15 +868,15 @@ def test_validate_tsl(default_conf):
|
||||
|
||||
def test_validate_edge2(edge_conf):
|
||||
edge_conf.update({
|
||||
"use_sell_signal": True,
|
||||
"use_exit_signal": True,
|
||||
})
|
||||
# Passes test
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
edge_conf.update({
|
||||
"use_sell_signal": False,
|
||||
"use_exit_signal": False,
|
||||
})
|
||||
with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, "
|
||||
with pytest.raises(OperationalException, match="Edge requires `use_exit_signal` to be True, "
|
||||
"otherwise no sells will happen."):
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
@ -1238,14 +1238,8 @@ def test_pairlist_resolving_fallback(mocker):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("setting", [
|
||||
("ask_strategy", "use_sell_signal", True,
|
||||
None, "use_sell_signal", False),
|
||||
("ask_strategy", "sell_profit_only", True,
|
||||
None, "sell_profit_only", False),
|
||||
("ask_strategy", "sell_profit_offset", 0.1,
|
||||
None, "sell_profit_offset", 0.01),
|
||||
("ask_strategy", "ignore_roi_if_buy_signal", True,
|
||||
None, "ignore_roi_if_buy_signal", False),
|
||||
("webhook", "webhookbuy", 'testWEbhook',
|
||||
"webhook", "webhookentry", 'testWEbhook'),
|
||||
("ask_strategy", "ignore_buying_expired_candle_after", 5,
|
||||
None, "ignore_buying_expired_candle_after", 6),
|
||||
])
|
||||
|
@ -2273,14 +2273,14 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_trade_use_sell_signal(
|
||||
def test_handle_trade_use_exit_signal(
|
||||
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
|
||||
) -> None:
|
||||
|
||||
enter_open_order = limit_order_open[exit_side(is_short)]
|
||||
exit_open_order = limit_order_open[entry_side(is_short)]
|
||||
|
||||
# use_sell_signal is True buy default
|
||||
# use_exit_signal is True buy default
|
||||
caplog.set_level(logging.DEBUG)
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -3637,7 +3637,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
|
||||
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, False),
|
||||
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, True),
|
||||
])
|
||||
def test_sell_profit_only(
|
||||
def test_exit_profit_only(
|
||||
default_conf_usdt, limit_order, limit_order_open, is_short,
|
||||
fee, mocker, profit_only, bid, ask, handle_first, handle_second, exit_type) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
@ -3657,9 +3657,9 @@ def test_sell_profit_only(
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf_usdt.update({
|
||||
'use_sell_signal': True,
|
||||
'sell_profit_only': profit_only,
|
||||
'sell_profit_offset': 0.1,
|
||||
'use_exit_signal': True,
|
||||
'exit_profit_only': profit_only,
|
||||
'exit_profit_offset': 0.1,
|
||||
})
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
@ -3679,7 +3679,7 @@ def test_sell_profit_only(
|
||||
assert freqtrade.handle_trade(trade) is handle_first
|
||||
|
||||
if handle_second:
|
||||
freqtrade.strategy.sell_profit_offset = 0.0
|
||||
freqtrade.strategy.exit_profit_offset = 0.0
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
@ -3799,8 +3799,8 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
|
||||
fee, mocker) -> None:
|
||||
def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
|
||||
fee, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
eside = entry_side(is_short)
|
||||
@ -3817,7 +3817,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_op
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf_usdt['ignore_roi_if_buy_signal'] = True
|
||||
default_conf_usdt['ignore_roi_if_entry_signal'] = True
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
@ -4016,8 +4016,8 @@ def test_trailing_stop_loss_positive(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open,
|
||||
is_short, fee, mocker) -> None:
|
||||
def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_open,
|
||||
is_short, fee, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
eside = entry_side(is_short)
|
||||
@ -4037,7 +4037,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
)
|
||||
default_conf_usdt['exit_pricing'] = {
|
||||
'ignore_roi_if_buy_signal': False
|
||||
'ignore_roi_if_entry_signal': False
|
||||
}
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
10
tests/testdata/strategy_SampleStrategy.fthypt
vendored
10
tests/testdata/strategy_SampleStrategy.fthypt
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user