Merge pull request #7298 from freqtrade/tif_align

align TimeInForce to ccxt usage
This commit is contained in:
Matthias 2022-08-29 06:36:51 +02:00 committed by GitHub
commit 4def3678b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 58 additions and 52 deletions

View File

@ -64,8 +64,8 @@
"stoploss_on_exchange_limit_ratio": 0.99 "stoploss_on_exchange_limit_ratio": 0.99
}, },
"order_time_in_force": { "order_time_in_force": {
"entry": "gtc", "entry": "GTC",
"exit": "gtc" "exit": "GTC"
}, },
"pairlists": [ "pairlists": [
{"method": "StaticPairList"}, {"method": "StaticPairList"},

View File

@ -525,21 +525,28 @@ 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 It is the same as FOK (above) except it can be partially fulfilled. The remaining part
is automatically cancelled by the exchange. is automatically cancelled by the exchange.
The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values. **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. This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set 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 ``` python
"order_time_in_force": { "order_time_in_force": {
"entry": "gtc", "entry": "GTC",
"exit": "gtc" "exit": "GTC"
}, },
``` ```
!!! Warning !!! 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. 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? ### What values can be used for fiat_display_currency?

View File

@ -278,7 +278,7 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
"exchange": { "exchange": {
"name": "kraken", "name": "kraken",
"_ft_has_params": { "_ft_has_params": {
"order_time_in_force": ["gtc", "fok"], "order_time_in_force": ["GTC", "FOK"],
"ohlcv_candle_limit": 200 "ohlcv_candle_limit": 200
} }
//... //...

View File

@ -332,8 +332,8 @@ After:
``` python hl_lines="2 3" ``` python hl_lines="2 3"
order_time_in_force: Dict = { order_time_in_force: Dict = {
"entry": "gtc", "entry": "GTC",
"exit": "gtc", "exit": "GTC",
} }
``` ```

View File

@ -23,7 +23,8 @@ REQUIRED_ORDERTIF = ['entry', 'exit']
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange'] REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
PRICING_SIDES = ['ask', 'bid', 'same', 'other'] PRICING_SIDES = ['ask', 'bid', 'same', 'other']
ORDERTYPE_POSSIBILITIES = ['limit', 'market'] 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', HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',

View File

@ -23,8 +23,7 @@ class Binance(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "stop_loss_limit"}, "stoploss_order_types": {"limit": "stop_loss_limit"},
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['GTC', 'FOK', 'IOC'],
"time_in_force_parameter": "timeInForce",
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",

View File

@ -62,7 +62,7 @@ class Exchange:
# or by specifying them in the configuration. # or by specifying them in the configuration.
_ft_has_default: Dict = { _ft_has_default: Dict = {
"stoploss_on_exchange": False, "stoploss_on_exchange": False,
"order_time_in_force": ["gtc"], "order_time_in_force": ["GTC"],
"time_in_force_parameter": "timeInForce", "time_in_force_parameter": "timeInForce",
"ohlcv_params": {}, "ohlcv_params": {},
"ohlcv_candle_limit": 500, "ohlcv_candle_limit": 500,
@ -611,7 +611,7 @@ class Exchange:
""" """
Checks if order time in force configured in strategy/config are supported 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()): for k, v in order_time_in_force.items()):
raise OperationalException( raise OperationalException(
f'Time in force policies are not supported for {self.name} yet.') f'Time in force policies are not supported for {self.name} yet.')
@ -989,12 +989,12 @@ class Exchange:
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = self._params.copy() 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', '') param = self._ft_has.get('time_in_force_parameter', '')
params.update({param: time_in_force}) params.update({param: time_in_force.upper()})
if reduceOnly: if reduceOnly:
params.update({'reduceOnly': True}) params.update({'reduceOnly': True})
return params return params
@ -1009,7 +1009,7 @@ class Exchange:
rate: float, rate: float,
leverage: float, leverage: float,
reduceOnly: bool = False, reduceOnly: bool = False,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order( dry_order = self.create_dry_run_order(

View File

@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
class Ftx(Exchange): class Ftx(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"order_time_in_force": ['GTC', 'IOC', 'PO'],
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
"ohlcv_require_since": True, "ohlcv_require_since": True,

View File

@ -25,8 +25,7 @@ class Gateio(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 1000, "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_order_types": {"limit": "limit"},
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
} }
@ -57,7 +56,7 @@ class Gateio(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,
@ -69,7 +68,7 @@ class Gateio(Exchange):
if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES: if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES:
params['type'] = 'market' params['type'] = 'market'
param = self._ft_has.get('time_in_force_parameter', '') param = self._ft_has.get('time_in_force_parameter', '')
params.update({param: 'ioc'}) params.update({param: 'IOC'})
return params return params
def get_trades_for_order(self, order_id: str, pair: str, since: datetime, def get_trades_for_order(self, order_id: str, pair: str, since: datetime,

View File

@ -171,7 +171,7 @@ class Kraken(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc' time_in_force: str = 'GTC'
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,

View File

@ -23,8 +23,7 @@ class Kucoin(Exchange):
"stoploss_order_types": {"limit": "limit", "market": "market"}, "stoploss_order_types": {"limit": "limit", "market": "market"},
"l2_limit_range": [20, 100], "l2_limit_range": [20, 100],
"l2_limit_range_required": False, "l2_limit_range_required": False,
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['GTC', 'FOK', 'IOC'],
"time_in_force_parameter": "timeInForce",
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
} }

View File

@ -98,7 +98,7 @@ class Okx(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,

View File

@ -78,8 +78,8 @@ class IStrategy(ABC, HyperStrategyMixin):
# Optional time in force # Optional time in force
order_time_in_force: Dict = { order_time_in_force: Dict = {
'entry': 'gtc', 'entry': 'GTC',
'exit': 'gtc', 'exit': 'GTC',
} }
# run "populate_indicators" only for new candle # run "populate_indicators" only for new candle

View File

@ -88,8 +88,8 @@ class {{ strategy }}(IStrategy):
# Optional order time in force. # Optional order time in force.
order_time_in_force = { order_time_in_force = {
'entry': 'gtc', 'entry': 'GTC',
'exit': 'gtc' 'exit': 'GTC'
} }
{{ plot_config | indent(4) }} {{ plot_config | indent(4) }}

View File

@ -88,8 +88,8 @@ class SampleStrategy(IStrategy):
# Optional order time in force. # Optional order time in force.
order_time_in_force = { order_time_in_force = {
'entry': 'gtc', 'entry': 'GTC',
'exit': 'gtc' 'exit': 'GTC'
} }
plot_config = { plot_config = {

View File

@ -275,7 +275,7 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
ex.validate_order_time_in_force(tif2) ex.validate_order_time_in_force(tif2)
# Patch to see if this will pass if the values are in the ft dict # 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) 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][3] == 1
assert api_mock.create_order.call_args[0][4] == 200 assert api_mock.create_order.call_args[0][4] == 200
assert "timeInForce" in api_mock.create_order.call_args[0][5] 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' order_type = 'market'
time_in_force = 'ioc' 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][3] == 1
assert api_mock.create_order.call_args[0][4] == 200 assert api_mock.create_order.call_args[0][4] == 200
assert "timeInForce" in api_mock.create_order.call_args[0][5] 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' order_type = 'market'
time_in_force = 'ioc' time_in_force = 'IOC'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
amount=1, rate=200, leverage=1.0, amount=1, rate=200, leverage=1.0,
time_in_force=time_in_force) time_in_force=time_in_force)
@ -3319,7 +3319,7 @@ def test_merge_ft_has_dict(default_conf, mocker):
ex = Binance(default_conf) ex = Binance(default_conf)
assert ex._ft_has != Exchange._ft_has_default assert ex._ft_has != Exchange._ft_has_default
assert ex.get_option('stoploss_on_exchange') 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') == 'id'
assert ex.get_option('trades_pagination_arg') == 'fromId' assert ex.get_option('trades_pagination_arg') == 'fromId'
@ -4954,7 +4954,7 @@ def test__get_params(mocker, default_conf, exchange_name):
params1 = {'test': True} params1 = {'test': True}
params2 = { params2 = {
'test': True, 'test': True,
'timeInForce': 'ioc', 'timeInForce': 'IOC',
'reduceOnly': True, 'reduceOnly': True,
} }
@ -4969,7 +4969,7 @@ def test__get_params(mocker, default_conf, exchange_name):
side="buy", side="buy",
ordertype='market', ordertype='market',
reduceOnly=False, reduceOnly=False,
time_in_force='gtc', time_in_force='GTC',
leverage=1.0, leverage=1.0,
) == params1 ) == params1
@ -4977,7 +4977,7 @@ def test__get_params(mocker, default_conf, exchange_name):
side="buy", side="buy",
ordertype='market', ordertype='market',
reduceOnly=False, reduceOnly=False,
time_in_force='ioc', time_in_force='IOC',
leverage=1.0, leverage=1.0,
) == params1 ) == params1
@ -4985,7 +4985,7 @@ def test__get_params(mocker, default_conf, exchange_name):
side="buy", side="buy",
ordertype='limit', ordertype='limit',
reduceOnly=False, reduceOnly=False,
time_in_force='gtc', time_in_force='GTC',
leverage=1.0, leverage=1.0,
) == params1 ) == params1
@ -4998,7 +4998,7 @@ def test__get_params(mocker, default_conf, exchange_name):
side="buy", side="buy",
ordertype='limit', ordertype='limit',
reduceOnly=True, reduceOnly=True,
time_in_force='ioc', time_in_force='IOC',
leverage=3.0, leverage=3.0,
) == params2 ) == params2

View File

@ -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][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1 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][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'} 'trading_agreement': 'agree'}

View File

@ -275,8 +275,8 @@ def test_strategy_override_order_tif(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
order_time_in_force = { order_time_in_force = {
'entry': 'fok', 'entry': 'FOK',
'exit': 'gtc', 'exit': 'GTC',
} }
default_conf.update({ 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 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:" 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({ default_conf.update({
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'order_time_in_force': {'entry': 'fok'} 'order_time_in_force': {'entry': 'FOK'}
}) })
# Raise error for invalid configuration # Raise error for invalid configuration
with pytest.raises(ImportError, with pytest.raises(ImportError,

View File

@ -973,17 +973,17 @@ def test_validate_time_in_force(default_conf, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['order_time_in_force'] = { conf['order_time_in_force'] = {
'buy': 'gtc', 'buy': 'gtc',
'sell': 'gtc', 'sell': 'GTC',
} }
validate_config_consistency(conf) validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for time_in_force is.*", caplog) 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']['entry'] == 'gtc'
assert conf['order_time_in_force']['exit'] == 'gtc' assert conf['order_time_in_force']['exit'] == 'GTC'
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['order_time_in_force'] = { conf['order_time_in_force'] = {
'buy': 'gtc', 'buy': 'GTC',
'sell': 'gtc', 'sell': 'GTC',
} }
conf['trading_mode'] = 'futures' conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException, with pytest.raises(OperationalException,