From 104a73025dac841c2d13819b260cf1ab70325fce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Aug 2022 10:24:56 +0200 Subject: [PATCH 1/2] Uppercase TimeInForce (align with ccxt) --- config_examples/config_full.example.json | 4 ++-- docs/configuration.md | 8 ++++---- docs/exchanges.md | 2 +- docs/strategy_migration.md | 4 ++-- freqtrade/constants.py | 3 ++- freqtrade/exchange/binance.py | 3 +-- freqtrade/exchange/exchange.py | 12 ++++++------ freqtrade/exchange/gateio.py | 7 +++---- freqtrade/exchange/kraken.py | 2 +- freqtrade/exchange/kucoin.py | 3 +-- freqtrade/exchange/okx.py | 2 +- freqtrade/strategy/interface.py | 4 ++-- freqtrade/templates/base_strategy.py.j2 | 4 ++-- freqtrade/templates/sample_strategy.py | 4 ++-- tests/exchange/test_exchange.py | 20 ++++++++++---------- tests/exchange/test_kraken.py | 2 +- tests/strategy/test_strategy_loading.py | 8 ++++---- tests/test_configuration.py | 8 ++++---- 18 files changed, 49 insertions(+), 51 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 74457d2b6..8155cb145 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -64,8 +64,8 @@ "stoploss_on_exchange_limit_ratio": 0.99 }, "order_time_in_force": { - "entry": "gtc", - "exit": "gtc" + "entry": "GTC", + "exit": "GTC" }, "pairlists": [ {"method": "StaticPairList"}, diff --git a/docs/configuration.md b/docs/configuration.md index d5c0b3d8b..851bbcd82 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -525,16 +525,16 @@ It means if the order is not executed immediately AND fully then it is cancelled It is the same as FOK (above) except it can be partially fulfilled. The remaining part is automatically cancelled by the exchange. -The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values. +The `order_time_in_force` parameter contains a dict with entry and exit time in force policy values. This can be set in the configuration file or in the strategy. Values set in the configuration file overwrites values set in the strategy. -The possible values are: `gtc` (default), `fok` or `ioc`. +The possible values are: `GTC` (default), `FOK` or `IOC`. ``` python "order_time_in_force": { - "entry": "gtc", - "exit": "gtc" + "entry": "GTC", + "exit": "GTC" }, ``` diff --git a/docs/exchanges.md b/docs/exchanges.md index 50ebf9e0a..407a67d70 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -278,7 +278,7 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t "exchange": { "name": "kraken", "_ft_has_params": { - "order_time_in_force": ["gtc", "fok"], + "order_time_in_force": ["GTC", "FOK"], "ohlcv_candle_limit": 200 } //... diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index 064e7a59d..ac65abff4 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -332,8 +332,8 @@ After: ``` python hl_lines="2 3" order_time_in_force: Dict = { - "entry": "gtc", - "exit": "gtc", + "entry": "GTC", + "exit": "GTC", } ``` diff --git a/freqtrade/constants.py b/freqtrade/constants.py index ddbc84fa9..bab8c4816 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -23,7 +23,8 @@ REQUIRED_ORDERTIF = ['entry', 'exit'] REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange'] PRICING_SIDES = ['ask', 'bid', 'same', 'other'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] -ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] +_ORDERTIF_POSSIBILITIES = ['GTC', 'FOK', 'IOC', 'PO'] +ORDERTIF_POSSIBILITIES = _ORDERTIF_POSSIBILITIES + [t.lower() for t in _ORDERTIF_POSSIBILITIES] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index a5e9fd37c..026ba1c65 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,8 +23,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, "stoploss_order_types": {"limit": "stop_loss_limit"}, - "order_time_in_force": ['gtc', 'fok', 'ioc'], - "time_in_force_parameter": "timeInForce", + "order_time_in_force": ['GTC', 'FOK', 'IOC'], "ohlcv_candle_limit": 1000, "trades_pagination": "id", "trades_pagination_arg": "fromId", diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 4386f47f6..586a364c5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -62,7 +62,7 @@ class Exchange: # or by specifying them in the configuration. _ft_has_default: Dict = { "stoploss_on_exchange": False, - "order_time_in_force": ["gtc"], + "order_time_in_force": ["GTC"], "time_in_force_parameter": "timeInForce", "ohlcv_params": {}, "ohlcv_candle_limit": 500, @@ -611,7 +611,7 @@ class Exchange: """ Checks if order time in force configured in strategy/config are supported """ - if any(v not in self._ft_has["order_time_in_force"] + if any(v.upper() not in self._ft_has["order_time_in_force"] for k, v in order_time_in_force.items()): raise OperationalException( f'Time in force policies are not supported for {self.name} yet.') @@ -989,12 +989,12 @@ class Exchange: ordertype: str, leverage: float, reduceOnly: bool, - time_in_force: str = 'gtc', + time_in_force: str = 'GTC', ) -> Dict: params = self._params.copy() - if time_in_force != 'gtc' and ordertype != 'market': + if time_in_force != 'GTC' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') - params.update({param: time_in_force}) + params.update({param: time_in_force.upper()}) if reduceOnly: params.update({'reduceOnly': True}) return params @@ -1009,7 +1009,7 @@ class Exchange: rate: float, leverage: float, reduceOnly: bool = False, - time_in_force: str = 'gtc', + time_in_force: str = 'GTC', ) -> Dict: if self._config['dry_run']: dry_order = self.create_dry_run_order( diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 426a4b64d..ab127a036 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -25,8 +25,7 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, - "time_in_force_parameter": "timeInForce", - "order_time_in_force": ['gtc', 'ioc'], + "order_time_in_force": ['GTC', 'IOC'], "stoploss_order_types": {"limit": "limit"}, "stoploss_on_exchange": True, } @@ -57,7 +56,7 @@ class Gateio(Exchange): ordertype: str, leverage: float, reduceOnly: bool, - time_in_force: str = 'gtc', + time_in_force: str = 'GTC', ) -> Dict: params = super()._get_params( side=side, @@ -69,7 +68,7 @@ class Gateio(Exchange): if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES: params['type'] = 'market' param = self._ft_has.get('time_in_force_parameter', '') - params.update({param: 'ioc'}) + params.update({param: 'IOC'}) return params def get_trades_for_order(self, order_id: str, pair: str, since: datetime, diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 0103e2702..6c9b88eae 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -171,7 +171,7 @@ class Kraken(Exchange): ordertype: str, leverage: float, reduceOnly: bool, - time_in_force: str = 'gtc' + time_in_force: str = 'GTC' ) -> Dict: params = super()._get_params( side=side, diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 21eaa4bc3..f05fd3f56 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -23,8 +23,7 @@ class Kucoin(Exchange): "stoploss_order_types": {"limit": "limit", "market": "market"}, "l2_limit_range": [20, 100], "l2_limit_range_required": False, - "order_time_in_force": ['gtc', 'fok', 'ioc'], - "time_in_force_parameter": "timeInForce", + "order_time_in_force": ['GTC', 'FOK', 'IOC'], "ohlcv_candle_limit": 1500, } diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index f039f2b3f..9340dd0e4 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -98,7 +98,7 @@ class Okx(Exchange): ordertype: str, leverage: float, reduceOnly: bool, - time_in_force: str = 'gtc', + time_in_force: str = 'GTC', ) -> Dict: params = super()._get_params( side=side, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 79dbd4c69..db16ac383 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -78,8 +78,8 @@ class IStrategy(ABC, HyperStrategyMixin): # Optional time in force order_time_in_force: Dict = { - 'entry': 'gtc', - 'exit': 'gtc', + 'entry': 'GTC', + 'exit': 'GTC', } # run "populate_indicators" only for new candle diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 610a7a96e..5a4504687 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -88,8 +88,8 @@ class {{ strategy }}(IStrategy): # Optional order time in force. order_time_in_force = { - 'entry': 'gtc', - 'exit': 'gtc' + 'entry': 'GTC', + 'exit': 'GTC' } {{ plot_config | indent(4) }} diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 1b375714a..1eb3d4256 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -88,8 +88,8 @@ class SampleStrategy(IStrategy): # Optional order time in force. order_time_in_force = { - 'entry': 'gtc', - 'exit': 'gtc' + 'entry': 'GTC', + 'exit': 'GTC' } plot_config = { diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 093284668..b1f22e647 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -275,7 +275,7 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) # Patch to see if this will pass if the values are in the ft dict - ex._ft_has.update({"order_time_in_force": ["gtc", "fok", "ioc"]}) + ex._ft_has.update({"order_time_in_force": ["GTC", "FOK", "IOC"]}) ex.validate_order_time_in_force(tif2) @@ -1503,7 +1503,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 assert "timeInForce" in api_mock.create_order.call_args[0][5] - assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force + assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force.upper() order_type = 'market' time_in_force = 'ioc' @@ -1642,10 +1642,10 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 assert "timeInForce" in api_mock.create_order.call_args[0][5] - assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force + assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force.upper() order_type = 'market' - time_in_force = 'ioc' + time_in_force = 'IOC' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0, time_in_force=time_in_force) @@ -3319,7 +3319,7 @@ def test_merge_ft_has_dict(default_conf, mocker): ex = Binance(default_conf) assert ex._ft_has != Exchange._ft_has_default assert ex.get_option('stoploss_on_exchange') - assert ex.get_option('order_time_in_force') == ['gtc', 'fok', 'ioc'] + assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC'] assert ex.get_option('trades_pagination') == 'id' assert ex.get_option('trades_pagination_arg') == 'fromId' @@ -4954,7 +4954,7 @@ def test__get_params(mocker, default_conf, exchange_name): params1 = {'test': True} params2 = { 'test': True, - 'timeInForce': 'ioc', + 'timeInForce': 'IOC', 'reduceOnly': True, } @@ -4969,7 +4969,7 @@ def test__get_params(mocker, default_conf, exchange_name): side="buy", ordertype='market', reduceOnly=False, - time_in_force='gtc', + time_in_force='GTC', leverage=1.0, ) == params1 @@ -4977,7 +4977,7 @@ def test__get_params(mocker, default_conf, exchange_name): side="buy", ordertype='market', reduceOnly=False, - time_in_force='ioc', + time_in_force='IOC', leverage=1.0, ) == params1 @@ -4985,7 +4985,7 @@ def test__get_params(mocker, default_conf, exchange_name): side="buy", ordertype='limit', reduceOnly=False, - time_in_force='gtc', + time_in_force='GTC', leverage=1.0, ) == params1 @@ -4998,7 +4998,7 @@ def test__get_params(mocker, default_conf, exchange_name): side="buy", ordertype='limit', reduceOnly=True, - time_in_force='ioc', + time_in_force='IOC', leverage=3.0, ) == params2 diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 02df60990..66006f2fe 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -50,7 +50,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): assert api_mock.create_order.call_args[0][2] == 'buy' assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 - assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', + assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'IOC', 'trading_agreement': 'agree'} diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index b794cdc99..bf81cd068 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -275,8 +275,8 @@ def test_strategy_override_order_tif(caplog, default_conf): caplog.set_level(logging.INFO) order_time_in_force = { - 'entry': 'fok', - 'exit': 'gtc', + 'entry': 'FOK', + 'exit': 'GTC', } default_conf.update({ @@ -290,11 +290,11 @@ def test_strategy_override_order_tif(caplog, default_conf): 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:" - " {'entry': 'fok', 'exit': 'gtc'}.", caplog) + " {'entry': 'FOK', 'exit': 'GTC'}.", caplog) default_conf.update({ 'strategy': CURRENT_TEST_STRATEGY, - 'order_time_in_force': {'entry': 'fok'} + 'order_time_in_force': {'entry': 'FOK'} }) # Raise error for invalid configuration with pytest.raises(ImportError, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index db87c405f..2825ede5c 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -973,17 +973,17 @@ def test_validate_time_in_force(default_conf, caplog) -> None: conf = deepcopy(default_conf) conf['order_time_in_force'] = { 'buy': 'gtc', - 'sell': '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' + assert conf['order_time_in_force']['exit'] == 'GTC' conf = deepcopy(default_conf) conf['order_time_in_force'] = { - 'buy': 'gtc', - 'sell': 'gtc', + 'buy': 'GTC', + 'sell': 'GTC', } conf['trading_mode'] = 'futures' with pytest.raises(OperationalException, From c61b986c3d7638640ed9b682db42a91f2c8ffe72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Aug 2022 10:30:38 +0200 Subject: [PATCH 2/2] FTX - support time_in_force (and PO ordertype) closes #7175 --- docs/configuration.md | 9 ++++++++- freqtrade/exchange/ftx.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 851bbcd82..940d5e8b0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -525,6 +525,13 @@ It means if the order is not executed immediately AND fully then it is cancelled It is the same as FOK (above) except it can be partially fulfilled. The remaining part is automatically cancelled by the exchange. +**PO (Post only):** + +Post only order. The order is either placed as a maker order, or it is canceled. +This means the order must be placed on orderbook for at at least time in an unfilled state. + +#### time_in_force config + The `order_time_in_force` parameter contains a dict with entry and exit time in force policy values. This can be set in the configuration file or in the strategy. Values set in the configuration file overwrites values set in the strategy. @@ -539,7 +546,7 @@ The possible values are: `GTC` (default), `FOK` or `IOC`. ``` !!! Warning - This is ongoing work. For now, it is supported only for binance and kucoin. + This is ongoing work. For now, it is supported only for binance, gate, ftx and kucoin. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange. ### What values can be used for fiat_display_currency? diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index b3c219542..6a43ab302 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -19,6 +19,7 @@ logger = logging.getLogger(__name__) class Ftx(Exchange): _ft_has: Dict = { + "order_time_in_force": ['GTC', 'IOC', 'PO'], "stoploss_on_exchange": True, "ohlcv_candle_limit": 1500, "ohlcv_require_since": True,