diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json
index ae84af420..8e622eeae 100644
--- a/config_examples/config_binance.example.json
+++ b/config_examples/config_binance.example.json
@@ -13,16 +13,18 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
- "ask_last_balance": 0.0,
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy": {
+ "exit_pricing": {
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
diff --git a/config_examples/config_bittrex.example.json b/config_examples/config_bittrex.example.json
index 1f55d43ed..d40ea6c5a 100644
--- a/config_examples/config_bittrex.example.json
+++ b/config_examples/config_bittrex.example.json
@@ -13,16 +13,18 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
- "ask_last_balance": 0.0,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy":{
+ "exit_pricing":{
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
diff --git a/config_examples/config_ftx.example.json b/config_examples/config_ftx.example.json
index fbdff3333..f86da8ea0 100644
--- a/config_examples/config_ftx.example.json
+++ b/config_examples/config_ftx.example.json
@@ -13,16 +13,18 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
- "ask_last_balance": 0.0,
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy": {
+ "exit_pricing": {
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json
index 9e4c342ed..540e83af6 100644
--- a/config_examples/config_full.example.json
+++ b/config_examples/config_full.example.json
@@ -35,20 +35,21 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
- "price_side": "bid",
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
- "ask_last_balance": 0.0,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy":{
- "price_side": "ask",
+ "exit_pricing":{
+ "price_side": "same",
"use_order_book": true,
- "order_book_top": 1
+ "order_book_top": 1,
+ "price_last_balance": 0.0
},
"order_types": {
"entry": "limit",
diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json
index 27a4979d4..69b00719a 100644
--- a/config_examples/config_kraken.example.json
+++ b/config_examples/config_kraken.example.json
@@ -13,16 +13,18 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
- "ask_last_balance": 0.0,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy":{
+ "exit_pricing":{
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
diff --git a/docs/bot-basics.md b/docs/bot-basics.md
index 30dc70d02..e45e3d9ca 100644
--- a/docs/bot-basics.md
+++ b/docs/bot-basics.md
@@ -36,12 +36,12 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
* Calls `check_exit_timeout()` strategy callback for open exit orders.
* Verifies existing positions and eventually places exit orders.
* 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 `exit_pricing` configuration setting or by using the `custom_exit_price()` callback.
* Before a exit order is placed, `confirm_trade_exit()` strategy callback is called.
* Check position adjustments for open trades if enabled by calling `adjust_trade_position()` and place additional order if required.
* Check if trade-slots are still available (if `max_open_trades` is reached).
* Verifies entry signal trying to enter new positions.
- * Determine entry-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback.
+ * Determine entry-price based on `entry_pricing` configuration setting, or by using the `custom_entry_price()` callback.
* In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage.
* Determine stake size by calling the `custom_stake_amount()` callback.
* Before an entry order is placed, `confirm_trade_entry()` strategy callback is called.
diff --git a/docs/configuration.md b/docs/configuration.md
index 3f3086833..9ed45fff3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -106,16 +106,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `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).
**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).
*Defaults to `minutes`.*
**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).
*Defaults to `0`.*
**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).
*Defaults to `bid`.*
**Datatype:** String (either `ask` or `bid`).
-| `bid_strategy.ask_last_balance` | **Required.** Interpolate the bidding price. More information [below](#buy-price-without-orderbook-enabled).
-| `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 "price_side" 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
-| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio)
-| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).
*Defaults to `ask`.*
**Datatype:** String (either `ask` or `bid`).
-| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled).
-| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean
-| `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer
+| `entry_pricing.price_side` | Select the side of the spread the bot should look at to get the entry rate. [More information below](#buy-price-side).
*Defaults to `same`.*
**Datatype:** String (either `ask`, `bid`, `same` or `other`).
+| `entry_pricing.price_last_balance` | **Required.** Interpolate the bidding price. More information [below](#entry-price-without-orderbook-enabled).
+| `entry_pricing.use_order_book` | Enable entering using the rates in [Order Book Entry](#entry-price-with-orderbook-enabled).
*Defaults to `True`.*
**Datatype:** Boolean
+| `entry_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to enter a trade. I.e. a value of 2 will allow the bot to pick the 2nd entry in [Order Book Entry](#entry-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer
+| `entry_pricing. check_depth_of_market.enabled` | Do not enter 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
+| `entry_pricing. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio)
+| `exit_pricing.price_side` | Select the side of the spread the bot should look at to get the exit rate. [More information below](#exit-price-side).
*Defaults to `same`.*
**Datatype:** String (either `ask`, `bid`, `same` or `other`).
+| `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled).
+| `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled).
*Defaults to `True`.*
**Datatype:** Boolean
+| `exit_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Exit](#exit-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer
| `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean
| `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio)
diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md
index 8a551d8a9..ee6ec0cce 100644
--- a/docs/includes/pricing.md
+++ b/docs/includes/pricing.md
@@ -1,6 +1,6 @@
## Prices used for orders
-Prices for regular orders can be controlled via the parameter structures `bid_strategy` for buying and `ask_strategy` for selling.
+Prices for regular orders can be controlled via the parameter structures `entry_pricing` for trade entries and `exit_pricing` for trade exits.
Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data.
!!! Note
@@ -9,20 +9,11 @@ Prices are always retrieved right before an order is placed, either by querying
!!! Warning "Using market orders"
Please read the section [Market order pricing](#market-order-pricing) section when using market orders.
-### Buy price
+### Entry price
-#### Check depth of market
+#### Enter price side
-When check depth of market is enabled (`bid_strategy.check_depth_of_market.enabled=True`), the buy signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side.
-
-Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `bid_strategy.check_depth_of_market.bids_to_ask_delta` parameter. The buy order is only executed if the orderbook delta is greater than or equal to the configured delta value.
-
-!!! Note
- A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
-
-#### Buy price side
-
-The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying.
+The configuration setting `entry_pricing.price_side` defines the side of the orderbook the bot looks for when buying.
The following displays an orderbook.
@@ -38,30 +29,53 @@ The following displays an orderbook.
...
```
-If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price.
-In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price.
+If `entry_pricing.price_side` is set to `"bid"`, then the bot will use 99 as entry price.
+In line with that, if `entry_pricing.price_side` is set to `"ask"`, then the bot will use 101 as entry price.
-Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary.
+Depending on the order direction (_long_/_short_), this will lead to different results. Therefore we recommend to use `"same"` or `"other"` for this configuration instead.
+This would result in the following pricing matrix:
+
+| direction | Order | setting | price | crosses spread |
+|------ |--------|-----|-----|-----|
+| long | buy | ask | 101 | yes |
+| long | buy | bid | 99 | no |
+| long | buy | same | 99 | no |
+| long | buy | other | 101 | yes |
+| short | sell | ask | 101 | no |
+| short | sell | bid | 99 | yes |
+| short | sell | same | 101 | no |
+| short | sell | other | 99 | yes |
+
+Using the other side of the orderbook often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary.
Taker fees instead of maker fees will most likely apply even when using limit buy orders.
-Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price).
+Also, prices at the "other" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price).
-#### Buy price with Orderbook enabled
+#### Entry price with Orderbook enabled
-When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
+When entering a trade with the orderbook enabled (`entry_pricing.use_order_book=True`), Freqtrade fetches the `entry_pricing.order_book_top` entries from the orderbook and uses the entry specified as `entry_pricing.order_book_top` on the configured side (`entry_pricing.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
-#### Buy price without Orderbook enabled
+#### Entry price without Orderbook enabled
-The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`).
+The following section uses `side` as the configured `entry_pricing.price_side` (defaults to `"same"`).
-When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`..
+When not using orderbook (`entry_pricing.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `entry_pricing.price_last_balance`.
-The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
+The `entry_pricing.price_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
-### Sell price
+#### Check depth of market
-#### Sell price side
+When check depth of market is enabled (`entry_pricing.check_depth_of_market.enabled=True`), the entry signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side.
-The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling.
+Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `entry_pricing.check_depth_of_market.bids_to_ask_delta` parameter. The entry order is only executed if the orderbook delta is greater than or equal to the configured delta value.
+
+!!! Note
+ A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
+
+### Exit price
+
+#### Exit price side
+
+The configuration setting `exit_pricing.price_side` defines the side of the spread the bot looks for when exiting a trade.
The following displays an orderbook:
@@ -77,27 +91,41 @@ The following displays an orderbook:
...
```
-If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price.
-In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price.
+If `exit_pricing.price_side` is set to `"ask"`, then the bot will use 101 as exiting price.
+In line with that, if `exit_pricing.price_side` is set to `"bid"`, then the bot will use 99 as exiting price.
-#### Sell price with Orderbook enabled
+Depending on the order direction (_long_/_short_), this will lead to different results. Therefore we recommend to use `"same"` or `"other"` for this configuration instead.
+This would result in the following pricing matrix:
-When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_top` entries in the orderbook and uses the entry specified as `ask_strategy.order_book_top` from the configured side (`ask_strategy.price_side`) as selling price.
+| Direction | Order | setting | price | crosses spread |
+|------ |--------|-----|-----|-----|
+| long | sell | ask | 101 | no |
+| long | sell | bid | 99 | yes |
+| long | sell | same | 101 | no |
+| long | sell | other | 99 | yes |
+| short | buy | ask | 101 | yes |
+| short | buy | bid | 99 | no |
+| short | buy | same | 99 | no |
+| short | buy | other | 101 | yes |
+
+#### Exit price with Orderbook enabled
+
+When exiting with the orderbook enabled (`exit_pricing.use_order_book=True`), Freqtrade fetches the `exit_pricing.order_book_top` entries in the orderbook and uses the entry specified as `exit_pricing.order_book_top` from the configured side (`exit_pricing.price_side`) as trade exit price.
1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
-#### Sell price without Orderbook enabled
+#### Exit price without Orderbook enabled
-The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`).
+The following section uses `side` as the configured `exit_pricing.price_side` (defaults to `"ask"`).
-When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`.
+When not using orderbook (`exit_pricing.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `exit_pricing.price_last_balance`.
-The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price.
+The `exit_pricing.price_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price.
### Market order pricing
When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection.
-Assuming both buy and sell are using market orders, a configuration similar to the following might be used
+Assuming both entry and exits are using market orders, a configuration similar to the following must be used
``` jsonc
"order_types": {
@@ -105,12 +133,12 @@ Assuming both buy and sell are using market orders, a configuration similar to t
"exit": "market"
// ...
},
- "bid_strategy": {
- "price_side": "ask",
+ "entry_pricing": {
+ "price_side": "other",
// ...
},
- "ask_strategy":{
- "price_side": "bid",
+ "exit_pricing":{
+ "price_side": "other",
// ...
},
```
diff --git a/docs/sandbox-testing.md b/docs/sandbox-testing.md
index 94a25b35f..2c0f306cf 100644
--- a/docs/sandbox-testing.md
+++ b/docs/sandbox-testing.md
@@ -108,12 +108,12 @@ To mitigate this, you can try to match the first order on the opposite orderbook
"exit": "limit"
// ...
},
- "bid_strategy": {
- "price_side": "ask",
+ "entry_pricing": {
+ "price_side": "other",
// ...
},
- "ask_strategy":{
- "price_side": "bid",
+ "exit_pricing":{
+ "price_side": "other",
// ...
},
```
diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md
index 6baf38c41..dfd29d544 100644
--- a/docs/strategy-callbacks.md
+++ b/docs/strategy-callbacks.md
@@ -160,7 +160,7 @@ class AwesomeStrategy(IStrategy):
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the current rate
@@ -707,7 +707,7 @@ class AwesomeStrategy(IStrategy):
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md
index ee6f1a494..88bda90e4 100644
--- a/docs/strategy_migration.md
+++ b/docs/strategy_migration.md
@@ -242,6 +242,8 @@ This should be given the value of `trade.is_short`.
```
+After:
+
``` python hl_lines="5 7"
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
@@ -267,6 +269,8 @@ This should be given the value of `trade.is_short`.
}
```
+After:
+
``` python hl_lines="2 3"
order_time_in_force: Dict = {
"entry": "gtc",
@@ -291,6 +295,8 @@ This should be given the value of `trade.is_short`.
}
```
+After:
+
``` python hl_lines="2-6"
order_types = {
"entry": "limit",
@@ -317,6 +323,8 @@ unfilledtimeout = {
}
```
+After:
+
``` python hl_lines="2-3"
unfilledtimeout = {
"entry": 10,
@@ -325,3 +333,54 @@ unfilledtimeout = {
"unit": "minutes"
}
```
+
+#### `order pricing`
+
+Order pricing changed in 2 ways. `bid_strategy` was renamed to `entry_strategy` and `ask_strategy` was renamed to `exit_strategy`.
+The attributes `ask_last_balance` -> `price_last_balance` and `bid_last_balance` -> `price_last_balance` were renamed as well.
+Also, price-side can now be defined as `ask`, `bid`, `same` or `other`.
+Please refer to the [pricing documentation](configuration.md) for more information.
+
+``` json hl_lines="2-3 6 12-13 16"
+{
+ "bid_strategy": {
+ "price_side": "bid",
+ "use_order_book": true,
+ "order_book_top": 1,
+ "ask_last_balance": 0.0,
+ "check_depth_of_market": {
+ "enabled": false,
+ "bids_to_ask_delta": 1
+ }
+ },
+ "ask_strategy":{
+ "price_side": "ask",
+ "use_order_book": true,
+ "order_book_top": 1,
+ "bid_last_balance": 0.0
+ }
+}
+```
+
+after:
+
+``` json hl_lines="2-3 6 12-13 16"
+{
+ "entry_pricing": {
+ "price_side": "same",
+ "use_order_book": true,
+ "order_book_top": 1,
+ "price_last_balance": 0.0,
+ "check_depth_of_market": {
+ "enabled": false,
+ "bids_to_ask_delta": 1
+ }
+ },
+ "exit_pricing":{
+ "price_side": "same",
+ "use_order_book": true,
+ "order_book_top": 1,
+ "price_last_balance": 0.0
+ }
+}
+```
diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py
index 3ebb18cd6..09e7b3335 100644
--- a/freqtrade/configuration/config_validation.py
+++ b/freqtrade/configuration/config_validation.py
@@ -103,14 +103,15 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
"""
When using market orders, price sides must be using the "other" side of the price
"""
- # TODO-lev: check this again when determining how to migrate pricing strategies!
+ # TODO: The below could be an enforced setting when using market orders
if (conf.get('order_types', {}).get('entry') == 'market'
- and conf.get('bid_strategy', {}).get('price_side') != 'ask'):
- raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".')
+ and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
+ raise OperationalException(
+ 'Market entry orders require entry_pricing.price_side = "other".')
if (conf.get('order_types', {}).get('exit') == 'market'
- and conf.get('ask_strategy', {}).get('price_side') != 'bid'):
- raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".')
+ and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
+ raise OperationalException('Market exit orders require exit_pricing.price_side = "other".')
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
@@ -193,13 +194,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
- ask_strategy = conf.get('ask_strategy', {})
+ ask_strategy = conf.get('exit_pricing', {})
ob_min = ask_strategy.get('order_book_min')
ob_max = ask_strategy.get('order_book_max')
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
if ob_min != ob_max:
raise OperationalException(
- "Using order_book_max != order_book_min in ask_strategy is no longer supported."
+ "Using order_book_max != order_book_min in exit_pricing is no longer supported."
"Please pick one value and use `order_book_top` in the future."
)
else:
@@ -208,7 +209,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
logger.warning(
"DEPRECATED: "
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
- "for your `ask_strategy` configuration."
+ "for your `exit_pricing` configuration."
)
@@ -217,6 +218,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
_validate_time_in_force(conf)
_validate_order_types(conf)
_validate_unfilledtimeout(conf)
+ _validate_pricing_rules(conf)
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
@@ -279,3 +281,34 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
]:
process_deprecated_setting(conf, 'unfilledtimeout', o, 'unfilledtimeout', n)
+
+
+def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
+
+ if conf.get('ask_strategy') or conf.get('bid_strategy'):
+ if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
+ raise OperationalException(
+ "Please migrate your pricing settings to use the new wording.")
+ else:
+
+ logger.warning(
+ "DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is deprecated."
+ "Please migrate your settings to use 'entry_pricing' and 'exit_pricing'."
+ )
+ conf['entry_pricing'] = {}
+ for obj in list(conf.get('bid_strategy', {}).keys()):
+ if obj == 'ask_last_balance':
+ process_deprecated_setting(conf, 'bid_strategy', obj,
+ 'entry_pricing', 'price_last_balance')
+ else:
+ process_deprecated_setting(conf, 'bid_strategy', obj, 'entry_pricing', obj)
+ del conf['bid_strategy']
+
+ conf['exit_pricing'] = {}
+ for obj in list(conf.get('ask_strategy', {}).keys()):
+ if obj == 'bid_last_balance':
+ process_deprecated_setting(conf, 'ask_strategy', obj,
+ 'exit_pricing', 'price_last_balance')
+ else:
+ process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj)
+ del conf['ask_strategy']
diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py
index c780afca1..aa8f51a1d 100644
--- a/freqtrade/configuration/configuration.py
+++ b/freqtrade/configuration/configuration.py
@@ -81,8 +81,6 @@ class Configuration:
# Normalize config
if 'internals' not in config:
config['internals'] = {}
- if 'ask_strategy' not in config:
- config['ask_strategy'] = {}
if 'pairlists' not in config:
config['pairlists'] = []
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 6441559ad..a06e2771f 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -21,7 +21,7 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['entry', 'exit']
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
-ORDERBOOK_SIDES = ['ask', 'bid']
+PRICING_SIDES = ['ask', 'bid', 'same', 'other']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
@@ -171,16 +171,16 @@ CONF_SCHEMA = {
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
}
},
- 'bid_strategy': {
+ 'entry_pricing': {
'type': 'object',
'properties': {
- 'ask_last_balance': {
+ 'price_last_balance': {
'type': 'number',
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False,
},
- 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
+ 'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'},
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
'check_depth_of_market': {
@@ -193,11 +193,11 @@ CONF_SCHEMA = {
},
'required': ['price_side']
},
- 'ask_strategy': {
+ 'exit_pricing': {
'type': 'object',
'properties': {
- 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
- 'bid_last_balance': {
+ 'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'},
+ 'price_last_balance': {
'type': 'number',
'minimum': 0,
'maximum': 1,
@@ -445,8 +445,8 @@ SCHEMA_TRADE_REQUIRED = [
'last_stake_amount_min_ratio',
'dry_run',
'dry_run_wallet',
- 'ask_strategy',
- 'bid_strategy',
+ 'exit_pricing',
+ 'entry_pricing',
'stoploss',
'minimal_roi',
'internals',
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 67692cd27..fc8174e62 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -111,8 +111,8 @@ class Exchange:
# Cache values for 1800 to avoid frequent polling of the exchange for prices
# Caching only applies to RPC methods, so prices for open trades are still
# refreshed once every iteration.
- self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
- self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
+ self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
+ self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
# Holds candles
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
@@ -184,8 +184,8 @@ class Exchange:
self.required_candle_call_count = self.validate_required_startup_candles(
config.get('startup_candle_count', 0), config.get('timeframe', ''))
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
- self.validate_pricing(config['ask_strategy'])
- self.validate_pricing(config['bid_strategy'])
+ self.validate_pricing(config['exit_pricing'])
+ self.validate_pricing(config['entry_pricing'])
# Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get(
@@ -1438,7 +1438,8 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def get_rate(self, pair: str, refresh: bool, side: str) -> float:
+ def get_rate(self, pair: str, refresh: bool,
+ side: Literal['entry', 'exit'], is_short: bool) -> float:
"""
Calculates bid/ask target
bid rate - between current ask price and last price
@@ -1450,9 +1451,10 @@ class Exchange:
:return: float: Price
:raises PricingError if orderbook price could not be determined.
"""
- cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
- [strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
+ name = side.capitalize()
+ strat_name = 'entry_pricing' if side == "entry" else 'exit_pricing'
+ cache_rate: TTLCache = self._entry_rate_cache if side == "entry" else self._exit_rate_cache
if not refresh:
rate = cache_rate.get(pair)
# Check if cache has been invalidated
@@ -1462,6 +1464,23 @@ class Exchange:
conf_strategy = self._config.get(strat_name, {})
+ price_side = conf_strategy['price_side']
+
+ if price_side in ('same', 'other'):
+ price_map = {
+ ('entry', 'long', 'same'): 'bid',
+ ('entry', 'long', 'other'): 'ask',
+ ('entry', 'short', 'same'): 'ask',
+ ('entry', 'short', 'other'): 'bid',
+ ('exit', 'long', 'same'): 'ask',
+ ('exit', 'long', 'other'): 'bid',
+ ('exit', 'short', 'same'): 'bid',
+ ('exit', 'short', 'other'): 'ask',
+ }
+ price_side = price_map[(side, 'short' if is_short else 'long', price_side)]
+
+ price_side_word = price_side.capitalize()
+
if conf_strategy.get('use_order_book', False):
order_book_top = conf_strategy.get('order_book_top', 1)
@@ -1469,26 +1488,25 @@ class Exchange:
logger.debug('order_book %s', order_book)
# top 1 = index 0
try:
- rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0]
+ rate = order_book[f"{price_side}s"][order_book_top - 1][0]
except (IndexError, KeyError) as e:
logger.warning(
f"{name} Price at location {order_book_top} from orderbook could not be "
f"determined. Orderbook: {order_book}"
)
raise PricingError from e
- price_side = {conf_strategy['price_side'].capitalize()}
- logger.debug(f"{name} price from orderbook {price_side}"
+ logger.debug(f"{name} price from orderbook {price_side_word}"
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
else:
- logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
+ logger.debug(f"Using Last {price_side_word} / Last Price")
ticker = self.fetch_ticker(pair)
- ticker_rate = ticker[conf_strategy['price_side']]
+ ticker_rate = ticker[price_side]
if ticker['last'] and ticker_rate:
- if side == 'buy' and ticker_rate > ticker['last']:
- balance = conf_strategy.get('ask_last_balance', 0.0)
+ if side == 'entry' and ticker_rate > ticker['last']:
+ balance = conf_strategy.get('price_last_balance', 0.0)
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
- elif side == 'sell' and ticker_rate < ticker['last']:
- balance = conf_strategy.get('bid_last_balance', 0.0)
+ elif side == 'exit' and ticker_rate < ticker['last']:
+ balance = conf_strategy.get('price_last_balance', 0.0)
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
rate = ticker_rate
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index b85052ff9..48e457f1e 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -7,7 +7,7 @@ import traceback
from datetime import datetime, time, timezone
from math import isclose
from threading import Lock
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, Literal, Optional, Tuple
from schedule import Scheduler
@@ -459,7 +459,7 @@ class FreqtradeBot(LoggingMixin):
if signal:
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
- bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
+ bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {})
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market(pair, bid_check_dom, side=signal):
@@ -511,7 +511,8 @@ class FreqtradeBot(LoggingMixin):
return
else:
logger.debug("Max adjustment entries is set to unlimited.")
- current_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.enter_side)
+ current_rate = self.exchange.get_rate(
+ trade.pair, side='entry', is_short=trade.is_short, refresh=True)
current_profit = trade.calc_profit_ratio(current_rate)
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
@@ -589,11 +590,11 @@ class FreqtradeBot(LoggingMixin):
time_in_force = self.strategy.order_time_in_force['entry']
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
- trade_side = 'short' if is_short else 'long'
+ trade_side: Literal['long', 'short'] = 'short' if is_short else 'long'
pos_adjust = trade is not None
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
- pair, price, stake_amount, side, trade_side, enter_tag, trade)
+ pair, price, stake_amount, trade_side, enter_tag, trade)
if not stake_amount:
return False
@@ -745,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
def get_valid_enter_price_and_stake(
self, pair: str, price: Optional[float], stake_amount: float,
- side: str, trade_side: str,
+ trade_side: Literal['long', 'short'],
entry_tag: Optional[str],
trade: Optional[Trade]
) -> Tuple[float, float, float]:
@@ -754,7 +755,8 @@ class FreqtradeBot(LoggingMixin):
enter_limit_requested = price
else:
# Calculate price
- proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side=side)
+ proposed_enter_rate = self.exchange.get_rate(
+ pair, side='entry', is_short=(trade_side == 'short'), refresh=True)
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=proposed_enter_rate)(
pair=pair, current_time=datetime.now(timezone.utc),
@@ -763,7 +765,7 @@ class FreqtradeBot(LoggingMixin):
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
if not enter_limit_requested:
- raise PricingError(f'Could not determine {side} price.')
+ raise PricingError('Could not determine entry price.')
if trade is None:
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
@@ -824,7 +826,8 @@ class FreqtradeBot(LoggingMixin):
current_rate = trade.open_rate_requested
if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
- current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
+ current_rate = self.exchange.get_rate(
+ trade.pair, side='entry', is_short=trade.is_short, refresh=False)
msg = {
'trade_id': trade.id,
@@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
"""
Sends rpc notification when a entry order cancel occurred.
"""
- current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
+ current_rate = self.exchange.get_rate(
+ trade.pair, side='entry', is_short=trade.is_short, refresh=False)
msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
msg = {
'trade_id': trade.id,
@@ -935,7 +939,8 @@ class FreqtradeBot(LoggingMixin):
)
logger.debug('checking exit')
- exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side)
+ exit_rate = self.exchange.get_rate(
+ trade.pair, side='exit', is_short=trade.is_short, refresh=True)
if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
return True
@@ -1433,7 +1438,7 @@ class FreqtradeBot(LoggingMixin):
profit_trade = trade.calc_profit(rate=profit_rate)
# Use cached rates here - it was updated seconds ago.
current_rate = self.exchange.get_rate(
- trade.pair, refresh=False, side=trade.exit_side) if not fill else None
+ trade.pair, side='exit', is_short=trade.is_short, refresh=False) if not fill else None
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
@@ -1482,7 +1487,8 @@ class FreqtradeBot(LoggingMixin):
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
- current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side)
+ current_rate = self.exchange.get_rate(
+ trade.pair, side='exit', is_short=trade.is_short, refresh=False)
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index d3e4a2f79..4d7092ff6 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -115,9 +115,9 @@ class Hyperopt:
if HyperoptTools.has_space(self.config, 'sell'):
# Make sure use_sell_signal is enabled
- if 'ask_strategy' not in self.config:
- self.config['ask_strategy'] = {}
- self.config['ask_strategy']['use_sell_signal'] = True
+ if 'exit_pricing' not in self.config:
+ self.config['exit_pricing'] = {}
+ self.config['exit_pricing']['use_sell_signal'] = True
self.print_all = self.config.get('print_all', False)
self.hyperopt_table_header = 0
diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py
index 07772647f..11baa9560 100644
--- a/freqtrade/rpc/api_server/api_schemas.py
+++ b/freqtrade/rpc/api_server/api_schemas.py
@@ -175,8 +175,8 @@ class ShowConfig(BaseModel):
exchange: str
strategy: Optional[str]
forcebuy_enabled: bool
- ask_strategy: Dict[str, Any]
- bid_strategy: Dict[str, Any]
+ exit_pricing: Dict[str, Any]
+ entry_pricing: Dict[str, Any]
bot_name: str
state: str
runmode: str
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 5b59da1fc..94bc513fb 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -136,8 +136,8 @@ class RPC:
'exchange': config['exchange']['name'],
'strategy': config['strategy'],
'forcebuy_enabled': config.get('forcebuy_enable', False),
- 'ask_strategy': config.get('ask_strategy', {}),
- 'bid_strategy': config.get('bid_strategy', {}),
+ 'exit_pricing': config.get('exit_pricing', {}),
+ 'entry_pricing': config.get('entry_pricing', {}),
'state': str(botstate),
'runmode': config['runmode'].value,
'position_adjustment_enable': config.get('position_adjustment_enable', False),
@@ -171,7 +171,7 @@ class RPC:
if trade.is_open:
try:
current_rate = self._freqtrade.exchange.get_rate(
- trade.pair, refresh=False, side=trade.exit_side)
+ trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (ExchangeError, PricingError):
current_rate = NAN
else:
@@ -231,7 +231,7 @@ class RPC:
# calculate profit and send message to user
try:
current_rate = self._freqtrade.exchange.get_rate(
- trade.pair, refresh=False, side=trade.exit_side)
+ trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (PricingError, ExchangeError):
current_rate = NAN
trade_profit = trade.calc_profit(current_rate)
@@ -485,7 +485,7 @@ class RPC:
# Get current rate
try:
current_rate = self._freqtrade.exchange.get_rate(
- trade.pair, refresh=False, side=trade.exit_side)
+ trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (PricingError, ExchangeError):
current_rate = NAN
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
@@ -705,7 +705,7 @@ class RPC:
if not fully_canceled:
# Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_rate(
- trade.pair, refresh=False, side=trade.exit_side)
+ trade.pair, side='exit', is_short=trade.is_short, refresh=True)
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL)
order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forceexit", self._freqtrade.strategy.order_types["exit"])
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 29f63215d..20ab86aeb 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1490,8 +1490,8 @@ class Telegram(RPCHandler):
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
f"*Max open Trades:* `{val['max_open_trades']}`\n"
f"*Minimum ROI:* `{val['minimal_roi']}`\n"
- f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
- f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
+ f"*Entry strategy:* ```\n{json.dumps(val['entry_pricing'])}```\n"
+ f"*Exit strategy:* ```\n{json.dumps(val['exit_pricing'])}```\n"
f"{sl_info}"
f"{pa_info}"
f"*Timeframe:* `{val['timeframe']}`\n"
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index c00eb238a..06fa121b3 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -331,7 +331,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the current_rate
@@ -349,7 +349,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
@@ -369,7 +369,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New exit price value if provided
@@ -393,7 +393,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return: To execute exit, return a string with custom sell reason or True. Otherwise return
@@ -417,7 +417,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return: To execute exit, return a string with custom sell reason or True. Otherwise return
@@ -433,7 +433,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_stake: A stake amount proposed by the bot.
:param min_stake: Minimal stake size allowed by exchange.
:param max_stake: Balance available for trading.
@@ -474,7 +474,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2
index e5f8f5efe..f1f611a45 100644
--- a/freqtrade/templates/base_config.json.j2
+++ b/freqtrade/templates/base_config.json.j2
@@ -21,18 +21,18 @@
"exit_timeout_count": 0,
"unit": "minutes"
},
- "bid_strategy": {
- "price_side": "bid",
- "ask_last_balance": 0.0,
+ "entry_pricing": {
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1,
+ "price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
- "ask_strategy": {
- "price_side": "ask",
+ "exit_pricing":{
+ "price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2
index 78ee8572e..e5eecf7eb 100644
--- a/freqtrade/templates/base_strategy.py.j2
+++ b/freqtrade/templates/base_strategy.py.j2
@@ -64,7 +64,7 @@ class {{ strategy }}(IStrategy):
# Run "populate_indicators()" only for new candle.
process_only_new_candles = False
- # These values can be overridden in the "ask_strategy" section in the config.
+ # These values can be overridden in the config.
use_sell_signal = True
sell_profit_only = False
ignore_roi_if_buy_signal = False
diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py
index 08a690ab0..0c5c501cf 100644
--- a/freqtrade/templates/sample_strategy.py
+++ b/freqtrade/templates/sample_strategy.py
@@ -64,7 +64,7 @@ class SampleStrategy(IStrategy):
# Run "populate_indicators()" only for new candle.
process_only_new_candles = False
- # These values can be overridden in the "ask_strategy" section in the config.
+ # These values can be overridden in the config.
use_sell_signal = True
sell_profit_only = False
ignore_roi_if_buy_signal = False
diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
index d3178740b..b2dbb736d 100644
--- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
+++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
@@ -23,7 +23,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate:
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
@@ -43,7 +43,7 @@ def custom_exit_price(self, pair: str, trade: 'Trade',
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New exit price value if provided
@@ -58,7 +58,7 @@ def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate:
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_stake: A stake amount proposed by the bot.
:param min_stake: Minimal stake size allowed by exchange.
:param max_stake: Balance available for trading.
@@ -85,7 +85,7 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the current_rate
@@ -108,7 +108,7 @@ def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', curre
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return: To execute sell, return a string with custom sell reason or True. Otherwise return
@@ -241,7 +241,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
- :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
+ :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
diff --git a/tests/conftest.py b/tests/conftest.py
index 898945370..b00feb14b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -419,8 +419,8 @@ def get_default_conf(testdatadir):
"entry": 10,
"exit": 30
},
- "bid_strategy": {
- "ask_last_balance": 0.0,
+ "entry_pricing": {
+ "price_last_balance": 0.0,
"use_order_book": False,
"order_book_top": 1,
"check_depth_of_market": {
@@ -428,7 +428,7 @@ def get_default_conf(testdatadir):
"bids_to_ask_delta": 1
}
},
- "ask_strategy": {
+ "exit_pricing": {
"use_order_book": False,
"order_book_top": 1,
},
diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py
index 89b3bcc1f..14e45c8b0 100644
--- a/tests/exchange/test_ccxt_compat.py
+++ b/tests/exchange/test_ccxt_compat.py
@@ -107,8 +107,8 @@ def exchange_conf():
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
config['dry_run'] = False
- config['bid_strategy']['use_order_book'] = True
- config['ask_strategy']['use_order_book'] = True
+ config['entry_pricing']['use_order_book'] = True
+ config['exit_pricing']['use_order_book'] = True
return config
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 5d16c3501..71d527206 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -971,7 +971,7 @@ def test_validate_pricing(default_conf, mocker):
has.update({'fetchTicker': True})
- default_conf['ask_strategy']['use_order_book'] = True
+ default_conf['exit_pricing']['use_order_book'] = True
ExchangeResolver.load_exchange('binance', default_conf)
has.update({'fetchL2OrderBook': False})
@@ -2276,6 +2276,7 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
+ ('other', 20, 19, 10, 0.0, 20), # Full ask side
('ask', 20, 19, 10, 0.0, 20), # Full ask side
('ask', 20, 19, 10, 1.0, 10), # Full last side
('ask', 20, 19, 10, 0.5, 15), # Between ask and last
@@ -2283,45 +2284,46 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
('ask', 20, 19, 10, 0.3, 17), # Between ask and last
('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
- ('ask', 20, 19, 10, None, 20), # ask_last_balance missing
+ ('ask', 20, 19, 10, None, 20), # price_last_balance missing
('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
('ask', 4, 5, None, 1, 4), # last not available - uses ask
('ask', 4, 5, None, 0, 4), # last not available - uses ask
+ ('same', 21, 20, 10, 0.0, 20), # Full bid side
('bid', 21, 20, 10, 0.0, 20), # Full bid side
('bid', 21, 20, 10, 1.0, 10), # Full last side
('bid', 21, 20, 10, 0.5, 15), # Between bid and last
('bid', 21, 20, 10, 0.7, 13), # Between bid and last
('bid', 21, 20, 10, 0.3, 17), # Between bid and last
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
- ('bid', 21, 20, 10, None, 20), # ask_last_balance missing
+ ('bid', 21, 20, 10, None, 20), # price_last_balance missing
('bid', 6, 5, 10, 0.5, 5), # last bigger than bid
('bid', 21, 20, None, 0.5, 20), # last not available - uses bid
('bid', 6, 5, None, 0.5, 5), # last not available - uses bid
('bid', 6, 5, None, 1, 5), # last not available - uses bid
('bid', 6, 5, None, 0, 5), # last not available - uses bid
])
-def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
- last, last_ab, expected) -> None:
+def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
+ last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
if last_ab is None:
- del default_conf['bid_strategy']['ask_last_balance']
+ del default_conf['entry_pricing']['price_last_balance']
else:
- default_conf['bid_strategy']['ask_last_balance'] = last_ab
- default_conf['bid_strategy']['price_side'] = side
+ default_conf['entry_pricing']['price_last_balance'] = last_ab
+ default_conf['entry_pricing']['price_side'] = side
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'last': last, 'bid': bid})
- assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
- assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
+ assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
+ assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
- assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected
- assert log_has("Using cached buy rate for ETH/BTC.", caplog)
+ assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
+ assert log_has("Using cached entry rate for ETH/BTC.", caplog)
# Running a 2nd time with Refresh on!
caplog.clear()
- assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
- assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
+ assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
+ assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [
@@ -2345,112 +2347,120 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
('ask', 0.006, 1.0, 11.0, 0.0, 0.006),
('ask', 0.006, 1.0, 11.0, None, 0.006),
])
-def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
+def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask,
last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
- default_conf['ask_strategy']['price_side'] = side
+ default_conf['exit_pricing']['price_side'] = side
if last_ab is not None:
- default_conf['ask_strategy']['bid_last_balance'] = last_ab
+ default_conf['exit_pricing']['price_last_balance'] = last_ab
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'bid': bid, 'last': last})
pair = "ETH/BTC"
# Test regular mode
exchange = get_patched_exchange(mocker, default_conf)
- rate = exchange.get_rate(pair, refresh=True, side="sell")
- assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
+ rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=True)
+ assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
assert rate == expected
# Use caching
- rate = exchange.get_rate(pair, refresh=False, side="sell")
+ rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=False)
assert rate == expected
- assert log_has("Using cached sell rate for ETH/BTC.", caplog)
+ assert log_has("Using cached exit rate for ETH/BTC.", caplog)
-@pytest.mark.parametrize("entry,side,ask,bid,last,last_ab,expected", [
- ('buy', 'ask', None, 4, 4, 0, 4), # ask not available
- ('buy', 'ask', None, None, 4, 0, 4), # ask not available
- ('buy', 'bid', 6, None, 4, 0, 5), # bid not available
- ('buy', 'bid', None, None, 4, 0, 5), # No rate available
- ('sell', 'ask', None, 4, 4, 0, 4), # ask not available
- ('sell', 'ask', None, None, 4, 0, 4), # ask not available
- ('sell', 'bid', 6, None, 4, 0, 5), # bid not available
- ('sell', 'bid', None, None, 4, 0, 5), # bid not available
+@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [
+ ('entry', False, 'ask', None, 4, 4, 0, 4), # ask not available
+ ('entry', False, 'ask', None, None, 4, 0, 4), # ask not available
+ ('entry', False, 'bid', 6, None, 4, 0, 5), # bid not available
+ ('entry', False, 'bid', None, None, 4, 0, 5), # No rate available
+ ('exit', False, 'ask', None, 4, 4, 0, 4), # ask not available
+ ('exit', False, 'ask', None, None, 4, 0, 4), # ask not available
+ ('exit', False, 'bid', 6, None, 4, 0, 5), # bid not available
+ ('exit', False, 'bid', None, None, 4, 0, 5), # bid not available
])
-def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, ask, bid,
+def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_short, ask, bid,
last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
- default_conf['bid_strategy']['ask_last_balance'] = last_ab
- default_conf['bid_strategy']['price_side'] = side
- default_conf['ask_strategy']['price_side'] = side
- default_conf['ask_strategy']['ask_last_balance'] = last_ab
+ default_conf['entry_pricing']['price_last_balance'] = last_ab
+ default_conf['entry_pricing']['price_side'] = side
+ default_conf['exit_pricing']['price_side'] = side
+ default_conf['exit_pricing']['price_last_balance'] = last_ab
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'last': last, 'bid': bid})
with pytest.raises(PricingError):
- exchange.get_rate('ETH/BTC', refresh=True, side=entry)
+ exchange.get_rate('ETH/BTC', refresh=True, side=entry, is_short=is_short)
-@pytest.mark.parametrize('side,expected', [
- ('bid', 0.043936), # Value from order_book_l2 fiture - bids side
- ('ask', 0.043949), # Value from order_book_l2 fiture - asks side
+@pytest.mark.parametrize('is_short,side,expected', [
+ (False, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side
+ (False, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side
+ (False, 'other', 0.043936), # Value from order_book_l2 fitxure - bids side
+ (False, 'same', 0.043949), # Value from order_book_l2 fitxure - asks side
+ (True, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side
+ (True, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side
+ (True, 'other', 0.043949), # Value from order_book_l2 fitxure - asks side
+ (True, 'same', 0.043936), # Value from order_book_l2 fitxure - bids side
])
-def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2):
+def test_get_exit_rate_orderbook(
+ default_conf, mocker, caplog, is_short, side, expected, order_book_l2):
caplog.set_level(logging.DEBUG)
# Test orderbook mode
- default_conf['ask_strategy']['price_side'] = side
- default_conf['ask_strategy']['use_order_book'] = True
- default_conf['ask_strategy']['order_book_top'] = 1
+ default_conf['exit_pricing']['price_side'] = side
+ default_conf['exit_pricing']['use_order_book'] = True
+ default_conf['exit_pricing']['order_book_top'] = 1
pair = "ETH/BTC"
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
exchange = get_patched_exchange(mocker, default_conf)
- rate = exchange.get_rate(pair, refresh=True, side="sell")
- assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
+ rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
+ assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
assert rate == expected
- rate = exchange.get_rate(pair, refresh=False, side="sell")
+ rate = exchange.get_rate(pair, refresh=False, side="exit", is_short=is_short)
assert rate == expected
- assert log_has("Using cached sell rate for ETH/BTC.", caplog)
+ assert log_has("Using cached exit rate for ETH/BTC.", caplog)
-def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
+def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog):
# Test orderbook mode
- default_conf['ask_strategy']['price_side'] = 'ask'
- default_conf['ask_strategy']['use_order_book'] = True
- default_conf['ask_strategy']['order_book_top'] = 1
+ default_conf['exit_pricing']['price_side'] = 'ask'
+ default_conf['exit_pricing']['use_order_book'] = True
+ default_conf['exit_pricing']['order_book_top'] = 1
pair = "ETH/BTC"
# Test What happens if the exchange returns an empty orderbook.
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
return_value={'bids': [[]], 'asks': [[]]})
exchange = get_patched_exchange(mocker, default_conf)
with pytest.raises(PricingError):
- exchange.get_rate(pair, refresh=True, side="sell")
- assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*",
+ exchange.get_rate(pair, refresh=True, side="exit", is_short=False)
+ assert log_has_re(r"Exit Price at location 1 from orderbook could not be determined\..*",
caplog)
-def test_get_sell_rate_exception(default_conf, mocker, caplog):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_get_exit_rate_exception(default_conf, mocker, is_short):
# Ticker on one side can be empty in certain circumstances.
- default_conf['ask_strategy']['price_side'] = 'ask'
+ default_conf['exit_pricing']['price_side'] = 'ask'
pair = "ETH/BTC"
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': None, 'bid': 0.12, 'last': None})
exchange = get_patched_exchange(mocker, default_conf)
- with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
- exchange.get_rate(pair, refresh=True, side="sell")
+ with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."):
+ exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
- exchange._config['ask_strategy']['price_side'] = 'bid'
- assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12
+ exchange._config['exit_pricing']['price_side'] = 'bid'
+ assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12
# Reverse sides
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': 0.13, 'bid': None, 'last': None})
- with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
- exchange.get_rate(pair, refresh=True, side="sell")
+ with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."):
+ exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
- exchange._config['ask_strategy']['price_side'] = 'ask'
- assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
+ exchange._config['exit_pricing']['price_side'] = 'ask'
+ assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13
@pytest.mark.parametrize("exchange_name", EXCHANGES)
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 61cdfb2bc..167f644c6 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -543,8 +543,8 @@ def test_api_show_config(botclient):
assert response['trading_mode'] == 'spot'
assert response['strategy_version'] is None
assert not response['trailing_stop']
- assert 'bid_strategy' in response
- assert 'ask_strategy' in response
+ assert 'entry_pricing' in response
+ assert 'exit_pricing' in response
assert 'unfilledtimeout' in response
assert 'version' in response
assert 'api_version' in response
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index 44187104a..3783e30a1 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -809,21 +809,21 @@ def test_validate_price_side(default_conf):
conf = deepcopy(default_conf)
conf['order_types']['entry'] = 'market'
with pytest.raises(OperationalException,
- match='Market buy orders require bid_strategy.price_side = "ask".'):
+ match='Market entry orders require entry_pricing.price_side = "other".'):
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf['order_types']['exit'] = 'market'
with pytest.raises(OperationalException,
- match='Market sell orders require ask_strategy.price_side = "bid".'):
+ match='Market exit orders require exit_pricing.price_side = "other".'):
validate_config_consistency(conf)
# Validate inversed case
conf = deepcopy(default_conf)
conf['order_types']['exit'] = 'market'
conf['order_types']['entry'] = 'market'
- conf['ask_strategy']['price_side'] = 'bid'
- conf['bid_strategy']['price_side'] = 'ask'
+ conf['exit_pricing']['price_side'] = 'bid'
+ conf['entry_pricing']['price_side'] = 'ask'
validate_config_consistency(conf)
@@ -926,18 +926,18 @@ def test_validate_protections(default_conf, protconf, expected):
def test_validate_ask_orderbook(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
- conf['ask_strategy']['use_order_book'] = True
- conf['ask_strategy']['order_book_min'] = 2
- conf['ask_strategy']['order_book_max'] = 2
+ conf['exit_pricing']['use_order_book'] = True
+ conf['exit_pricing']['order_book_min'] = 2
+ conf['exit_pricing']['order_book_max'] = 2
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Please use `order_book_top` instead of.*", caplog)
- assert conf['ask_strategy']['order_book_top'] == 2
+ assert conf['exit_pricing']['order_book_top'] == 2
- conf['ask_strategy']['order_book_max'] = 5
+ conf['exit_pricing']['order_book_max'] = 5
with pytest.raises(OperationalException,
- match=r"Using order_book_max != order_book_min in ask_strategy.*"):
+ match=r"Using order_book_max != order_book_min in exit_pricing.*"):
validate_config_consistency(conf)
@@ -1023,6 +1023,44 @@ def test__validate_unfilledtimeout(default_conf, caplog) -> None:
validate_config_consistency(conf)
+def test__validate_pricing_rules(default_conf, caplog) -> None:
+ def_conf = deepcopy(default_conf)
+ del def_conf['entry_pricing']
+ del def_conf['exit_pricing']
+
+ def_conf['ask_strategy'] = {
+ 'price_side': 'ask',
+ 'use_order_book': True,
+ 'bid_last_balance': 0.5
+ }
+ def_conf['bid_strategy'] = {
+ 'price_side': 'bid',
+ 'use_order_book': False,
+ 'ask_last_balance': 0.7
+ }
+ conf = deepcopy(def_conf)
+
+ validate_config_consistency(conf)
+ assert log_has_re(
+ r"DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is.*", caplog)
+ assert conf['exit_pricing']['price_side'] == 'ask'
+ assert conf['exit_pricing']['use_order_book'] is True
+ assert conf['exit_pricing']['price_last_balance'] == 0.5
+ assert conf['entry_pricing']['price_side'] == 'bid'
+ assert conf['entry_pricing']['use_order_book'] is False
+ assert conf['entry_pricing']['price_last_balance'] == 0.7
+ assert 'ask_strategy' not in conf
+ assert 'bid_strategy' not in conf
+
+ conf = deepcopy(def_conf)
+
+ conf['trading_mode'] = 'futures'
+ with pytest.raises(
+ OperationalException,
+ match=r"Please migrate your pricing settings to use the new wording\."):
+ validate_config_consistency(conf)
+
+
def test_load_config_test_comments() -> None:
"""
Load config with comments
@@ -1434,15 +1472,15 @@ def test_flat_vars_to_nested_dict(caplog):
'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
- 'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid',
- 'FREQTRADE__ASK_STRATEGY__cccc': '500',
+ 'FREQTRADE__EXIT_PRICING__PRICE_SIDE': 'bid',
+ 'FREQTRADE__EXIT_PRICING__cccc': '500',
'FREQTRADE__STAKE_AMOUNT': '200.05',
'FREQTRADE__TELEGRAM__CHAT_ID': '2151',
'NOT_RELEVANT': '200.0', # Will be ignored
}
expected = {
'stake_amount': 200.05,
- 'ask_strategy': {
+ 'exit_pricing': {
'price_side': 'bid',
'cccc': 500,
},
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 624399884..6e5b8a119 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -96,7 +96,7 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
- conf['bid_strategy']['price_side'] = 'ask'
+ conf['entry_pricing']['price_side'] = 'ask'
freqtrade = FreqtradeBot(conf)
if runmode == RunMode.LIVE:
@@ -898,7 +898,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
# Fail to get price...
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
- with pytest.raises(PricingError, match=f"Could not determine {enter_side(is_short)} price."):
+ with pytest.raises(PricingError, match="Could not determine entry price."):
freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
# In case of custom entry price
@@ -4052,7 +4052,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_
get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
- default_conf_usdt['ask_strategy'] = {
+ default_conf_usdt['exit_pricing'] = {
'ignore_roi_if_buy_signal': False
}
freqtrade = FreqtradeBot(default_conf_usdt)
@@ -4432,8 +4432,8 @@ def test_order_book_depth_of_market(
):
ticker_side = 'ask' if is_short else 'bid'
- default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
- default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = delta
+ default_conf_usdt['entry_pricing']['check_depth_of_market']['enabled'] = True
+ default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = delta
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
@@ -4476,8 +4476,8 @@ def test_order_book_depth_of_market(
(False, 0.045, 0.046, 2, None),
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
])
-def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
- ask, last, order_book_top, order_book, caplog) -> None:
+def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
+ ask, last, order_book_top, order_book, caplog) -> None:
"""
test if function get_rate will return the order book price instead of the ask rate
"""
@@ -4489,19 +4489,20 @@ def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exce
fetch_ticker=ticker_usdt_mock,
)
default_conf_usdt['exchange']['name'] = 'binance'
- default_conf_usdt['bid_strategy']['use_order_book'] = True
- default_conf_usdt['bid_strategy']['order_book_top'] = order_book_top
- default_conf_usdt['bid_strategy']['ask_last_balance'] = 0
+ default_conf_usdt['entry_pricing']['use_order_book'] = True
+ default_conf_usdt['entry_pricing']['order_book_top'] = order_book_top
+ default_conf_usdt['entry_pricing']['price_last_balance'] = 0
default_conf_usdt['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf_usdt)
if exception_thrown:
with pytest.raises(PricingError):
- freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy")
+ freqtrade.exchange.get_rate('ETH/USDT', side="entry", is_short=False, refresh=True)
assert log_has_re(
- r'Buy Price at location 1 from orderbook could not be determined.', caplog)
+ r'Entry Price at location 1 from orderbook could not be determined.', caplog)
else:
- assert freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy") == 0.043935
+ assert freqtrade.exchange.get_rate(
+ 'ETH/USDT', side="entry", is_short=False, refresh=True) == 0.043935
assert ticker_usdt_mock.call_count == 0
@@ -4516,17 +4517,17 @@ def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None
)
default_conf_usdt['telegram']['enabled'] = False
default_conf_usdt['exchange']['name'] = 'binance'
- default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
+ default_conf_usdt['entry_pricing']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence function will return false
- default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
+ default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = 100
freqtrade = FreqtradeBot(default_conf_usdt)
- conf = default_conf_usdt['bid_strategy']['check_depth_of_market']
+ conf = default_conf_usdt['entry_pricing']['check_depth_of_market']
assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
@pytest.mark.parametrize('is_short', [False, True])
-def test_order_book_ask_strategy(
+def test_order_book_exit_pricing(
default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
"""
@@ -4534,8 +4535,8 @@ def test_order_book_ask_strategy(
"""
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
default_conf_usdt['exchange']['name'] = 'binance'
- default_conf_usdt['ask_strategy']['use_order_book'] = True
- default_conf_usdt['ask_strategy']['order_book_top'] = 1
+ default_conf_usdt['exit_pricing']['use_order_book'] = True
+ default_conf_usdt['exit_pricing']['order_book_top'] = 1
default_conf_usdt['telegram']['enabled'] = False
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -4577,7 +4578,7 @@ def test_order_book_ask_strategy(
return_value={'bids': [[]], 'asks': [[]]})
with pytest.raises(PricingError):
freqtrade.handle_trade(trade)
- assert log_has_re(r'Sell Price at location 1 from orderbook could not be determined\..*',
+ assert log_has_re(r'Exit Price at location 1 from orderbook could not be determined\..*',
caplog)