diff --git a/config.json.example b/config.json.example
index dc2bd6beb..c1686a4ac 100644
--- a/config.json.example
+++ b/config.json.example
@@ -7,13 +7,17 @@
"ticker_interval" : "5m",
"dry_run": false,
"trailing_stop": false,
- "unfilledtimeout": {
- "buy": 10,
- "sell": 30
+ "unfilled_timeout": {
+ "buy": {
+ "after": 10,
+ "if_buy_signal_still_valid": false
+ },
+ "sell": {
+ "after": 30
+ }
},
"bid_strategy": {
"ask_last_balance": 0.0,
- "timeout_even_if_buy_signal_valid": true,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
diff --git a/config_binance.json.example b/config_binance.json.example
index b5ed62488..7c884feeb 100644
--- a/config_binance.json.example
+++ b/config_binance.json.example
@@ -7,13 +7,17 @@
"ticker_interval" : "5m",
"dry_run": true,
"trailing_stop": false,
- "unfilledtimeout": {
- "buy": 10,
- "sell": 30
+ "unfilled_timeout": {
+ "buy": {
+ "after": 10,
+ "if_buy_signal_still_valid": false
+ },
+ "sell": {
+ "after": 30
+ }
},
"bid_strategy": {
"use_order_book": false,
- "timeout_even_if_buy_signal_valid": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
diff --git a/config_full.json.example b/config_full.json.example
index f191e7b77..79adde9da 100644
--- a/config_full.json.example
+++ b/config_full.json.example
@@ -20,13 +20,17 @@
"0": 0.04
},
"stoploss": -0.10,
- "unfilledtimeout": {
- "buy": 10,
- "sell": 30
+ "unfilled_timeout": {
+ "buy": {
+ "after": 10,
+ "if_buy_signal_still_valid": false
+ },
+ "sell": {
+ "after": 30
+ }
},
"bid_strategy": {
"use_order_book": false,
- "timeout_even_if_buy_signal_valid": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
diff --git a/config_kraken.json.example b/config_kraken.json.example
index 7f64c3402..4b2520455 100644
--- a/config_kraken.json.example
+++ b/config_kraken.json.example
@@ -7,13 +7,17 @@
"ticker_interval" : "5m",
"dry_run": true,
"trailing_stop": false,
- "unfilledtimeout": {
- "buy": 10,
- "sell": 30
+ "unfilled_timeout": {
+ "buy": {
+ "after": 10,
+ "if_buy_signal_still_valid": false
+ },
+ "sell": {
+ "after": 30
+ }
},
"bid_strategy": {
"use_order_book": false,
- "timeout_even_if_buy_signal_valid": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
diff --git a/docs/configuration.md b/docs/configuration.md
index 2f13afaa3..b3123cb10 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -58,10 +58,10 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Float*
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).*
***Datatype:*** *Float*
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean*
-| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer*
-| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer*
+| `unfilled_timeout.buy.after` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer*
+| `unfilled_timeout.buy.if_buy_signal_still_valid` | **Required.** Choose whether you would prefer unfilled buy orders to [timeout if buy signal was still valid](#timeout-even-if-buy-signal-valid). If false, your unfilled buy orders won't get timed out, so use this option wisely.
*Defaults to `true`.*
***Datatype:*** *Boolean*
+| `unfilled_timeout.sell.after` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer*
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
-| `bid_strategy.timeout_even_if_buy_signal_valid` | **Required.** Choose whether you would prefer buy orders to [timeout even if buy signal was still valid](#timeout-even-if-buy-signal-valid). If false, your buy orders will not timeout and will ignore your unfilledtimeout for buy orders, so use this option wisely.
*Defaults to `true`.*
***Datatype:*** *Boolean*
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
***Datatype:*** *Boolean*
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
***Datatype:*** *Positive Integer*
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
***Datatype:*** *Boolean*
@@ -128,7 +128,7 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force`
* `stake_currency`
* `stake_amount`
-* `unfilledtimeout`
+* `unfilled_timeout`
* `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy)
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index eae7f4d8d..8c3ccdc6a 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -94,11 +94,22 @@ CONF_SCHEMA = {
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_only_offset_is_reached': {'type': 'boolean'},
- 'unfilledtimeout': {
+ 'unfilled_timeout': {
'type': 'object',
'properties': {
- 'buy': {'type': 'number', 'minimum': 1},
- 'sell': {'type': 'number', 'minimum': 1}
+ 'buy': {
+ 'type': 'object',
+ 'properties': {
+ 'after': {'type': 'number', 'minimum': 1},
+ 'if_buy_signal_still_valid': {'type': 'boolean'},
+ },
+ },
+ 'sell': {
+ 'type': 'object',
+ 'properties': {
+ 'after': {'type': 'number', 'minimum': 1},
+ }
+ }
}
},
'bid_strategy': {
@@ -110,7 +121,6 @@ CONF_SCHEMA = {
'maximum': 1,
'exclusiveMaximum': False,
},
- 'timeout_even_if_buy_signal_valid': {'type': 'boolean'},
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
@@ -286,7 +296,7 @@ SCHEMA_TRADE_REQUIRED = [
'dry_run',
'dry_run_wallet',
'bid_strategy',
- 'unfilledtimeout',
+ 'unfilled_timeout',
'stoploss',
'minimal_roi',
]
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 20104f469..aaec927f3 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -785,7 +785,7 @@ class FreqtradeBot:
"""
Check if timeout is active, and if the order is still open and timed out
"""
- timeout = self.config.get('unfilledtimeout', {}).get(side)
+ timeout = self.config.get('unfilled_timeout', {}).get(side).get('after')
ordertime = arrow.get(order['datetime']).datetime
if timeout is not None:
timeout_threshold = arrow.utcnow().shift(minutes=-timeout).datetime
@@ -821,8 +821,20 @@ class FreqtradeBot:
if ((order['side'] == 'buy' and order['status'] == 'canceled')
or (self._check_timed_out('buy', order))):
- self.handle_timedout_limit_buy(trade, order)
- self.wallets.update()
+ # running get_signal on historical data fetched
+ (buy, sell) = self.strategy.get_signal(
+ trade.pair, self.strategy.ticker_interval,
+ self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
+
+ # proceed to cancel buy order by timeout if configuration
+ # unfilled_timeout.if_buy_signal_still_valid is true (original behaviour) -OR-
+ # cancel buy order only if buying condition is no longer valid OR if there's
+ # a sell signal present
+ config_buy_signal_still_valid = self.config.get('unfilled_timeout', {}) \
+ .get('buy').get('if_buy_signal_still_valid')
+ if (config_buy_signal_still_valid) or (not buy or sell):
+ self.handle_timedout_limit_buy(trade, order)
+ self.wallets.update()
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
or (self._check_timed_out('sell', order))):
@@ -846,59 +858,44 @@ class FreqtradeBot:
"""
reason = "cancelled due to timeout"
- # running get_signal on historical data fetched
- (buy, sell) = self.strategy.get_signal(
- trade.pair, self.strategy.ticker_interval,
- self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
-
- # get config for bid strategy
- config_bid_strategy = self.config.get('bid_strategy', {})
-
- # proceed to cancel buy order by timeout if timeout_even_if_buy_signal_valid
- # is true (original behaviour) -OR-
- # cancel buy order only if buying condition is no longer valid OR if there's
- # a sell signal present
- if config_bid_strategy.get('timeout_even_if_buy_signal_valid', True) or (not buy or sell):
- if order['status'] != 'canceled':
- corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
- else:
- # Order was cancelled already, so we can reuse the existing dict
- corder = order
- reason = "canceled on exchange"
-
- if corder.get('remaining', order['remaining']) == order['amount']:
- # if trade is not partially completed, just delete the trade
- self.handle_buy_order_full_cancel(trade, reason)
- return True
-
- # if trade is partially complete, edit the stake details for the trade
- # and close the order
- # cancel_order may not contain the full order dict, so we need to fallback
- # to the order dict aquired before cancelling.
- # we need to fall back to the values from order if corder does not contain these keys.
- trade.amount = order['amount'] - corder.get('remaining', order['remaining'])
- trade.stake_amount = trade.amount * trade.open_rate
- # verify if fees were taken from amount to avoid problems during selling
- try:
- new_amount = self.get_real_amount(trade, corder if 'fee' in corder else order,
- trade.amount)
- if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
- trade.amount = new_amount
- # Fee was applied, so set to 0
- trade.fee_open = 0
- trade.recalc_open_trade_price()
- except DependencyException as e:
- logger.warning("Could not update trade amount: %s", e)
-
- trade.open_order_id = None
- logger.info(f"Partial buy order timeout for {trade.pair}")
- self.rpc.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
- 'status': f"Remaining buy order for {trade.pair} cancelled due to timeout"
- })
- return False
+ if order['status'] != 'canceled':
+ corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
else:
- return False
+ # Order was cancelled already, so we can reuse the existing dict
+ corder = order
+ reason = "canceled on exchange"
+
+ if corder.get('remaining', order['remaining']) == order['amount']:
+ # if trade is not partially completed, just delete the trade
+ self.handle_buy_order_full_cancel(trade, reason)
+ return True
+
+ # if trade is partially complete, edit the stake details for the trade
+ # and close the order
+ # cancel_order may not contain the full order dict, so we need to fallback
+ # to the order dict aquired before cancelling.
+ # we need to fall back to the values from order if corder does not contain these keys.
+ trade.amount = order['amount'] - corder.get('remaining', order['remaining'])
+ trade.stake_amount = trade.amount * trade.open_rate
+ # verify if fees were taken from amount to avoid problems during selling
+ try:
+ new_amount = self.get_real_amount(trade, corder if 'fee' in corder else order,
+ trade.amount)
+ if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
+ trade.amount = new_amount
+ # Fee was applied, so set to 0
+ trade.fee_open = 0
+ trade.recalc_open_trade_price()
+ except DependencyException as e:
+ logger.warning("Could not update trade amount: %s", e)
+
+ trade.open_order_id = None
+ logger.info(f"Partial buy order timeout for {trade.pair}")
+ self.rpc.send_msg({
+ 'type': RPCMessageType.STATUS_NOTIFICATION,
+ 'status': f"Remaining buy order for {trade.pair} cancelled due to timeout"
+ })
+ return False
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
"""
diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py
index 015ba24d9..d1e068a3b 100644
--- a/freqtrade/resolvers/strategy_resolver.py
+++ b/freqtrade/resolvers/strategy_resolver.py
@@ -66,7 +66,7 @@ class StrategyResolver(IResolver):
("stake_currency", None, False),
("stake_amount", None, False),
("startup_candle_count", None, False),
- ("unfilledtimeout", None, False),
+ ("unfilled_timeout", None, False),
("use_sell_signal", True, True),
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
diff --git a/tests/conftest.py b/tests/conftest.py
index 068e42d75..349c46499 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -210,13 +210,17 @@ def default_conf(testdatadir):
"0": 0.04
},
"stoploss": -0.10,
- "unfilledtimeout": {
- "buy": 10,
- "sell": 30
+ "unfilled_timeout": {
+ "buy": {
+ "after": 10,
+ "if_buy_signal_still_valid": False
+ },
+ "sell": {
+ "after": 30
+ }
},
"bid_strategy": {
"ask_last_balance": 0.0,
- "timeout_even_if_buy_signal_valid": True,
"use_order_book": False,
"order_book_top": 1,
"check_depth_of_market": {