Merge pull request #5188 from freqtrade/move_config_settings

Move ask_strategy config settings to root level
This commit is contained in:
Matthias 2021-06-27 11:30:50 +02:00 committed by GitHub
commit ab07fb5b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 182 additions and 171 deletions

View File

@ -22,10 +22,7 @@
}, },
"ask_strategy": { "ask_strategy": {
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
}, },
"exchange": { "exchange": {
"name": "binance", "name": "binance",

View File

@ -22,10 +22,7 @@
}, },
"ask_strategy":{ "ask_strategy":{
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
}, },
"exchange": { "exchange": {
"name": "bittrex", "name": "bittrex",

View File

@ -22,10 +22,7 @@
}, },
"ask_strategy": { "ask_strategy": {
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
}, },
"exchange": { "exchange": {
"name": "ftx", "name": "ftx",

View File

@ -14,6 +14,10 @@
"trailing_stop_positive": 0.005, "trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051, "trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false, "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,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,
@ -39,11 +43,7 @@
"ask_strategy":{ "ask_strategy":{
"price_side": "ask", "price_side": "ask",
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1
"use_sell_signal": true,
"sell_profit_only": false,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": false
}, },
"order_types": { "order_types": {
"buy": "limit", "buy": "limit",

View File

@ -22,10 +22,7 @@
}, },
"ask_strategy":{ "ask_strategy":{
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
}, },
"exchange": { "exchange": {
"name": "kraken", "name": "kraken",

View File

@ -81,11 +81,11 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled). | `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled).
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean | `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
| `ask_strategy.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 Asks](#sell-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer | `ask_strategy.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 Asks](#sell-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
| `ask_strategy.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 | `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
| `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <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
| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio) | `sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
| `ask_strategy.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 | `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
| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer | `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 (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"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 buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
@ -156,11 +156,11 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force` * `order_time_in_force`
* `unfilledtimeout` * `unfilledtimeout`
* `disable_dataframe_checks` * `disable_dataframe_checks`
* `use_sell_signal` (ask_strategy) * `use_sell_signal`
* `sell_profit_only` (ask_strategy) * `sell_profit_only`
* `sell_profit_offset` (ask_strategy) * `sell_profit_offset`
* `ignore_roi_if_buy_signal` (ask_strategy) * `ignore_roi_if_buy_signal`
* `ignore_buying_expired_candle_after` (ask_strategy) * `ignore_buying_expired_candle_after`
### Configuring amount per trade ### Configuring amount per trade
@ -291,16 +291,16 @@ See [the telegram documentation](telegram-usage.md) for details on usage.
When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it.
In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired. In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired.
For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy: For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy:
``` json ``` json
"ask_strategy":{ {
//...
"ignore_buying_expired_candle_after": 300, "ignore_buying_expired_candle_after": 300,
"price_side": "bid",
// ... // ...
}, }
``` ```
!!! Note !!! Note

View File

@ -150,7 +150,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
if not conf.get('edge', {}).get('enabled'): if not conf.get('edge', {}).get('enabled'):
return return
if not conf.get('ask_strategy', {}).get('use_sell_signal', True): if not conf.get('use_sell_signal', True):
raise OperationalException( raise OperationalException(
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen." "Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
) )

View File

@ -3,7 +3,7 @@ Functions to handle deprecated settings
""" """
import logging import logging
from typing import Any, Dict from typing import Any, Dict, Optional
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -12,23 +12,24 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(config: Dict[str, Any], def check_conflicting_settings(config: Dict[str, Any],
section1: str, name1: str, section_old: str, name_old: str,
section2: str, name2: str) -> None: section_new: Optional[str], name_new: str) -> None:
section1_config = config.get(section1, {}) section_new_config = config.get(section_new, {}) if section_new else config
section2_config = config.get(section2, {}) section_old_config = config.get(section_old, {})
if name1 in section1_config and name2 in section2_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}"
raise OperationalException( raise OperationalException(
f"Conflicting settings `{section1}.{name1}` and `{section2}.{name2}` " f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` "
"(DEPRECATED) detected in the configuration file. " "(DEPRECATED) detected in the configuration file. "
"This deprecated setting will be removed in the next versions of Freqtrade. " "This deprecated setting will be removed in the next versions of Freqtrade. "
f"Please delete it from your configuration and use the `{section1}.{name1}` " f"Please delete it from your configuration and use the `{new_name}` "
"setting instead." "setting instead."
) )
def process_removed_setting(config: Dict[str, Any], def process_removed_setting(config: Dict[str, Any],
section1: str, name1: str, section1: str, name1: str,
section2: str, name2: str) -> None: section2: Optional[str], name2: str) -> None:
""" """
:param section1: Removed section :param section1: Removed section
:param name1: Removed setting name :param name1: Removed setting name
@ -37,27 +38,32 @@ def process_removed_setting(config: Dict[str, Any],
""" """
section1_config = config.get(section1, {}) section1_config = config.get(section1, {})
if name1 in section1_config: if name1 in section1_config:
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
raise OperationalException( raise OperationalException(
f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. " f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
f"Please delete it from your configuration and use the `{section2}.{name2}` " f"Please delete it from your configuration and use the `{section_2}` "
"setting instead." "setting instead."
) )
def process_deprecated_setting(config: Dict[str, Any], def process_deprecated_setting(config: Dict[str, Any],
section1: str, name1: str, section_old: str, name_old: str,
section2: str, name2: str) -> None: section_new: Optional[str], name_new: str
section2_config = config.get(section2, {}) ) -> None:
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
section_old_config = config.get(section_old, {})
if name2 in section2_config: if name_old in section_old_config:
section_2 = f"{section_new}.{name_new}" if section_new else f"{name_new}"
logger.warning( logger.warning(
"DEPRECATED: " "DEPRECATED: "
f"The `{section2}.{name2}` setting is deprecated and " f"The `{section_old}.{name_old}` setting is deprecated and "
"will be removed in the next versions of Freqtrade. " "will be removed in the next versions of Freqtrade. "
f"Please use the `{section1}.{name1}` setting in your configuration instead." f"Please use the `{section_2}` setting in your configuration instead."
) )
section1_config = config.get(section1, {})
section1_config[name1] = section2_config[name2] section_new_config = config.get(section_new, {}) if section_new else config
section_new_config[name_new] = section_old_config[name_old]
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
@ -65,15 +71,24 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
# Kept for future deprecated / moved settings # Kept for future deprecated / moved settings
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
# 'experimental', 'use_sell_signal') # 'experimental', 'use_sell_signal')
# process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
# 'experimental', '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')
# Legacy way - having them in experimental ...
process_removed_setting(config, 'experimental', 'use_sell_signal', process_removed_setting(config, 'experimental', 'use_sell_signal',
'ask_strategy', 'use_sell_signal') None, 'use_sell_signal')
process_removed_setting(config, 'experimental', 'sell_profit_only', process_removed_setting(config, 'experimental', 'sell_profit_only',
'ask_strategy', 'sell_profit_only') None, 'sell_profit_only')
process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal', process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
'ask_strategy', 'ignore_roi_if_buy_signal') None, 'ignore_roi_if_buy_signal')
if (config.get('edge', {}).get('enabled', False) if (config.get('edge', {}).get('enabled', False)
and 'capital_available_percentage' in config.get('edge', {})): and 'capital_available_percentage' in config.get('edge', {})):

View File

@ -134,6 +134,11 @@ CONF_SCHEMA = {
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_only_offset_is_reached': {'type': 'boolean'}, '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'},
'ignore_buying_expired_candle_after': {'type': 'number'},
'bot_name': {'type': 'string'}, 'bot_name': {'type': 'string'},
'unfilledtimeout': { 'unfilledtimeout': {
'type': 'object', 'type': 'object',
@ -177,10 +182,6 @@ CONF_SCHEMA = {
}, },
'use_order_book': {'type': 'boolean'}, 'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, }, 'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'},
'sell_profit_offset': {'type': 'number'},
'ignore_roi_if_buy_signal': {'type': 'boolean'}
}, },
'required': ['price_side'] 'required': ['price_side']
}, },

View File

@ -684,10 +684,8 @@ class FreqtradeBot(LoggingMixin):
(buy, sell) = (False, False) (buy, sell) = (False, False)
config_ask_strategy = self.config.get('ask_strategy', {}) if (self.config.get('use_sell_signal', True) or
self.config.get('ignore_roi_if_buy_signal', False)):
if (config_ask_strategy.get('use_sell_signal', True) or
config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
self.strategy.timeframe) self.strategy.timeframe)

View File

@ -378,10 +378,10 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False),
'use_custom_stoploss': config.get('use_custom_stoploss', False), 'use_custom_stoploss': config.get('use_custom_stoploss', False),
'minimal_roi': config['minimal_roi'], 'minimal_roi': config['minimal_roi'],
'use_sell_signal': config['ask_strategy']['use_sell_signal'], 'use_sell_signal': config['use_sell_signal'],
'sell_profit_only': config['ask_strategy']['sell_profit_only'], 'sell_profit_only': config['sell_profit_only'],
'sell_profit_offset': config['ask_strategy']['sell_profit_offset'], 'sell_profit_offset': config['sell_profit_offset'],
'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'], 'ignore_roi_if_buy_signal': config['ignore_roi_if_buy_signal'],
**daily_stats, **daily_stats,
**trade_stats **trade_stats
} }

View File

@ -45,10 +45,6 @@ class StrategyResolver(IResolver):
strategy_name, config=config, strategy_name, config=config,
extra_dir=config.get('strategy_path')) extra_dir=config.get('strategy_path'))
# make sure ask_strategy dict is available
if 'ask_strategy' not in config:
config['ask_strategy'] = {}
if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'):
# Assign ticker_interval to timeframe to keep compatibility # Assign ticker_interval to timeframe to keep compatibility
if 'timeframe' not in config: if 'timeframe' not in config:
@ -60,42 +56,36 @@ class StrategyResolver(IResolver):
# Set attributes # Set attributes
# Check if we need to override configuration # Check if we need to override configuration
# (Attribute name, default, subkey) # (Attribute name, default, subkey)
attributes = [("minimal_roi", {"0": 10.0}, None), attributes = [("minimal_roi", {"0": 10.0}),
("timeframe", None, None), ("timeframe", None),
("stoploss", None, None), ("stoploss", None),
("trailing_stop", None, None), ("trailing_stop", None),
("trailing_stop_positive", None, None), ("trailing_stop_positive", None),
("trailing_stop_positive_offset", 0.0, None), ("trailing_stop_positive_offset", 0.0),
("trailing_only_offset_is_reached", None, None), ("trailing_only_offset_is_reached", None),
("use_custom_stoploss", None, None), ("use_custom_stoploss", None),
("process_only_new_candles", None, None), ("process_only_new_candles", None),
("order_types", None, None), ("order_types", None),
("order_time_in_force", None, None), ("order_time_in_force", None),
("stake_currency", None, None), ("stake_currency", None),
("stake_amount", None, None), ("stake_amount", None),
("protections", None, None), ("protections", None),
("startup_candle_count", None, None), ("startup_candle_count", None),
("unfilledtimeout", None, None), ("unfilledtimeout", None),
("use_sell_signal", True, 'ask_strategy'), ("use_sell_signal", True),
("sell_profit_only", False, 'ask_strategy'), ("sell_profit_only", False),
("ignore_roi_if_buy_signal", False, 'ask_strategy'), ("ignore_roi_if_buy_signal", False),
("sell_profit_offset", 0.0, 'ask_strategy'), ("sell_profit_offset", 0.0),
("disable_dataframe_checks", False, None), ("disable_dataframe_checks", False),
("ignore_buying_expired_candle_after", 0, 'ask_strategy') ("ignore_buying_expired_candle_after", 0)
] ]
for attribute, default, subkey in attributes: for attribute, default in attributes:
if subkey: StrategyResolver._override_attribute_helper(strategy, config,
StrategyResolver._override_attribute_helper(strategy, config.get(subkey, {}), attribute, default)
attribute, default)
else:
StrategyResolver._override_attribute_helper(strategy, config,
attribute, default)
# Loop this list again to have output combined # Loop this list again to have output combined
for attribute, _, subkey in attributes: for attribute, _ in attributes:
if subkey and attribute in config[subkey]: if attribute in config:
logger.info("Strategy using %s: %s", attribute, config[subkey][attribute])
elif attribute in config:
logger.info("Strategy using %s: %s", attribute, config[attribute]) logger.info("Strategy using %s: %s", attribute, config[attribute])
StrategyResolver._normalize_attributes(strategy) StrategyResolver._normalize_attributes(strategy)

View File

@ -97,6 +97,11 @@ class IStrategy(ABC, HyperStrategyMixin):
# run "populate_indicators" only for new candle # run "populate_indicators" only for new candle
process_only_new_candles: bool = False process_only_new_candles: bool = False
use_sell_signal: bool
sell_profit_only: bool
sell_profit_offset: float
ignore_roi_if_buy_signal: bool
# Number of seconds after which the candle will no longer result in a buy on expired candles # Number of seconds after which the candle will no longer result in a buy on expired candles
ignore_buying_expired_candle_after: int = 0 ignore_buying_expired_candle_after: int = 0
@ -543,10 +548,9 @@ class IStrategy(ABC, HyperStrategyMixin):
# Set current rate to high for backtesting sell # Set current rate to high for backtesting sell
current_rate = high or rate current_rate = high or rate
current_profit = trade.calc_profit_ratio(current_rate) current_profit = trade.calc_profit_ratio(current_rate)
ask_strategy = self.config.get('ask_strategy', {})
# if buy signal and ignore_roi is set, we don't need to evaluate min_roi. # if buy signal and ignore_roi is set, we don't need to evaluate min_roi.
roi_reached = (not (buy and ask_strategy.get('ignore_roi_if_buy_signal', False)) roi_reached = (not (buy and self.ignore_roi_if_buy_signal)
and self.min_roi_reached(trade=trade, current_profit=current_profit, and self.min_roi_reached(trade=trade, current_profit=current_profit,
current_time=date)) current_time=date))
@ -556,11 +560,10 @@ class IStrategy(ABC, HyperStrategyMixin):
current_rate = rate current_rate = rate
current_profit = trade.calc_profit_ratio(current_rate) current_profit = trade.calc_profit_ratio(current_rate)
if (ask_strategy.get('sell_profit_only', False) if (self.sell_profit_only and current_profit <= self.sell_profit_offset):
and current_profit <= ask_strategy.get('sell_profit_offset', 0)):
# sell_profit_only and profit doesn't reach the offset - ignore sell signal # sell_profit_only and profit doesn't reach the offset - ignore sell signal
pass pass
elif ask_strategy.get('use_sell_signal', True) and not buy: elif self.use_sell_signal and not buy:
if sell: if sell:
sell_signal = SellType.SELL_SIGNAL sell_signal = SellType.SELL_SIGNAL
else: else:

View File

@ -26,9 +26,6 @@
"price_side": "ask", "price_side": "ask",
"use_order_book": true, "use_order_book": true,
"order_book_top": 1, "order_book_top": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
}, },
{{ exchange | indent(4) }}, {{ exchange | indent(4) }},
"pairlists": [ "pairlists": [

View File

@ -551,7 +551,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
if data.trailing_stop_positive is not None: if data.trailing_stop_positive is not None:
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["ask_strategy"] = {"use_sell_signal": data.use_sell_signal} default_conf["use_sell_signal"] = data.use_sell_signal
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)

View File

@ -465,7 +465,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
def test_backtest__enter_trade(default_conf, fee, mocker) -> None: def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False default_conf['use_sell_signal'] = False
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)
patch_exchange(mocker) patch_exchange(mocker)
@ -511,7 +511,7 @@ def test_backtest__enter_trade(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['ask_strategy']['use_sell_signal'] = False default_conf['use_sell_signal'] = False
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)
patch_exchange(mocker) patch_exchange(mocker)
@ -574,7 +574,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False default_conf['use_sell_signal'] = False
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)
patch_exchange(mocker) patch_exchange(mocker)
@ -819,7 +819,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated") @pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
default_conf['ask_strategy'].update({ default_conf.update({
"use_sell_signal": True, "use_sell_signal": True,
"sell_profit_only": False, "sell_profit_only": False,
"sell_profit_offset": 0.0, "sell_profit_offset": 0.0,
@ -894,7 +894,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated") @pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys): def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
default_conf['ask_strategy'].update({ default_conf.update({
"use_sell_signal": True, "use_sell_signal": True,
"sell_profit_only": False, "sell_profit_only": False,
"sell_profit_offset": 0.0, "sell_profit_offset": 0.0,

View File

@ -290,14 +290,12 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
assert strategy.use_sell_signal assert strategy.use_sell_signal
assert isinstance(strategy.use_sell_signal, bool) assert isinstance(strategy.use_sell_signal, bool)
# must be inserted to configuration # must be inserted to configuration
assert 'use_sell_signal' in default_conf['ask_strategy'] assert 'use_sell_signal' in default_conf
assert default_conf['ask_strategy']['use_sell_signal'] assert default_conf['use_sell_signal']
default_conf.update({ default_conf.update({
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'ask_strategy': { 'use_sell_signal': False,
'use_sell_signal': False,
},
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -315,14 +313,12 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
assert not strategy.sell_profit_only assert not strategy.sell_profit_only
assert isinstance(strategy.sell_profit_only, bool) assert isinstance(strategy.sell_profit_only, bool)
# must be inserted to configuration # must be inserted to configuration
assert 'sell_profit_only' in default_conf['ask_strategy'] assert 'sell_profit_only' in default_conf
assert not default_conf['ask_strategy']['sell_profit_only'] assert not default_conf['sell_profit_only']
default_conf.update({ default_conf.update({
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'ask_strategy': { 'sell_profit_only': True,
'sell_profit_only': True,
},
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)

View File

@ -878,15 +878,15 @@ def test_validate_tsl(default_conf):
def test_validate_edge2(edge_conf): def test_validate_edge2(edge_conf):
edge_conf.update({"ask_strategy": { edge_conf.update({
"use_sell_signal": True, "use_sell_signal": True,
}}) })
# Passes test # Passes test
validate_config_consistency(edge_conf) validate_config_consistency(edge_conf)
edge_conf.update({"ask_strategy": { edge_conf.update({
"use_sell_signal": False, "use_sell_signal": False,
}}) })
with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, " with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, "
"otherwise no sells will happen."): "otherwise no sells will happen."):
validate_config_consistency(edge_conf) validate_config_consistency(edge_conf)
@ -1128,11 +1128,17 @@ def test_pairlist_resolving_fallback(mocker):
assert config['datadir'] == Path.cwd() / "user_data/data/binance" assert config['datadir'] == Path.cwd() / "user_data/data/binance"
@pytest.mark.skip(reason='Currently no deprecated / moved sections')
# The below is kept as a sample for the future.
@pytest.mark.parametrize("setting", [ @pytest.mark.parametrize("setting", [
("ask_strategy", "use_sell_signal", True, ("ask_strategy", "use_sell_signal", True,
"experimental", "use_sell_signal", False), 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),
("ask_strategy", "ignore_buying_expired_candle_after", 5,
None, "ignore_buying_expired_candle_after", 6),
]) ])
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog):
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
@ -1141,10 +1147,14 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca
# (they may not exist in the config) # (they may not exist in the config)
default_conf[setting[0]] = {} default_conf[setting[0]] = {}
default_conf[setting[3]] = {} default_conf[setting[3]] = {}
# Assign new setting
default_conf[setting[0]][setting[1]] = setting[2]
# Assign deprecated setting # Assign deprecated setting
default_conf[setting[3]][setting[4]] = setting[5] default_conf[setting[0]][setting[1]] = setting[2]
# Assign new setting
if setting[3]:
default_conf[setting[3]][setting[4]] = setting[5]
else:
default_conf[setting[4]] = setting[5]
# New and deprecated settings are conflicting ones # New and deprecated settings are conflicting ones
with pytest.raises(OperationalException, match=r'DEPRECATED'): with pytest.raises(OperationalException, match=r'DEPRECATED'):
@ -1153,13 +1163,19 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca
caplog.clear() caplog.clear()
# Delete new setting # Delete new setting
del default_conf[setting[0]][setting[1]] if setting[3]:
del default_conf[setting[3]][setting[4]]
else:
del default_conf[setting[4]]
process_temporary_deprecated_settings(default_conf) process_temporary_deprecated_settings(default_conf)
assert log_has_re('DEPRECATED', caplog) assert log_has_re('DEPRECATED', caplog)
# The value of the new setting shall have been set to the # The value of the new setting shall have been set to the
# value of the deprecated one # value of the deprecated one
assert default_conf[setting[0]][setting[1]] == setting[5] if setting[3]:
assert default_conf[setting[3]][setting[4]] == setting[2]
else:
assert default_conf[setting[4]] == setting[2]
@pytest.mark.parametrize("setting", [ @pytest.mark.parametrize("setting", [
@ -1209,16 +1225,16 @@ def test_check_conflicting_settings(mocker, default_conf, caplog):
# New and deprecated settings are conflicting ones # New and deprecated settings are conflicting ones
with pytest.raises(OperationalException, match=r'DEPRECATED'): with pytest.raises(OperationalException, match=r'DEPRECATED'):
check_conflicting_settings(default_conf, check_conflicting_settings(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
caplog.clear() caplog.clear()
# Delete new setting (deprecated exists) # Delete new setting (deprecated exists)
del default_conf['sectionA']['new_setting'] del default_conf['sectionA']['new_setting']
check_conflicting_settings(default_conf, check_conflicting_settings(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
assert not log_has_re('DEPRECATED', caplog) assert not log_has_re('DEPRECATED', caplog)
assert 'new_setting' not in default_conf['sectionA'] assert 'new_setting' not in default_conf['sectionA']
@ -1229,8 +1245,8 @@ def test_check_conflicting_settings(mocker, default_conf, caplog):
# Delete deprecated setting # Delete deprecated setting
del default_conf['sectionB']['deprecated_setting'] del default_conf['sectionB']['deprecated_setting']
check_conflicting_settings(default_conf, check_conflicting_settings(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
assert not log_has_re('DEPRECATED', caplog) assert not log_has_re('DEPRECATED', caplog)
assert default_conf['sectionA']['new_setting'] == 'valA' assert default_conf['sectionA']['new_setting'] == 'valA'
@ -1242,15 +1258,13 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# (they may not exist in the config) # (they may not exist in the config)
default_conf['sectionA'] = {} default_conf['sectionA'] = {}
default_conf['sectionB'] = {} default_conf['sectionB'] = {}
# Assign new setting
default_conf['sectionA']['new_setting'] = 'valA'
# Assign deprecated setting # Assign deprecated setting
default_conf['sectionB']['deprecated_setting'] = 'valB' default_conf['sectionB']['deprecated_setting'] = 'valB'
# Both new and deprecated settings exists # Both new and deprecated settings exists
process_deprecated_setting(default_conf, process_deprecated_setting(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
assert log_has_re('DEPRECATED', caplog) assert log_has_re('DEPRECATED', caplog)
# The value of the new setting shall have been set to the # The value of the new setting shall have been set to the
# value of the deprecated one # value of the deprecated one
@ -1261,8 +1275,8 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# Delete new setting (deprecated exists) # Delete new setting (deprecated exists)
del default_conf['sectionA']['new_setting'] del default_conf['sectionA']['new_setting']
process_deprecated_setting(default_conf, process_deprecated_setting(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
assert log_has_re('DEPRECATED', caplog) assert log_has_re('DEPRECATED', caplog)
# The value of the new setting shall have been set to the # The value of the new setting shall have been set to the
# value of the deprecated one # value of the deprecated one
@ -1275,11 +1289,21 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# Delete deprecated setting # Delete deprecated setting
del default_conf['sectionB']['deprecated_setting'] del default_conf['sectionB']['deprecated_setting']
process_deprecated_setting(default_conf, process_deprecated_setting(default_conf,
'sectionA', 'new_setting', 'sectionB', 'deprecated_setting',
'sectionB', 'deprecated_setting') 'sectionA', 'new_setting')
assert not log_has_re('DEPRECATED', caplog) assert not log_has_re('DEPRECATED', caplog)
assert default_conf['sectionA']['new_setting'] == 'valA' assert default_conf['sectionA']['new_setting'] == 'valA'
caplog.clear()
# Test moving to root
default_conf['sectionB']['deprecated_setting2'] = "DeadBeef"
process_deprecated_setting(default_conf,
'sectionB', 'deprecated_setting2',
None, 'new_setting')
assert log_has_re('DEPRECATED', caplog)
assert default_conf['new_setting']
def test_process_removed_setting(mocker, default_conf, caplog): def test_process_removed_setting(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)

View File

@ -2960,11 +2960,11 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
buy=MagicMock(return_value=limit_buy_order_open), buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee, get_fee=fee,
) )
default_conf['ask_strategy'] = { default_conf.update({
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': True, 'sell_profit_only': True,
'sell_profit_offset': 0.1, 'sell_profit_offset': 0.1,
} })
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
@ -2977,7 +2977,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
freqtrade.config['ask_strategy']['sell_profit_offset'] = 0.0 freqtrade.strategy.sell_profit_offset = 0.0
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value assert trade.sell_reason == SellType.SELL_SIGNAL.value
@ -2997,10 +2997,10 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu
buy=MagicMock(return_value=limit_buy_order_open), buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee, get_fee=fee,
) )
default_conf['ask_strategy'] = { default_conf.update({
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': False, 'sell_profit_only': False,
} })
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
@ -3028,10 +3028,10 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
buy=MagicMock(return_value=limit_buy_order_open), buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee, get_fee=fee,
) )
default_conf['ask_strategy'] = { default_conf.update({
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': True, 'sell_profit_only': True,
} })
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
@ -3058,10 +3058,10 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_
buy=MagicMock(return_value=limit_buy_order_open), buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee, get_fee=fee,
) )
default_conf['ask_strategy'] = { default_conf.update({
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': False, 'sell_profit_only': False,
} })
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
@ -3209,9 +3209,8 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order
buy=MagicMock(return_value=limit_buy_order_open), buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee, get_fee=fee,
) )
default_conf['ask_strategy'] = { default_conf['ignore_roi_if_buy_signal'] = True
'ignore_roi_if_buy_signal': True
}
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)