Merge pull request #6507 from freqtrade/short_order_tif
Update order time in force to use entry/exit wording
This commit is contained in:
		| @@ -61,8 +61,8 @@ | ||||
|         "stoploss_on_exchange_interval": 60 | ||||
|     }, | ||||
|     "order_time_in_force": { | ||||
|         "buy": "gtc", | ||||
|         "sell": "gtc" | ||||
|         "entry": "gtc", | ||||
|         "exit": "gtc" | ||||
|     }, | ||||
|     "pairlists": [ | ||||
|         {"method": "StaticPairList"}, | ||||
|   | ||||
| @@ -122,7 +122,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | ||||
| | `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_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_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 entry and exit orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict | ||||
| | `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price. <br>*Defaults to `0.02` 2%).*<br> **Datatype:** Positive float | ||||
| | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String | ||||
| | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean | ||||
| @@ -465,8 +465,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`. | ||||
|  | ||||
| ``` python | ||||
| "order_time_in_force": { | ||||
|     "buy": "gtc", | ||||
|     "sell": "gtc" | ||||
|     "entry": "gtc", | ||||
|     "exit": "gtc" | ||||
| }, | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from jsonschema import Draft4Validator, validators | ||||
| from jsonschema.exceptions import ValidationError, best_match | ||||
|  | ||||
| from freqtrade import constants | ||||
| from freqtrade.enums import RunMode | ||||
| from freqtrade.enums import RunMode, TradingMode | ||||
| from freqtrade.exceptions import OperationalException | ||||
|  | ||||
|  | ||||
| @@ -80,6 +80,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None: | ||||
|     _validate_protections(conf) | ||||
|     _validate_unlimited_amount(conf) | ||||
|     _validate_ask_orderbook(conf) | ||||
|     validate_migrated_strategy_settings(conf) | ||||
|  | ||||
|     # validate configuration before returning | ||||
|     logger.info('Validating configuration ...') | ||||
| @@ -207,3 +208,24 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: | ||||
|                 "Please use `order_book_top` instead of `order_book_min` and `order_book_max` " | ||||
|                 "for your `ask_strategy` configuration." | ||||
|             ) | ||||
|  | ||||
|  | ||||
| def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None: | ||||
|  | ||||
|     _validate_time_in_force(conf) | ||||
|  | ||||
|  | ||||
| def _validate_time_in_force(conf: Dict[str, Any]) -> None: | ||||
|  | ||||
|     time_in_force = conf.get('order_time_in_force', {}) | ||||
|     if 'buy' in time_in_force or 'sell' in time_in_force: | ||||
|         if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: | ||||
|             raise OperationalException( | ||||
|                 "Please migrate your time_in_force settings to use 'entry' and 'exit'.") | ||||
|         else: | ||||
|             logger.warning( | ||||
|                 "DEPRECATED: Using 'buy' and 'sell' for time_in_force is deprecated." | ||||
|                 "Please migrate your time_in_force settings to use 'entry' and 'exit'." | ||||
|             ) | ||||
|             time_in_force['entry'] = time_in_force.pop('buy') | ||||
|             time_in_force['exit'] = time_in_force.pop('sell') | ||||
|   | ||||
| @@ -19,7 +19,7 @@ DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' | ||||
| DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite' | ||||
| UNLIMITED_STAKE_AMOUNT = 'unlimited' | ||||
| DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05 | ||||
| REQUIRED_ORDERTIF = ['buy', 'sell'] | ||||
| REQUIRED_ORDERTIF = ['entry', 'exit'] | ||||
| REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] | ||||
| ORDERBOOK_SIDES = ['ask', 'bid'] | ||||
| ORDERTYPE_POSSIBILITIES = ['limit', 'market'] | ||||
| @@ -233,10 +233,10 @@ CONF_SCHEMA = { | ||||
|         'order_time_in_force': { | ||||
|             'type': 'object', | ||||
|             'properties': { | ||||
|                 'buy': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}, | ||||
|                 'sell': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES} | ||||
|                 'entry': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}, | ||||
|                 'exit': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES} | ||||
|             }, | ||||
|             'required': ['buy', 'sell'] | ||||
|             'required': REQUIRED_ORDERTIF | ||||
|         }, | ||||
|         'exchange': {'$ref': '#/definitions/exchange'}, | ||||
|         'edge': {'$ref': '#/definitions/edge'}, | ||||
|   | ||||
| @@ -595,7 +595,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         :param leverage: amount of leverage applied to this trade | ||||
|         :return: True if a buy order is created, false if it fails. | ||||
|         """ | ||||
|         time_in_force = self.strategy.order_time_in_force['buy'] | ||||
|         time_in_force = self.strategy.order_time_in_force['entry'] | ||||
|  | ||||
|         [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] | ||||
|         trade_side = 'short' if is_short else 'long' | ||||
| @@ -659,13 +659,12 @@ class FreqtradeBot(LoggingMixin): | ||||
|         amount_requested = amount | ||||
|  | ||||
|         if order_status == 'expired' or order_status == 'rejected': | ||||
|             order_tif = self.strategy.order_time_in_force['buy'] | ||||
|  | ||||
|             # return false if the order is not filled | ||||
|             if float(order['filled']) == 0: | ||||
|                 logger.warning('%s %s order with time in force %s for %s is %s by %s.' | ||||
|                                ' zero amount is fulfilled.', | ||||
|                                name, order_tif, order_type, pair, order_status, self.exchange.name) | ||||
|                 logger.warning(f'{name} {time_in_force} order with time in force {order_type} ' | ||||
|                                f'for {pair} is {order_status} by {self.exchange.name}.' | ||||
|                                ' zero amount is fulfilled.') | ||||
|                 return False | ||||
|             else: | ||||
|                 # the order is partially fulfilled | ||||
| @@ -673,8 +672,9 @@ class FreqtradeBot(LoggingMixin): | ||||
|                 # if the order is fulfilled fully or partially | ||||
|                 logger.warning('%s %s order with time in force %s for %s is %s by %s.' | ||||
|                                ' %s amount fulfilled out of %s (%s remaining which is canceled).', | ||||
|                                name, order_tif, order_type, pair, order_status, self.exchange.name, | ||||
|                                order['filled'], order['amount'], order['remaining'] | ||||
|                                name, time_in_force, order_type, pair, order_status, | ||||
|                                self.exchange.name, order['filled'], order['amount'], | ||||
|                                order['remaining'] | ||||
|                                ) | ||||
|                 stake_amount = order['cost'] | ||||
|                 amount = safe_value_fallback(order, 'filled', 'amount') | ||||
| @@ -1382,7 +1382,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|             order_type = self.strategy.order_types.get("emergencysell", "market") | ||||
|  | ||||
|         amount = self._safe_exit_amount(trade.pair, trade.amount) | ||||
|         time_in_force = self.strategy.order_time_in_force['sell'] | ||||
|         time_in_force = self.strategy.order_time_in_force['exit'] | ||||
|  | ||||
|         if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( | ||||
|                 pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, | ||||
|   | ||||
| @@ -504,7 +504,7 @@ class Backtesting: | ||||
|                     # freqtrade does not support this in live, and the order would fill immediately | ||||
|                     closerate = max(closerate, sell_row[LOW_IDX]) | ||||
|             # Confirm trade exit: | ||||
|             time_in_force = self.strategy.order_time_in_force['sell'] | ||||
|             time_in_force = self.strategy.order_time_in_force['exit'] | ||||
|  | ||||
|             if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( | ||||
|                     pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount, | ||||
| @@ -640,7 +640,7 @@ class Backtesting: | ||||
|             # If not pos adjust, trade is None | ||||
|             return trade | ||||
|         order_type = self.strategy.order_types['buy'] | ||||
|         time_in_force = self.strategy.order_time_in_force['buy'] | ||||
|         time_in_force = self.strategy.order_time_in_force['entry'] | ||||
|  | ||||
|         if not pos_adjust: | ||||
|             max_leverage = self.exchange.get_max_leverage(pair, stake_amount) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from inspect import getfullargspec | ||||
| from pathlib import Path | ||||
| 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.exceptions import OperationalException | ||||
| from freqtrade.resolvers import IResolver | ||||
| @@ -160,10 +161,12 @@ class StrategyResolver(IResolver): | ||||
|  | ||||
|     @staticmethod | ||||
|     def _strategy_sanity_validations(strategy): | ||||
|         # Ensure necessary migrations are performed first. | ||||
|         validate_migrated_strategy_settings(strategy.config) | ||||
|  | ||||
|         if not all(k in strategy.order_types for k in REQUIRED_ORDERTYPES): | ||||
|             raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. " | ||||
|                               f"Order-types mapping is incomplete.") | ||||
|  | ||||
|         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.") | ||||
|   | ||||
| @@ -96,8 +96,8 @@ class IStrategy(ABC, HyperStrategyMixin): | ||||
|  | ||||
|     # Optional time in force | ||||
|     order_time_in_force: Dict = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc', | ||||
|     } | ||||
|  | ||||
|     # run "populate_indicators" only for new candle | ||||
|   | ||||
| @@ -83,8 +83,8 @@ class {{ strategy }}(IStrategy): | ||||
|  | ||||
|     # Optional order time in force. | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc' | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc' | ||||
|     } | ||||
|     {{ plot_config | indent(4) }} | ||||
|  | ||||
|   | ||||
| @@ -84,8 +84,8 @@ class SampleShortStrategy(IStrategy): | ||||
|  | ||||
|     # Optional order time in force. | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc' | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc' | ||||
|     } | ||||
|  | ||||
|     plot_config = { | ||||
|   | ||||
| @@ -85,8 +85,8 @@ class SampleStrategy(IStrategy): | ||||
|  | ||||
|     # Optional order time in force. | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc' | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc' | ||||
|     } | ||||
|  | ||||
|     plot_config = { | ||||
|   | ||||
| @@ -319,6 +319,7 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: | ||||
|     del default_conf['timeframe'] | ||||
|     default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, | ||||
|                                      'SampleStrategy'] | ||||
|     # TODO: This refers to the sampleStrategy in user_data if it exists... | ||||
|  | ||||
|     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) | ||||
|     with pytest.raises(OperationalException): | ||||
|   | ||||
| @@ -45,8 +45,8 @@ class HyperoptableStrategy(IStrategy): | ||||
|  | ||||
|     # Optional time in force for orders | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc', | ||||
|     } | ||||
|  | ||||
|     buy_params = { | ||||
|   | ||||
| @@ -47,8 +47,8 @@ class StrategyTestV2(IStrategy): | ||||
|  | ||||
|     # Optional time in force for orders | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc', | ||||
|     } | ||||
|  | ||||
|     # By default this strategy does not use Position Adjustments | ||||
|   | ||||
| @@ -48,8 +48,8 @@ class StrategyTestV3(IStrategy): | ||||
|  | ||||
|     # Optional time in force for orders | ||||
|     order_time_in_force = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|         'entry': 'gtc', | ||||
|         'exit': 'gtc', | ||||
|     } | ||||
|  | ||||
|     buy_params = { | ||||
|   | ||||
| @@ -257,8 +257,8 @@ def test_strategy_override_order_tif(caplog, default_conf): | ||||
|     caplog.set_level(logging.INFO) | ||||
|  | ||||
|     order_time_in_force = { | ||||
|         'buy': 'fok', | ||||
|         'sell': 'gtc', | ||||
|         'entry': 'fok', | ||||
|         'exit': 'gtc', | ||||
|     } | ||||
|  | ||||
|     default_conf.update({ | ||||
| @@ -268,15 +268,15 @@ def test_strategy_override_order_tif(caplog, default_conf): | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     assert strategy.order_time_in_force | ||||
|     for method in ['buy', 'sell']: | ||||
|     for method in ['entry', 'exit']: | ||||
|         assert strategy.order_time_in_force[method] == order_time_in_force[method] | ||||
|  | ||||
|     assert log_has("Override strategy 'order_time_in_force' with value in config file:" | ||||
|                    " {'buy': 'fok', 'sell': 'gtc'}.", caplog) | ||||
|                    " {'entry': 'fok', 'exit': 'gtc'}.", caplog) | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': CURRENT_TEST_STRATEGY, | ||||
|         'order_time_in_force': {'buy': 'fok'} | ||||
|         'order_time_in_force': {'entry': 'fok'} | ||||
|     }) | ||||
|     # Raise error for invalid configuration | ||||
|     with pytest.raises(ImportError, | ||||
|   | ||||
| @@ -941,6 +941,28 @@ def test_validate_ask_orderbook(default_conf, caplog) -> None: | ||||
|         validate_config_consistency(conf) | ||||
|  | ||||
|  | ||||
| def test_validate_time_in_force(default_conf, caplog) -> None: | ||||
|     conf = deepcopy(default_conf) | ||||
|     conf['order_time_in_force'] = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|     } | ||||
|     validate_config_consistency(conf) | ||||
|     assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for time_in_force is.*", caplog) | ||||
|     assert conf['order_time_in_force']['entry'] == 'gtc' | ||||
|     assert conf['order_time_in_force']['exit'] == 'gtc' | ||||
|  | ||||
|     conf = deepcopy(default_conf) | ||||
|     conf['order_time_in_force'] = { | ||||
|         'buy': 'gtc', | ||||
|         'sell': 'gtc', | ||||
|     } | ||||
|     conf['trading_mode'] = 'futures' | ||||
|     with pytest.raises(OperationalException, | ||||
|                        match=r"Please migrate your time_in_force settings .* 'entry' and 'exit'\."): | ||||
|         validate_config_consistency(conf) | ||||
|  | ||||
|  | ||||
| def test_load_config_test_comments() -> None: | ||||
|     """ | ||||
|     Load config with comments | ||||
|   | ||||
		Reference in New Issue
	
	Block a user