commit
5d3f2523e3
@ -8,8 +8,8 @@
|
|||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
},
|
},
|
||||||
"stoploss": -0.10,
|
"stoploss": -0.10,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -32,8 +32,8 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
|
|||||||
* Call `populate_entry_trend()`
|
* Call `populate_entry_trend()`
|
||||||
* Call `populate_exit_trend()`
|
* Call `populate_exit_trend()`
|
||||||
* Check timeouts for open orders.
|
* Check timeouts for open orders.
|
||||||
* Calls `check_buy_timeout()` strategy callback for open entry orders.
|
* Calls `check_entry_timeout()` strategy callback for open entry orders.
|
||||||
* Calls `check_sell_timeout()` strategy callback for open exit orders.
|
* Calls `check_exit_timeout()` strategy callback for open exit orders.
|
||||||
* Verifies existing positions and eventually places exit orders.
|
* Verifies existing positions and eventually places exit orders.
|
||||||
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
||||||
* Determine exit-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback.
|
* Determine exit-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback.
|
||||||
@ -64,7 +64,7 @@ This loop will be repeated again and again until the bot is stopped.
|
|||||||
* Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
|
* Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
|
||||||
* Call `custom_stoploss()` and `custom_exit()` to find custom exit points.
|
* Call `custom_stoploss()` and `custom_exit()` to find custom exit points.
|
||||||
* For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
|
* For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
|
||||||
* Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_buy_timeout()` / `check_sell_timeout()` strategy callbacks.
|
* Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks.
|
||||||
* Generate backtest report output
|
* Generate backtest report output
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
@ -102,8 +102,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md). <br>*Defaults to `"spot"`.* <br> **Datatype:** String
|
| `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md). <br>*Defaults to `"spot"`.* <br> **Datatype:** String
|
||||||
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
|
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
|
||||||
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
|
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
|
||||||
| `unfilledtimeout.buy` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.sell` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
|
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
|
||||||
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
||||||
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
|
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
|
||||||
|
@ -12,7 +12,7 @@ Currently available callbacks:
|
|||||||
* [`custom_exit()`](#custom-exit-signal)
|
* [`custom_exit()`](#custom-exit-signal)
|
||||||
* [`custom_stoploss()`](#custom-stoploss)
|
* [`custom_stoploss()`](#custom-stoploss)
|
||||||
* [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules)
|
* [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules)
|
||||||
* [`check_buy_timeout()` and `check_sell_timeout()`](#custom-order-timeout-rules)
|
* [`check_entry_timeout()` and `check_exit_timeout()`](#custom-order-timeout-rules)
|
||||||
* [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation)
|
* [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation)
|
||||||
* [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation)
|
* [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation)
|
||||||
* [`adjust_trade_position()`](#adjust-trade-position)
|
* [`adjust_trade_position()`](#adjust-trade-position)
|
||||||
@ -408,7 +408,7 @@ However, freqtrade also offers a custom callback for both order types, which all
|
|||||||
### Custom order timeout example
|
### Custom order timeout example
|
||||||
|
|
||||||
Called for every open order until that order is either filled or cancelled.
|
Called for every open order until that order is either filled or cancelled.
|
||||||
`check_buy_timeout()` is called for trade entries, while `check_sell_timeout()` is called for trade exit orders.
|
`check_entry_timeout()` is called for trade entries, while `check_exit_timeout()` is called for trade exit orders.
|
||||||
|
|
||||||
A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below.
|
A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below.
|
||||||
It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins.
|
It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins.
|
||||||
@ -425,11 +425,11 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
||||||
unfilledtimeout = {
|
unfilledtimeout = {
|
||||||
'buy': 60 * 25,
|
'entry': 60 * 25,
|
||||||
'sell': 60 * 25
|
'exit': 60 * 25
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict,
|
def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@ -440,7 +440,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
|
def check_exit_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@ -466,11 +466,11 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
||||||
unfilledtimeout = {
|
unfilledtimeout = {
|
||||||
'buy': 60 * 25,
|
'entry': 60 * 25,
|
||||||
'sell': 60 * 25
|
'exit': 60 * 25
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
|
def check_entry_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['bids'][0][0]
|
current_price = ob['bids'][0][0]
|
||||||
@ -480,7 +480,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
|
def check_exit_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['asks'][0][0]
|
current_price = ob['asks'][0][0]
|
||||||
|
@ -11,6 +11,8 @@ If you intend on using markets other than spot markets, please migrate your stra
|
|||||||
* `populate_buy_trend()` -> `populate_entry_trend()`
|
* `populate_buy_trend()` -> `populate_entry_trend()`
|
||||||
* `populate_sell_trend()` -> `populate_exit_trend()`
|
* `populate_sell_trend()` -> `populate_exit_trend()`
|
||||||
* `custom_sell()` -> `custom_exit()`
|
* `custom_sell()` -> `custom_exit()`
|
||||||
|
* `check_buy_timeout()` -> `check_entry_timeout()`
|
||||||
|
* `check_sell_timeout()` -> `check_exit_timeout()`
|
||||||
* Dataframe columns:
|
* Dataframe columns:
|
||||||
* `buy` -> `enter_long`
|
* `buy` -> `enter_long`
|
||||||
* `sell` -> `exit_long`
|
* `sell` -> `exit_long`
|
||||||
@ -30,6 +32,7 @@ If you intend on using markets other than spot markets, please migrate your stra
|
|||||||
* Strategy/Configuration settings.
|
* Strategy/Configuration settings.
|
||||||
* `order_time_in_force` buy -> entry, sell -> exit.
|
* `order_time_in_force` buy -> entry, sell -> exit.
|
||||||
* `order_types` buy -> entry, sell -> exit.
|
* `order_types` buy -> entry, sell -> exit.
|
||||||
|
* `unfilledtimeout` buy -> entry, sell -> exit.
|
||||||
|
|
||||||
## Extensive explanation
|
## Extensive explanation
|
||||||
|
|
||||||
@ -124,6 +127,32 @@ class AwesomeStrategy(IStrategy):
|
|||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `custom_entry_timeout`
|
||||||
|
|
||||||
|
`check_buy_timeout()` has been renamed to `check_entry_timeout()`, and `check_sell_timeout()` has been renamed to `check_exit_timeout()`.
|
||||||
|
|
||||||
|
``` python hl_lines="2 6"
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
``` python hl_lines="2 6"
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
### Custom-stake-amount
|
### Custom-stake-amount
|
||||||
|
|
||||||
New string argument `side` - which can be either `"long"` or `"short"`.
|
New string argument `side` - which can be either `"long"` or `"short"`.
|
||||||
@ -259,6 +288,7 @@ This should be given the value of `trade.is_short`.
|
|||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": false,
|
"stoploss_on_exchange": false,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
``` python hl_lines="2-6"
|
``` python hl_lines="2-6"
|
||||||
@ -271,4 +301,27 @@ This should be given the value of `trade.is_short`.
|
|||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": false,
|
"stoploss_on_exchange": false,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `unfilledtimeout`
|
||||||
|
|
||||||
|
`unfilledtimeout` have changed all wordings from `buy` to `entry` - and `sell` to `exit`.
|
||||||
|
|
||||||
|
``` python hl_lines="2-3"
|
||||||
|
unfilledtimeout = {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 10,
|
||||||
|
"exit_timeout_count": 0,
|
||||||
|
"unit": "minutes"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` python hl_lines="2-3"
|
||||||
|
unfilledtimeout = {
|
||||||
|
"entry": 10,
|
||||||
|
"exit": 10,
|
||||||
|
"exit_timeout_count": 0,
|
||||||
|
"unit": "minutes"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
@ -216,6 +216,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
_validate_time_in_force(conf)
|
_validate_time_in_force(conf)
|
||||||
_validate_order_types(conf)
|
_validate_order_types(conf)
|
||||||
|
_validate_unfilledtimeout(conf)
|
||||||
|
|
||||||
|
|
||||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
||||||
@ -258,3 +259,23 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
|
|||||||
]:
|
]:
|
||||||
|
|
||||||
process_deprecated_setting(conf, 'order_types', o, 'order_types', n)
|
process_deprecated_setting(conf, 'order_types', o, 'order_types', n)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
||||||
|
unfilledtimeout = conf.get('unfilledtimeout', {})
|
||||||
|
if any(x in unfilledtimeout for x in ['buy', 'sell']):
|
||||||
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
|
raise OperationalException(
|
||||||
|
"Please migrate your unfilledtimeout settings to use the new wording.")
|
||||||
|
else:
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is deprecated."
|
||||||
|
"Please migrate your unfilledtimeout settings to use 'entry' and 'exit' wording."
|
||||||
|
)
|
||||||
|
for o, n in [
|
||||||
|
('buy', 'entry'),
|
||||||
|
('sell', 'exit'),
|
||||||
|
]:
|
||||||
|
|
||||||
|
process_deprecated_setting(conf, 'unfilledtimeout', o, 'unfilledtimeout', n)
|
||||||
|
@ -165,8 +165,8 @@ CONF_SCHEMA = {
|
|||||||
'unfilledtimeout': {
|
'unfilledtimeout': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'buy': {'type': 'number', 'minimum': 1},
|
'entry': {'type': 'number', 'minimum': 1},
|
||||||
'sell': {'type': 'number', 'minimum': 1},
|
'exit': {'type': 'number', 'minimum': 1},
|
||||||
'exit_timeout_count': {'type': 'number', 'minimum': 0, 'default': 0},
|
'exit_timeout_count': {'type': 'number', 'minimum': 0, 'default': 0},
|
||||||
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
|
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
|
||||||
}
|
}
|
||||||
|
@ -1138,13 +1138,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
|
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
|
||||||
is_entering = order['side'] == trade.enter_side
|
is_entering = order['side'] == trade.enter_side
|
||||||
not_closed = order['status'] == 'open' or fully_cancelled
|
not_closed = order['status'] == 'open' or fully_cancelled
|
||||||
time_method = 'sell' if order['side'] == 'sell' else 'buy'
|
|
||||||
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
|
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
|
||||||
|
|
||||||
order_obj = trade.select_order_by_order_id(trade.open_order_id)
|
order_obj = trade.select_order_by_order_id(trade.open_order_id)
|
||||||
|
|
||||||
if not_closed and (fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
|
if not_closed and (fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
|
||||||
time_method, trade, order_obj, datetime.now(timezone.utc)))
|
trade, order_obj, datetime.now(timezone.utc)))
|
||||||
):
|
):
|
||||||
if is_entering:
|
if is_entering:
|
||||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
|
@ -853,7 +853,7 @@ class Backtesting:
|
|||||||
"""
|
"""
|
||||||
for order in [o for o in trade.orders if o.ft_is_open]:
|
for order in [o for o in trade.orders if o.ft_is_open]:
|
||||||
|
|
||||||
timedout = self.strategy.ft_check_timed_out(order.side, trade, order, current_time)
|
timedout = self.strategy.ft_check_timed_out(trade, order, current_time)
|
||||||
if timedout:
|
if timedout:
|
||||||
if order.side == trade.enter_side:
|
if order.side == trade.enter_side:
|
||||||
self.timedout_entry_orders += 1
|
self.timedout_entry_orders += 1
|
||||||
|
@ -169,6 +169,51 @@ class StrategyResolver(IResolver):
|
|||||||
" in your strategy. Please note that short signals will be ignored in that case."
|
" in your strategy. Please note that short signals will be ignored in that case."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_strategy(strategy: IStrategy) -> IStrategy:
|
||||||
|
if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
|
# Require new method
|
||||||
|
if not check_override(strategy, IStrategy, 'populate_entry_trend'):
|
||||||
|
raise OperationalException("`populate_entry_trend` must be implemented.")
|
||||||
|
if not check_override(strategy, IStrategy, 'populate_exit_trend'):
|
||||||
|
raise OperationalException("`populate_exit_trend` must be implemented.")
|
||||||
|
if check_override(strategy, IStrategy, 'check_buy_timeout'):
|
||||||
|
raise OperationalException("Please migrate your implementation "
|
||||||
|
"of `check_buy_timeout` to `check_entry_timeout`.")
|
||||||
|
if check_override(strategy, IStrategy, 'check_sell_timeout'):
|
||||||
|
raise OperationalException("Please migrate your implementation "
|
||||||
|
"of `check_sell_timeout` to `check_exit_timeout`.")
|
||||||
|
|
||||||
|
if check_override(strategy, IStrategy, 'custom_sell'):
|
||||||
|
raise OperationalException(
|
||||||
|
"Please migrate your implementation of `custom_sell` to `custom_exit`.")
|
||||||
|
else:
|
||||||
|
# TODO: Implementing one of the following methods should show a deprecation warning
|
||||||
|
# buy_trend and sell_trend, custom_sell
|
||||||
|
if (
|
||||||
|
not check_override(strategy, IStrategy, 'populate_buy_trend')
|
||||||
|
and not check_override(strategy, IStrategy, 'populate_entry_trend')
|
||||||
|
):
|
||||||
|
raise OperationalException(
|
||||||
|
"`populate_entry_trend` or `populate_buy_trend` must be implemented.")
|
||||||
|
if (
|
||||||
|
not check_override(strategy, IStrategy, 'populate_sell_trend')
|
||||||
|
and not check_override(strategy, IStrategy, 'populate_exit_trend')
|
||||||
|
):
|
||||||
|
raise OperationalException(
|
||||||
|
"`populate_exit_trend` or `populate_sell_trend` must be implemented.")
|
||||||
|
|
||||||
|
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
|
||||||
|
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
||||||
|
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
||||||
|
if any(x == 2 for x in [
|
||||||
|
strategy._populate_fun_len,
|
||||||
|
strategy._buy_fun_len,
|
||||||
|
strategy._sell_fun_len
|
||||||
|
]):
|
||||||
|
strategy.INTERFACE_VERSION = 1
|
||||||
|
return strategy
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_strategy(strategy_name: str,
|
def _load_strategy(strategy_name: str,
|
||||||
config: dict, extra_dir: Optional[str] = None) -> IStrategy:
|
config: dict, extra_dir: Optional[str] = None) -> IStrategy:
|
||||||
@ -208,42 +253,8 @@ class StrategyResolver(IResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if strategy:
|
if strategy:
|
||||||
if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
|
||||||
# Require new method
|
|
||||||
if not check_override(strategy, IStrategy, 'populate_entry_trend'):
|
|
||||||
raise OperationalException("`populate_entry_trend` must be implemented.")
|
|
||||||
if not check_override(strategy, IStrategy, 'populate_exit_trend'):
|
|
||||||
raise OperationalException("`populate_exit_trend` must be implemented.")
|
|
||||||
if check_override(strategy, IStrategy, 'custom_sell'):
|
|
||||||
raise OperationalException(
|
|
||||||
"Please migrate your implementation of `custom_sell` to `custom_exit`.")
|
|
||||||
else:
|
|
||||||
# TODO: Implementing one of the following methods should show a deprecation warning
|
|
||||||
# buy_trend and sell_trend, custom_sell
|
|
||||||
if (
|
|
||||||
not check_override(strategy, IStrategy, 'populate_buy_trend')
|
|
||||||
and not check_override(strategy, IStrategy, 'populate_entry_trend')
|
|
||||||
):
|
|
||||||
raise OperationalException(
|
|
||||||
"`populate_entry_trend` or `populate_buy_trend` must be implemented.")
|
|
||||||
if (
|
|
||||||
not check_override(strategy, IStrategy, 'populate_sell_trend')
|
|
||||||
and not check_override(strategy, IStrategy, 'populate_exit_trend')
|
|
||||||
):
|
|
||||||
raise OperationalException(
|
|
||||||
"`populate_exit_trend` or `populate_sell_trend` must be implemented.")
|
|
||||||
|
|
||||||
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
|
return StrategyResolver.validate_strategy(strategy)
|
||||||
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
|
||||||
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
|
||||||
if any(x == 2 for x in [
|
|
||||||
strategy._populate_fun_len,
|
|
||||||
strategy._buy_fun_len,
|
|
||||||
strategy._sell_fun_len
|
|
||||||
]):
|
|
||||||
strategy.INTERFACE_VERSION = 1
|
|
||||||
|
|
||||||
return strategy
|
|
||||||
|
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
|
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
|
||||||
|
@ -131,8 +131,8 @@ class Daily(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class UnfilledTimeout(BaseModel):
|
class UnfilledTimeout(BaseModel):
|
||||||
buy: Optional[int]
|
entry: Optional[int]
|
||||||
sell: Optional[int]
|
exit: Optional[int]
|
||||||
unit: Optional[str]
|
unit: Optional[str]
|
||||||
exit_timeout_count: Optional[int]
|
exit_timeout_count: Optional[int]
|
||||||
|
|
||||||
|
@ -209,7 +209,14 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
|
def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Check buy timeout function callback.
|
DEPRECATED: Please use `check_entry_timeout` instead.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_entry_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
Check entry timeout function callback.
|
||||||
This method can be used to override the enter-timeout.
|
This method can be used to override the enter-timeout.
|
||||||
It is called whenever a limit entry order has been created,
|
It is called whenever a limit entry order has been created,
|
||||||
and is not yet fully filled.
|
and is not yet fully filled.
|
||||||
@ -224,11 +231,19 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the entry order is cancelled.
|
:return bool: When True is returned, then the entry order is cancelled.
|
||||||
"""
|
"""
|
||||||
return False
|
return self.check_buy_timeout(
|
||||||
|
pair=pair, trade=trade, order=order, current_time=current_time)
|
||||||
|
|
||||||
def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
|
def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
|
DEPRECATED: Please use `check_exit_timeout` instead.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_exit_timeout(self, pair: str, trade: Trade, order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
Check sell timeout function callback.
|
Check sell timeout function callback.
|
||||||
This method can be used to override the exit-timeout.
|
This method can be used to override the exit-timeout.
|
||||||
It is called whenever a (long) limit sell order or (short) limit buy
|
It is called whenever a (long) limit sell order or (short) limit buy
|
||||||
@ -244,7 +259,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled.
|
:return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled.
|
||||||
"""
|
"""
|
||||||
return False
|
return self.check_sell_timeout(
|
||||||
|
pair=pair, trade=trade, order=order, current_time=current_time)
|
||||||
|
|
||||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||||
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
|
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
|
||||||
@ -1023,22 +1039,24 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
else:
|
else:
|
||||||
return current_profit > roi
|
return current_profit > roi
|
||||||
|
|
||||||
def ft_check_timed_out(self, side: str, trade: LocalTrade, order: Order,
|
def ft_check_timed_out(self, trade: LocalTrade, order: Order,
|
||||||
current_time: datetime) -> bool:
|
current_time: datetime) -> bool:
|
||||||
"""
|
"""
|
||||||
FT Internal method.
|
FT Internal method.
|
||||||
Check if timeout is active, and if the order is still open and timed out
|
Check if timeout is active, and if the order is still open and timed out
|
||||||
"""
|
"""
|
||||||
|
side = 'entry' if order.ft_order_side == trade.enter_side else 'exit'
|
||||||
|
|
||||||
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
|
timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
|
||||||
timeout_kwargs = {timeout_unit: -timeout}
|
timeout_kwargs = {timeout_unit: -timeout}
|
||||||
timeout_threshold = current_time + timedelta(**timeout_kwargs)
|
timeout_threshold = current_time + timedelta(**timeout_kwargs)
|
||||||
timedout = (order.status == 'open' and order.side == side
|
timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold)
|
||||||
and order.order_date_utc < timeout_threshold)
|
|
||||||
if timedout:
|
if timedout:
|
||||||
return True
|
return True
|
||||||
time_method = self.check_sell_timeout if order.side == 'sell' else self.check_buy_timeout
|
time_method = (self.check_exit_timeout if order.side == trade.exit_side
|
||||||
|
else self.check_entry_timeout)
|
||||||
|
|
||||||
return strategy_safe_wrapper(time_method,
|
return strategy_safe_wrapper(time_method,
|
||||||
default_retval=False)(
|
default_retval=False)(
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
"trading_mode": "{{ trading_mode }}",
|
"trading_mode": "{{ trading_mode }}",
|
||||||
"margin_mode": "{{ margin_mode }}",
|
"margin_mode": "{{ margin_mode }}",
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 10,
|
"exit": 10,
|
||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
|
@ -170,11 +170,11 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
|
def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Check buy timeout function callback.
|
Check entry timeout function callback.
|
||||||
This method can be used to override the buy-timeout.
|
This method can be used to override the entry-timeout.
|
||||||
It is called whenever a limit buy order has been created,
|
It is called whenever a limit entry order has been created,
|
||||||
and is not yet fully filled.
|
and is not yet fully filled.
|
||||||
Configuration options in `unfilledtimeout` will be verified before this,
|
Configuration options in `unfilledtimeout` will be verified before this,
|
||||||
so ensure to set these timeouts high enough.
|
so ensure to set these timeouts high enough.
|
||||||
@ -190,11 +190,11 @@ def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) ->
|
|||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
|
def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Check sell timeout function callback.
|
Check exit timeout function callback.
|
||||||
This method can be used to override the sell-timeout.
|
This method can be used to override the exit-timeout.
|
||||||
It is called whenever a limit sell order has been created,
|
It is called whenever a limit exit order has been created,
|
||||||
and is not yet fully filled.
|
and is not yet fully filled.
|
||||||
Configuration options in `unfilledtimeout` will be verified before this,
|
Configuration options in `unfilledtimeout` will be verified before this,
|
||||||
so ensure to set these timeouts high enough.
|
so ensure to set these timeouts high enough.
|
||||||
|
@ -416,8 +416,8 @@ def get_default_conf(testdatadir):
|
|||||||
"dry_run_wallet": 1000,
|
"dry_run_wallet": 1000,
|
||||||
"stoploss": -0.10,
|
"stoploss": -0.10,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"entry": 10,
|
||||||
"sell": 30
|
"exit": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
|
@ -29,3 +29,21 @@ class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
|
|||||||
current_rate: float, current_profit: float,
|
current_rate: float, current_profit: float,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell):
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
return super().populate_exit_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
def check_buy_timeout(self, pair: str, trade, order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell):
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
return super().populate_exit_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
def check_sell_timeout(self, pair: str, trade, order: dict,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
|
return False
|
||||||
|
@ -418,11 +418,20 @@ def test_missing_implements(default_conf):
|
|||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
default_conf['strategy'] = 'TestStrategyImplementCustomSell'
|
default_conf['strategy'] = 'TestStrategyImplementCustomSell'
|
||||||
|
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r"Please migrate your implementation of `custom_sell`.*"):
|
match=r"Please migrate your implementation of `custom_sell`.*"):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
default_conf['strategy'] = 'TestStrategyImplementBuyTimeout'
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"Please migrate your implementation of `check_buy_timeout`.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
default_conf['strategy'] = 'TestStrategyImplementSellTimeout'
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"Please migrate your implementation of `check_sell_timeout`.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
def test_call_deprecated_function(result, default_conf, caplog):
|
def test_call_deprecated_function(result, default_conf, caplog):
|
||||||
|
@ -963,7 +963,7 @@ def test_validate_time_in_force(default_conf, caplog) -> None:
|
|||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_order_types(default_conf, caplog) -> None:
|
def test__validate_order_types(default_conf, caplog) -> None:
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['order_types'] = {
|
conf['order_types'] = {
|
||||||
'buy': 'limit',
|
'buy': 'limit',
|
||||||
@ -998,6 +998,31 @@ def test_validate_order_types(default_conf, caplog) -> None:
|
|||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test__validate_unfilledtimeout(default_conf, caplog) -> None:
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['unfilledtimeout'] = {
|
||||||
|
'buy': 30,
|
||||||
|
'sell': 35,
|
||||||
|
}
|
||||||
|
validate_config_consistency(conf)
|
||||||
|
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is.*", caplog)
|
||||||
|
assert conf['unfilledtimeout']['entry'] == 30
|
||||||
|
assert conf['unfilledtimeout']['exit'] == 35
|
||||||
|
assert 'buy' not in conf['unfilledtimeout']
|
||||||
|
assert 'sell' not in conf['unfilledtimeout']
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['unfilledtimeout'] = {
|
||||||
|
'buy': 30,
|
||||||
|
'sell': 35,
|
||||||
|
}
|
||||||
|
conf['trading_mode'] = 'futures'
|
||||||
|
with pytest.raises(
|
||||||
|
OperationalException,
|
||||||
|
match=r"Please migrate your unfilledtimeout settings to use the new wording\."):
|
||||||
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_test_comments() -> None:
|
def test_load_config_test_comments() -> None:
|
||||||
"""
|
"""
|
||||||
Load config with comments
|
Load config with comments
|
||||||
|
@ -2370,7 +2370,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
def test_check_handle_timedout_buy_usercustom(
|
def test_check_handle_timedout_entry_usercustom(
|
||||||
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
||||||
limit_sell_order_old, fee, mocker, is_short
|
limit_sell_order_old, fee, mocker, is_short
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -2378,8 +2378,7 @@ def test_check_handle_timedout_buy_usercustom(
|
|||||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||||
old_order['id'] = open_trade.open_order_id
|
old_order['id'] = open_trade.open_order_id
|
||||||
|
|
||||||
default_conf_usdt["unfilledtimeout"] = {"buy": 30,
|
default_conf_usdt["unfilledtimeout"] = {"entry": 1400, "exit": 30}
|
||||||
"sell": 1400} if is_short else {"buy": 1400, "sell": 30}
|
|
||||||
|
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
cancel_order_mock = MagicMock(return_value=old_order)
|
cancel_order_mock = MagicMock(return_value=old_order)
|
||||||
@ -2399,6 +2398,7 @@ def test_check_handle_timedout_buy_usercustom(
|
|||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
open_trade.is_short = is_short
|
open_trade.is_short = is_short
|
||||||
open_trade.orders[0].side = 'sell' if is_short else 'buy'
|
open_trade.orders[0].side = 'sell' if is_short else 'buy'
|
||||||
|
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
|
||||||
Trade.query.session.add(open_trade)
|
Trade.query.session.add(open_trade)
|
||||||
|
|
||||||
# Ensure default is to return empty (so not mocked yet)
|
# Ensure default is to return empty (so not mocked yet)
|
||||||
@ -2406,34 +2406,23 @@ def test_check_handle_timedout_buy_usercustom(
|
|||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
|
|
||||||
# Return false - trade remains open
|
# Return false - trade remains open
|
||||||
if is_short:
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
|
||||||
else:
|
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 1
|
assert nb_trades == 1
|
||||||
if is_short:
|
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == 1
|
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
|
||||||
# Raise Keyerror ... (no impact on trade)
|
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
|
|
||||||
else:
|
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == 1
|
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
|
|
||||||
|
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 1
|
assert nb_trades == 1
|
||||||
if is_short:
|
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == 1
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
|
|
||||||
else:
|
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == 1
|
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
|
|
||||||
# Trade should be closed since the function returns true
|
# Trade should be closed since the function returns true
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_wr_mock.call_count == 1
|
assert cancel_order_wr_mock.call_count == 1
|
||||||
@ -2441,10 +2430,7 @@ def test_check_handle_timedout_buy_usercustom(
|
|||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 0
|
assert nb_trades == 0
|
||||||
if is_short:
|
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == 1
|
|
||||||
else:
|
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
@ -2472,9 +2458,9 @@ def test_check_handle_timedout_buy(
|
|||||||
Trade.query.session.add(open_trade)
|
Trade.query.session.add(open_trade)
|
||||||
|
|
||||||
if is_short:
|
if is_short:
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
|
||||||
else:
|
else:
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||||
# check it does cancel buy orders over the time limit
|
# check it does cancel buy orders over the time limit
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
@ -2484,9 +2470,9 @@ def test_check_handle_timedout_buy(
|
|||||||
assert nb_trades == 0
|
assert nb_trades == 0
|
||||||
# Custom user buy-timeout is never called
|
# Custom user buy-timeout is never called
|
||||||
if is_short:
|
if is_short:
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == 0
|
assert freqtrade.strategy.check_exit_timeout.call_count == 0
|
||||||
else:
|
else:
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == 0
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
@ -2553,11 +2539,11 @@ def test_check_handle_timedout_buy_exception(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
def test_check_handle_timedout_sell_usercustom(
|
def test_check_handle_timedout_exit_usercustom(
|
||||||
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
|
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
|
||||||
is_short, open_trade_usdt, caplog
|
is_short, open_trade_usdt, caplog
|
||||||
) -> None:
|
) -> None:
|
||||||
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
|
default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1}
|
||||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||||
if is_short:
|
if is_short:
|
||||||
limit_sell_order_old['side'] = 'buy'
|
limit_sell_order_old['side'] = 'buy'
|
||||||
@ -2585,35 +2571,35 @@ def test_check_handle_timedout_sell_usercustom(
|
|||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
|
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||||
# Return false - No impact
|
# Return false - No impact
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
assert rpc_mock.call_count == 0
|
assert rpc_mock.call_count == 0
|
||||||
assert open_trade_usdt.is_open is False
|
assert open_trade_usdt.is_open is False
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1)
|
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0)
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
|
freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError)
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
|
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
|
||||||
# Return Error - No impact
|
# Return Error - No impact
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
assert rpc_mock.call_count == 0
|
assert rpc_mock.call_count == 0
|
||||||
assert open_trade_usdt.is_open is False
|
assert open_trade_usdt.is_open is False
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1)
|
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0)
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
# Return True - sells!
|
# Return True - sells!
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
|
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True)
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
assert open_trade_usdt.is_open is True
|
assert open_trade_usdt.is_open is True
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1)
|
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0)
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
# 2nd canceled trade - Fail execute sell
|
# 2nd canceled trade - Fail execute sell
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
@ -2665,16 +2651,16 @@ def test_check_handle_timedout_sell(
|
|||||||
|
|
||||||
Trade.query.session.add(open_trade_usdt)
|
Trade.query.session.add(open_trade_usdt)
|
||||||
|
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||||
# check it does cancel sell orders over the time limit
|
# check it does cancel sell orders over the time limit
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
assert open_trade_usdt.is_open is True
|
assert open_trade_usdt.is_open is True
|
||||||
# Custom user sell-timeout is never called
|
# Custom user sell-timeout is never called
|
||||||
assert freqtrade.strategy.check_sell_timeout.call_count == 0
|
assert freqtrade.strategy.check_exit_timeout.call_count == 0
|
||||||
assert freqtrade.strategy.check_buy_timeout.call_count == 0
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
|
Loading…
Reference in New Issue
Block a user