Uppercase TimeInForce (align with ccxt)
This commit is contained in:
parent
6686489c06
commit
104a73025d
@ -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"},
|
||||||
|
@ -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
|
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.
|
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"
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
//...
|
//...
|
||||||
|
@ -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",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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",
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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) }}
|
||||||
|
|
||||||
|
@ -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 = {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user