Merge pull request #6607 from freqtrade/short_pricing
Short pricing updates
This commit is contained in:
commit
e1ccbdb927
@ -13,16 +13,18 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"ask_last_balance": 0.0,
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"exit_pricing": {
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1
|
||||||
},
|
},
|
||||||
|
@ -13,16 +13,18 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"exit_pricing":{
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1
|
||||||
},
|
},
|
||||||
|
@ -13,16 +13,18 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"ask_last_balance": 0.0,
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"exit_pricing": {
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1
|
||||||
},
|
},
|
||||||
|
@ -35,20 +35,21 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"price_side": "bid",
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"exit_pricing":{
|
||||||
"price_side": "ask",
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0
|
||||||
},
|
},
|
||||||
"order_types": {
|
"order_types": {
|
||||||
"entry": "limit",
|
"entry": "limit",
|
||||||
|
@ -13,16 +13,18 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"exit_pricing":{
|
||||||
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1
|
||||||
},
|
},
|
||||||
|
@ -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.
|
* Calls `check_exit_timeout()` strategy callback for open exit orders.
|
||||||
* Verifies existing positions and eventually places exit orders.
|
* Verifies existing positions and eventually places exit orders.
|
||||||
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
||||||
* Determine exit-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback.
|
* Determine exit-price based on `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.
|
* 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 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).
|
* Check if trade-slots are still available (if `max_open_trades` is reached).
|
||||||
* Verifies entry signal trying to enter new positions.
|
* 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.
|
* 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.
|
* Determine stake size by calling the `custom_stake_amount()` callback.
|
||||||
* Before an entry order is placed, `confirm_trade_entry()` strategy callback is called.
|
* Before an entry order is placed, `confirm_trade_entry()` strategy callback is called.
|
||||||
|
@ -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).<br> **Datatype:** Integer
|
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
|
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
|
||||||
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
||||||
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
|
| `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).<br> *Defaults to `same`.* <br> **Datatype:** String (either `ask`, `bid`, `same` or `other`).
|
||||||
| `bid_strategy.ask_last_balance` | **Required.** Interpolate the bidding price. More information [below](#buy-price-without-orderbook-enabled).
|
| `entry_pricing.price_last_balance` | **Required.** Interpolate the bidding price. More information [below](#entry-price-without-orderbook-enabled).
|
||||||
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
| `entry_pricing.use_order_book` | Enable entering using the rates in [Order Book Entry](#entry-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **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). <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `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). <br>*Defaults to `1`.* <br> **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). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `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). <br>*Defaults to `false`.* <br> **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) <br> *Defaults to `0`.* <br> **Datatype:** Float (as ratio)
|
| `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) <br> *Defaults to `0`.* <br> **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).<br> *Defaults to `ask`.* <br> **Datatype:** String (either `ask` or `bid`).
|
| `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).<br> *Defaults to `same`.* <br> **Datatype:** String (either `ask`, `bid`, `same` or `other`).
|
||||||
| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled).
|
| `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled).
|
||||||
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
| `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **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)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `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)<br>*Defaults to `1`.* <br> **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). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **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). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
| `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). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
## Prices used for orders
|
## 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.
|
Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
@ -9,20 +9,11 @@ Prices are always retrieved right before an order is placed, either by querying
|
|||||||
!!! Warning "Using market orders"
|
!!! Warning "Using market orders"
|
||||||
Please read the section [Market order pricing](#market-order-pricing) section when 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.
|
The configuration setting `entry_pricing.price_side` defines the side of the orderbook the bot looks for when buying.
|
||||||
|
|
||||||
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 following displays an orderbook.
|
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.
|
If `entry_pricing.price_side` is set to `"bid"`, then the bot will use 99 as entry price.
|
||||||
In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying 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.
|
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:
|
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.
|
If `exit_pricing.price_side` is set to `"ask"`, then the bot will use 101 as exiting price.
|
||||||
In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling 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.
|
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
|
### Market order pricing
|
||||||
|
|
||||||
When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection.
|
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
|
``` jsonc
|
||||||
"order_types": {
|
"order_types": {
|
||||||
@ -105,12 +133,12 @@ Assuming both buy and sell are using market orders, a configuration similar to t
|
|||||||
"exit": "market"
|
"exit": "market"
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"price_side": "ask",
|
"price_side": "other",
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"exit_pricing":{
|
||||||
"price_side": "bid",
|
"price_side": "other",
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
@ -108,12 +108,12 @@ To mitigate this, you can try to match the first order on the opposite orderbook
|
|||||||
"exit": "limit"
|
"exit": "limit"
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"price_side": "ask",
|
"price_side": "other",
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"exit_pricing":{
|
||||||
"price_side": "bid",
|
"price_side": "other",
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
@ -160,7 +160,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 proposed_leverage: A leverage proposed by the bot.
|
||||||
:param max_leverage: Max leverage allowed on this pair
|
:param max_leverage: Max leverage allowed on this pair
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||||
|
@ -242,6 +242,8 @@ This should be given the value of `trade.is_short`.
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
``` python hl_lines="5 7"
|
``` python hl_lines="5 7"
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
||||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
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"
|
``` python hl_lines="2 3"
|
||||||
order_time_in_force: Dict = {
|
order_time_in_force: Dict = {
|
||||||
"entry": "gtc",
|
"entry": "gtc",
|
||||||
@ -291,6 +295,8 @@ This should be given the value of `trade.is_short`.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
``` python hl_lines="2-6"
|
``` python hl_lines="2-6"
|
||||||
order_types = {
|
order_types = {
|
||||||
"entry": "limit",
|
"entry": "limit",
|
||||||
@ -317,6 +323,8 @@ unfilledtimeout = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
``` python hl_lines="2-3"
|
``` python hl_lines="2-3"
|
||||||
unfilledtimeout = {
|
unfilledtimeout = {
|
||||||
"entry": 10,
|
"entry": 10,
|
||||||
@ -325,3 +333,54 @@ unfilledtimeout = {
|
|||||||
"unit": "minutes"
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -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
|
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'
|
if (conf.get('order_types', {}).get('entry') == 'market'
|
||||||
and conf.get('bid_strategy', {}).get('price_side') != 'ask'):
|
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
||||||
raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".')
|
raise OperationalException(
|
||||||
|
'Market entry orders require entry_pricing.price_side = "other".')
|
||||||
|
|
||||||
if (conf.get('order_types', {}).get('exit') == 'market'
|
if (conf.get('order_types', {}).get('exit') == 'market'
|
||||||
and conf.get('ask_strategy', {}).get('price_side') != 'bid'):
|
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
||||||
raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".')
|
raise OperationalException('Market exit orders require exit_pricing.price_side = "other".')
|
||||||
|
|
||||||
|
|
||||||
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
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:
|
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_min = ask_strategy.get('order_book_min')
|
||||||
ob_max = ask_strategy.get('order_book_max')
|
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 is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||||
if ob_min != ob_max:
|
if ob_min != ob_max:
|
||||||
raise OperationalException(
|
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."
|
"Please pick one value and use `order_book_top` in the future."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -208,7 +209,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
|
"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_time_in_force(conf)
|
||||||
_validate_order_types(conf)
|
_validate_order_types(conf)
|
||||||
_validate_unfilledtimeout(conf)
|
_validate_unfilledtimeout(conf)
|
||||||
|
_validate_pricing_rules(conf)
|
||||||
|
|
||||||
|
|
||||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
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)
|
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']
|
||||||
|
@ -81,8 +81,6 @@ class Configuration:
|
|||||||
# Normalize config
|
# Normalize config
|
||||||
if 'internals' not in config:
|
if 'internals' not in config:
|
||||||
config['internals'] = {}
|
config['internals'] = {}
|
||||||
if 'ask_strategy' not in config:
|
|
||||||
config['ask_strategy'] = {}
|
|
||||||
|
|
||||||
if 'pairlists' not in config:
|
if 'pairlists' not in config:
|
||||||
config['pairlists'] = []
|
config['pairlists'] = []
|
||||||
|
@ -21,7 +21,7 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
|||||||
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
|
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
|
||||||
REQUIRED_ORDERTIF = ['entry', 'exit']
|
REQUIRED_ORDERTIF = ['entry', 'exit']
|
||||||
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
|
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
|
||||||
ORDERBOOK_SIDES = ['ask', 'bid']
|
PRICING_SIDES = ['ask', 'bid', 'same', 'other']
|
||||||
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||||
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||||
@ -171,16 +171,16 @@ CONF_SCHEMA = {
|
|||||||
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
|
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'bid_strategy': {
|
'entry_pricing': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'ask_last_balance': {
|
'price_last_balance': {
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'minimum': 0,
|
'minimum': 0,
|
||||||
'maximum': 1,
|
'maximum': 1,
|
||||||
'exclusiveMaximum': False,
|
'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'},
|
'use_order_book': {'type': 'boolean'},
|
||||||
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
|
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
|
||||||
'check_depth_of_market': {
|
'check_depth_of_market': {
|
||||||
@ -193,11 +193,11 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'required': ['price_side']
|
'required': ['price_side']
|
||||||
},
|
},
|
||||||
'ask_strategy': {
|
'exit_pricing': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
|
'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'},
|
||||||
'bid_last_balance': {
|
'price_last_balance': {
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'minimum': 0,
|
'minimum': 0,
|
||||||
'maximum': 1,
|
'maximum': 1,
|
||||||
@ -445,8 +445,8 @@ SCHEMA_TRADE_REQUIRED = [
|
|||||||
'last_stake_amount_min_ratio',
|
'last_stake_amount_min_ratio',
|
||||||
'dry_run',
|
'dry_run',
|
||||||
'dry_run_wallet',
|
'dry_run_wallet',
|
||||||
'ask_strategy',
|
'exit_pricing',
|
||||||
'bid_strategy',
|
'entry_pricing',
|
||||||
'stoploss',
|
'stoploss',
|
||||||
'minimal_roi',
|
'minimal_roi',
|
||||||
'internals',
|
'internals',
|
||||||
|
@ -111,8 +111,8 @@ class Exchange:
|
|||||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
# 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
|
# Caching only applies to RPC methods, so prices for open trades are still
|
||||||
# refreshed once every iteration.
|
# refreshed once every iteration.
|
||||||
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
|
|
||||||
# Holds candles
|
# Holds candles
|
||||||
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
|
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
|
||||||
@ -184,8 +184,8 @@ class Exchange:
|
|||||||
self.required_candle_call_count = self.validate_required_startup_candles(
|
self.required_candle_call_count = self.validate_required_startup_candles(
|
||||||
config.get('startup_candle_count', 0), config.get('timeframe', ''))
|
config.get('startup_candle_count', 0), config.get('timeframe', ''))
|
||||||
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
||||||
self.validate_pricing(config['ask_strategy'])
|
self.validate_pricing(config['exit_pricing'])
|
||||||
self.validate_pricing(config['bid_strategy'])
|
self.validate_pricing(config['entry_pricing'])
|
||||||
|
|
||||||
# Converts the interval provided in minutes in config to seconds
|
# Converts the interval provided in minutes in config to seconds
|
||||||
self.markets_refresh_interval: int = exchange_config.get(
|
self.markets_refresh_interval: int = exchange_config.get(
|
||||||
@ -1438,7 +1438,8 @@ class Exchange:
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from 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
|
Calculates bid/ask target
|
||||||
bid rate - between current ask price and last price
|
bid rate - between current ask price and last price
|
||||||
@ -1450,9 +1451,10 @@ class Exchange:
|
|||||||
:return: float: Price
|
:return: float: Price
|
||||||
:raises PricingError if orderbook price could not be determined.
|
:raises PricingError if orderbook price could not be determined.
|
||||||
"""
|
"""
|
||||||
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
|
name = side.capitalize()
|
||||||
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
|
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:
|
if not refresh:
|
||||||
rate = cache_rate.get(pair)
|
rate = cache_rate.get(pair)
|
||||||
# Check if cache has been invalidated
|
# Check if cache has been invalidated
|
||||||
@ -1462,6 +1464,23 @@ class Exchange:
|
|||||||
|
|
||||||
conf_strategy = self._config.get(strat_name, {})
|
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):
|
if conf_strategy.get('use_order_book', False):
|
||||||
|
|
||||||
order_book_top = conf_strategy.get('order_book_top', 1)
|
order_book_top = conf_strategy.get('order_book_top', 1)
|
||||||
@ -1469,26 +1488,25 @@ class Exchange:
|
|||||||
logger.debug('order_book %s', order_book)
|
logger.debug('order_book %s', order_book)
|
||||||
# top 1 = index 0
|
# top 1 = index 0
|
||||||
try:
|
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:
|
except (IndexError, KeyError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{name} Price at location {order_book_top} from orderbook could not be "
|
f"{name} Price at location {order_book_top} from orderbook could not be "
|
||||||
f"determined. Orderbook: {order_book}"
|
f"determined. Orderbook: {order_book}"
|
||||||
)
|
)
|
||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
price_side = {conf_strategy['price_side'].capitalize()}
|
logger.debug(f"{name} price from orderbook {price_side_word}"
|
||||||
logger.debug(f"{name} price from orderbook {price_side}"
|
|
||||||
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
||||||
else:
|
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 = self.fetch_ticker(pair)
|
||||||
ticker_rate = ticker[conf_strategy['price_side']]
|
ticker_rate = ticker[price_side]
|
||||||
if ticker['last'] and ticker_rate:
|
if ticker['last'] and ticker_rate:
|
||||||
if side == 'buy' and ticker_rate > ticker['last']:
|
if side == 'entry' and ticker_rate > ticker['last']:
|
||||||
balance = conf_strategy.get('ask_last_balance', 0.0)
|
balance = conf_strategy.get('price_last_balance', 0.0)
|
||||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||||
elif side == 'sell' and ticker_rate < ticker['last']:
|
elif side == 'exit' and ticker_rate < ticker['last']:
|
||||||
balance = conf_strategy.get('bid_last_balance', 0.0)
|
balance = conf_strategy.get('price_last_balance', 0.0)
|
||||||
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
||||||
rate = ticker_rate
|
rate = ticker_rate
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import traceback
|
|||||||
from datetime import datetime, time, timezone
|
from datetime import datetime, time, timezone
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from threading import Lock
|
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
|
from schedule import Scheduler
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if signal:
|
if signal:
|
||||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
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
|
if ((bid_check_dom.get('enabled', False)) and
|
||||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||||
if self._check_depth_of_market(pair, bid_check_dom, side=signal):
|
if self._check_depth_of_market(pair, bid_check_dom, side=signal):
|
||||||
@ -511,7 +511,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.debug("Max adjustment entries is set to unlimited.")
|
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)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
|
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']
|
time_in_force = self.strategy.order_time_in_force['entry']
|
||||||
|
|
||||||
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
|
[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
|
pos_adjust = trade is not None
|
||||||
|
|
||||||
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
|
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:
|
if not stake_amount:
|
||||||
return False
|
return False
|
||||||
@ -745,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def get_valid_enter_price_and_stake(
|
def get_valid_enter_price_and_stake(
|
||||||
self, pair: str, price: Optional[float], stake_amount: float,
|
self, pair: str, price: Optional[float], stake_amount: float,
|
||||||
side: str, trade_side: str,
|
trade_side: Literal['long', 'short'],
|
||||||
entry_tag: Optional[str],
|
entry_tag: Optional[str],
|
||||||
trade: Optional[Trade]
|
trade: Optional[Trade]
|
||||||
) -> Tuple[float, float, float]:
|
) -> Tuple[float, float, float]:
|
||||||
@ -754,7 +755,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
enter_limit_requested = price
|
enter_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# 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,
|
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||||
default_retval=proposed_enter_rate)(
|
default_retval=proposed_enter_rate)(
|
||||||
pair=pair, current_time=datetime.now(timezone.utc),
|
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)
|
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||||
|
|
||||||
if not enter_limit_requested:
|
if not enter_limit_requested:
|
||||||
raise PricingError(f'Could not determine {side} price.')
|
raise PricingError('Could not determine entry price.')
|
||||||
|
|
||||||
if trade is None:
|
if trade is None:
|
||||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||||
@ -824,7 +826,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
current_rate = trade.open_rate_requested
|
current_rate = trade.open_rate_requested
|
||||||
if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
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 = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a entry order cancel occurred.
|
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_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
|
||||||
msg = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
@ -935,7 +939,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug('checking exit')
|
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):
|
if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -1433,7 +1438,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
# Use cached rates here - it was updated seconds ago.
|
# Use cached rates here - it was updated seconds ago.
|
||||||
current_rate = self.exchange.get_rate(
|
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)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
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_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
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)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
|
@ -115,9 +115,9 @@ class Hyperopt:
|
|||||||
|
|
||||||
if HyperoptTools.has_space(self.config, 'sell'):
|
if HyperoptTools.has_space(self.config, 'sell'):
|
||||||
# Make sure use_sell_signal is enabled
|
# Make sure use_sell_signal is enabled
|
||||||
if 'ask_strategy' not in self.config:
|
if 'exit_pricing' not in self.config:
|
||||||
self.config['ask_strategy'] = {}
|
self.config['exit_pricing'] = {}
|
||||||
self.config['ask_strategy']['use_sell_signal'] = True
|
self.config['exit_pricing']['use_sell_signal'] = True
|
||||||
|
|
||||||
self.print_all = self.config.get('print_all', False)
|
self.print_all = self.config.get('print_all', False)
|
||||||
self.hyperopt_table_header = 0
|
self.hyperopt_table_header = 0
|
||||||
|
@ -175,8 +175,8 @@ class ShowConfig(BaseModel):
|
|||||||
exchange: str
|
exchange: str
|
||||||
strategy: Optional[str]
|
strategy: Optional[str]
|
||||||
forcebuy_enabled: bool
|
forcebuy_enabled: bool
|
||||||
ask_strategy: Dict[str, Any]
|
exit_pricing: Dict[str, Any]
|
||||||
bid_strategy: Dict[str, Any]
|
entry_pricing: Dict[str, Any]
|
||||||
bot_name: str
|
bot_name: str
|
||||||
state: str
|
state: str
|
||||||
runmode: str
|
runmode: str
|
||||||
|
@ -136,8 +136,8 @@ class RPC:
|
|||||||
'exchange': config['exchange']['name'],
|
'exchange': config['exchange']['name'],
|
||||||
'strategy': config['strategy'],
|
'strategy': config['strategy'],
|
||||||
'forcebuy_enabled': config.get('forcebuy_enable', False),
|
'forcebuy_enabled': config.get('forcebuy_enable', False),
|
||||||
'ask_strategy': config.get('ask_strategy', {}),
|
'exit_pricing': config.get('exit_pricing', {}),
|
||||||
'bid_strategy': config.get('bid_strategy', {}),
|
'entry_pricing': config.get('entry_pricing', {}),
|
||||||
'state': str(botstate),
|
'state': str(botstate),
|
||||||
'runmode': config['runmode'].value,
|
'runmode': config['runmode'].value,
|
||||||
'position_adjustment_enable': config.get('position_adjustment_enable', False),
|
'position_adjustment_enable': config.get('position_adjustment_enable', False),
|
||||||
@ -171,7 +171,7 @@ class RPC:
|
|||||||
if trade.is_open:
|
if trade.is_open:
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (ExchangeError, PricingError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
else:
|
else:
|
||||||
@ -231,7 +231,7 @@ class RPC:
|
|||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_profit = trade.calc_profit(current_rate)
|
trade_profit = trade.calc_profit(current_rate)
|
||||||
@ -485,7 +485,7 @@ class RPC:
|
|||||||
# Get current rate
|
# Get current rate
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
||||||
@ -705,7 +705,7 @@ class RPC:
|
|||||||
if not fully_canceled:
|
if not fully_canceled:
|
||||||
# Get current rate and execute sell
|
# Get current rate and execute sell
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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)
|
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL)
|
||||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||||
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
||||||
|
@ -1490,8 +1490,8 @@ class Telegram(RPCHandler):
|
|||||||
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
|
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
|
||||||
f"*Max open Trades:* `{val['max_open_trades']}`\n"
|
f"*Max open Trades:* `{val['max_open_trades']}`\n"
|
||||||
f"*Minimum ROI:* `{val['minimal_roi']}`\n"
|
f"*Minimum ROI:* `{val['minimal_roi']}`\n"
|
||||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
f"*Entry strategy:* ```\n{json.dumps(val['entry_pricing'])}```\n"
|
||||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
f"*Exit strategy:* ```\n{json.dumps(val['exit_pricing'])}```\n"
|
||||||
f"{sl_info}"
|
f"{sl_info}"
|
||||||
f"{pa_info}"
|
f"{pa_info}"
|
||||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||||
|
@ -331,7 +331,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return float: New entry price value if provided
|
:return float: New entry price value if provided
|
||||||
@ -369,7 +369,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return float: New exit price value if provided
|
:return float: New exit price value if provided
|
||||||
@ -393,7 +393,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 proposed_stake: A stake amount proposed by the bot.
|
||||||
:param min_stake: Minimal stake size allowed by exchange.
|
:param min_stake: Minimal stake size allowed by exchange.
|
||||||
:param max_stake: Balance available for trading.
|
:param max_stake: Balance available for trading.
|
||||||
@ -474,7 +474,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 proposed_leverage: A leverage proposed by the bot.
|
||||||
:param max_leverage: Max leverage allowed on this pair
|
:param max_leverage: Max leverage allowed on this pair
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||||
|
@ -21,18 +21,18 @@
|
|||||||
"exit_timeout_count": 0,
|
"exit_timeout_count": 0,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"price_side": "bid",
|
"price_side": "same",
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
|
"price_last_balance": 0.0,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"exit_pricing":{
|
||||||
"price_side": "ask",
|
"price_side": "same",
|
||||||
"use_order_book": true,
|
"use_order_book": true,
|
||||||
"order_book_top": 1
|
"order_book_top": 1
|
||||||
},
|
},
|
||||||
|
@ -64,7 +64,7 @@ class {{ strategy }}(IStrategy):
|
|||||||
# Run "populate_indicators()" only for new candle.
|
# Run "populate_indicators()" only for new candle.
|
||||||
process_only_new_candles = False
|
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
|
use_sell_signal = True
|
||||||
sell_profit_only = False
|
sell_profit_only = False
|
||||||
ignore_roi_if_buy_signal = False
|
ignore_roi_if_buy_signal = False
|
||||||
|
@ -64,7 +64,7 @@ class SampleStrategy(IStrategy):
|
|||||||
# Run "populate_indicators()" only for new candle.
|
# Run "populate_indicators()" only for new candle.
|
||||||
process_only_new_candles = False
|
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
|
use_sell_signal = True
|
||||||
sell_profit_only = False
|
sell_profit_only = False
|
||||||
ignore_roi_if_buy_signal = False
|
ignore_roi_if_buy_signal = False
|
||||||
|
@ -23,7 +23,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate:
|
|||||||
|
|
||||||
:param pair: Pair that's currently analyzed
|
:param pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return float: New entry price value if provided
|
: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 pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return float: New exit price value if provided
|
: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 pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 proposed_stake: A stake amount proposed by the bot.
|
||||||
:param min_stake: Minimal stake size allowed by exchange.
|
:param min_stake: Minimal stake size allowed by exchange.
|
||||||
:param max_stake: Balance available for trading.
|
: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 pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 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.
|
: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
|
: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 pair: Pair that's currently analyzed
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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 proposed_leverage: A leverage proposed by the bot.
|
||||||
:param max_leverage: Max leverage allowed on this pair
|
:param max_leverage: Max leverage allowed on this pair
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||||
|
@ -419,8 +419,8 @@ def get_default_conf(testdatadir):
|
|||||||
"entry": 10,
|
"entry": 10,
|
||||||
"exit": 30
|
"exit": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"entry_pricing": {
|
||||||
"ask_last_balance": 0.0,
|
"price_last_balance": 0.0,
|
||||||
"use_order_book": False,
|
"use_order_book": False,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
@ -428,7 +428,7 @@ def get_default_conf(testdatadir):
|
|||||||
"bids_to_ask_delta": 1
|
"bids_to_ask_delta": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"exit_pricing": {
|
||||||
"use_order_book": False,
|
"use_order_book": False,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
},
|
},
|
||||||
|
@ -107,8 +107,8 @@ def exchange_conf():
|
|||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
config['dry_run'] = False
|
config['dry_run'] = False
|
||||||
config['bid_strategy']['use_order_book'] = True
|
config['entry_pricing']['use_order_book'] = True
|
||||||
config['ask_strategy']['use_order_book'] = True
|
config['exit_pricing']['use_order_book'] = True
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -971,7 +971,7 @@ def test_validate_pricing(default_conf, mocker):
|
|||||||
|
|
||||||
has.update({'fetchTicker': True})
|
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)
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
has.update({'fetchL2OrderBook': False})
|
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", [
|
@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, 0.0, 20), # Full ask side
|
||||||
('ask', 20, 19, 10, 1.0, 10), # Full last side
|
('ask', 20, 19, 10, 1.0, 10), # Full last side
|
||||||
('ask', 20, 19, 10, 0.5, 15), # Between ask and last
|
('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', 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, 1.0, 5), # last bigger than ask
|
||||||
('ask', 5, 6, 10, 0.5, 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', 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, 0.5, 4), # last not available - uses ask
|
||||||
('ask', 4, 5, None, 1, 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
|
('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, 0.0, 20), # Full bid side
|
||||||
('bid', 21, 20, 10, 1.0, 10), # Full last 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.5, 15), # Between bid and last
|
||||||
('bid', 21, 20, 10, 0.7, 13), # 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', 21, 20, 10, 0.3, 17), # Between bid and last
|
||||||
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
|
('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', 6, 5, 10, 0.5, 5), # last bigger than bid
|
||||||
('bid', 21, 20, None, 0.5, 20), # last not available - uses 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, 0.5, 5), # last not available - uses bid
|
||||||
('bid', 6, 5, None, 1, 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
|
('bid', 6, 5, None, 0, 5), # last not available - uses bid
|
||||||
])
|
])
|
||||||
def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||||
last, last_ab, expected) -> None:
|
last, last_ab, expected) -> None:
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
if last_ab is None:
|
if last_ab is None:
|
||||||
del default_conf['bid_strategy']['ask_last_balance']
|
del default_conf['entry_pricing']['price_last_balance']
|
||||||
else:
|
else:
|
||||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
default_conf['entry_pricing']['price_last_balance'] = last_ab
|
||||||
default_conf['bid_strategy']['price_side'] = side
|
default_conf['entry_pricing']['price_side'] = side
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||||
|
|
||||||
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
|
||||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected
|
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
|
||||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||||
# Running a 2nd time with Refresh on!
|
# Running a 2nd time with Refresh on!
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
|
||||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [
|
@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, 0.0, 0.006),
|
||||||
('ask', 0.006, 1.0, 11.0, None, 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:
|
last, last_ab, expected) -> None:
|
||||||
caplog.set_level(logging.DEBUG)
|
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:
|
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',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': ask, 'bid': bid, 'last': last})
|
return_value={'ask': ask, 'bid': bid, 'last': last})
|
||||||
pair = "ETH/BTC"
|
pair = "ETH/BTC"
|
||||||
|
|
||||||
# Test regular mode
|
# Test regular mode
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=True)
|
||||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
|
||||||
assert isinstance(rate, float)
|
assert isinstance(rate, float)
|
||||||
assert rate == expected
|
assert rate == expected
|
||||||
# Use caching
|
# 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 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", [
|
@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [
|
||||||
('buy', 'ask', None, 4, 4, 0, 4), # ask not available
|
('entry', False, 'ask', None, 4, 4, 0, 4), # ask not available
|
||||||
('buy', 'ask', None, None, 4, 0, 4), # ask not available
|
('entry', False, 'ask', None, None, 4, 0, 4), # ask not available
|
||||||
('buy', 'bid', 6, None, 4, 0, 5), # bid not available
|
('entry', False, 'bid', 6, None, 4, 0, 5), # bid not available
|
||||||
('buy', 'bid', None, None, 4, 0, 5), # No rate available
|
('entry', False, 'bid', None, None, 4, 0, 5), # No rate available
|
||||||
('sell', 'ask', None, 4, 4, 0, 4), # ask not available
|
('exit', False, 'ask', None, 4, 4, 0, 4), # ask not available
|
||||||
('sell', 'ask', None, None, 4, 0, 4), # ask not available
|
('exit', False, 'ask', None, None, 4, 0, 4), # ask not available
|
||||||
('sell', 'bid', 6, None, 4, 0, 5), # bid not available
|
('exit', False, 'bid', 6, None, 4, 0, 5), # bid not available
|
||||||
('sell', 'bid', None, 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:
|
last, last_ab, expected) -> None:
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
default_conf['entry_pricing']['price_last_balance'] = last_ab
|
||||||
default_conf['bid_strategy']['price_side'] = side
|
default_conf['entry_pricing']['price_side'] = side
|
||||||
default_conf['ask_strategy']['price_side'] = side
|
default_conf['exit_pricing']['price_side'] = side
|
||||||
default_conf['ask_strategy']['ask_last_balance'] = last_ab
|
default_conf['exit_pricing']['price_last_balance'] = last_ab
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||||
|
|
||||||
with pytest.raises(PricingError):
|
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', [
|
@pytest.mark.parametrize('is_short,side,expected', [
|
||||||
('bid', 0.043936), # Value from order_book_l2 fiture - bids side
|
(False, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side
|
||||||
('ask', 0.043949), # Value from order_book_l2 fiture - asks 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)
|
caplog.set_level(logging.DEBUG)
|
||||||
# Test orderbook mode
|
# Test orderbook mode
|
||||||
default_conf['ask_strategy']['price_side'] = side
|
default_conf['exit_pricing']['price_side'] = side
|
||||||
default_conf['ask_strategy']['use_order_book'] = True
|
default_conf['exit_pricing']['use_order_book'] = True
|
||||||
default_conf['ask_strategy']['order_book_top'] = 1
|
default_conf['exit_pricing']['order_book_top'] = 1
|
||||||
pair = "ETH/BTC"
|
pair = "ETH/BTC"
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
|
||||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
|
||||||
assert isinstance(rate, float)
|
assert isinstance(rate, float)
|
||||||
assert rate == expected
|
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 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
|
# Test orderbook mode
|
||||||
default_conf['ask_strategy']['price_side'] = 'ask'
|
default_conf['exit_pricing']['price_side'] = 'ask'
|
||||||
default_conf['ask_strategy']['use_order_book'] = True
|
default_conf['exit_pricing']['use_order_book'] = True
|
||||||
default_conf['ask_strategy']['order_book_top'] = 1
|
default_conf['exit_pricing']['order_book_top'] = 1
|
||||||
pair = "ETH/BTC"
|
pair = "ETH/BTC"
|
||||||
# Test What happens if the exchange returns an empty orderbook.
|
# Test What happens if the exchange returns an empty orderbook.
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
|
||||||
return_value={'bids': [[]], 'asks': [[]]})
|
return_value={'bids': [[]], 'asks': [[]]})
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
with pytest.raises(PricingError):
|
with pytest.raises(PricingError):
|
||||||
exchange.get_rate(pair, refresh=True, side="sell")
|
exchange.get_rate(pair, refresh=True, side="exit", is_short=False)
|
||||||
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)
|
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.
|
# 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"
|
pair = "ETH/BTC"
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."):
|
||||||
exchange.get_rate(pair, refresh=True, side="sell")
|
exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
|
||||||
|
|
||||||
exchange._config['ask_strategy']['price_side'] = 'bid'
|
exchange._config['exit_pricing']['price_side'] = 'bid'
|
||||||
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12
|
assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12
|
||||||
# Reverse sides
|
# Reverse sides
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
||||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."):
|
||||||
exchange.get_rate(pair, refresh=True, side="sell")
|
exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short)
|
||||||
|
|
||||||
exchange._config['ask_strategy']['price_side'] = 'ask'
|
exchange._config['exit_pricing']['price_side'] = 'ask'
|
||||||
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
|
assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
|
@ -543,8 +543,8 @@ def test_api_show_config(botclient):
|
|||||||
assert response['trading_mode'] == 'spot'
|
assert response['trading_mode'] == 'spot'
|
||||||
assert response['strategy_version'] is None
|
assert response['strategy_version'] is None
|
||||||
assert not response['trailing_stop']
|
assert not response['trailing_stop']
|
||||||
assert 'bid_strategy' in response
|
assert 'entry_pricing' in response
|
||||||
assert 'ask_strategy' in response
|
assert 'exit_pricing' in response
|
||||||
assert 'unfilledtimeout' in response
|
assert 'unfilledtimeout' in response
|
||||||
assert 'version' in response
|
assert 'version' in response
|
||||||
assert 'api_version' in response
|
assert 'api_version' in response
|
||||||
|
@ -809,21 +809,21 @@ def test_validate_price_side(default_conf):
|
|||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['order_types']['entry'] = 'market'
|
conf['order_types']['entry'] = 'market'
|
||||||
with pytest.raises(OperationalException,
|
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)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['order_types']['exit'] = 'market'
|
conf['order_types']['exit'] = 'market'
|
||||||
with pytest.raises(OperationalException,
|
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_config_consistency(conf)
|
||||||
|
|
||||||
# Validate inversed case
|
# Validate inversed case
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['order_types']['exit'] = 'market'
|
conf['order_types']['exit'] = 'market'
|
||||||
conf['order_types']['entry'] = 'market'
|
conf['order_types']['entry'] = 'market'
|
||||||
conf['ask_strategy']['price_side'] = 'bid'
|
conf['exit_pricing']['price_side'] = 'bid'
|
||||||
conf['bid_strategy']['price_side'] = 'ask'
|
conf['entry_pricing']['price_side'] = 'ask'
|
||||||
|
|
||||||
validate_config_consistency(conf)
|
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:
|
def test_validate_ask_orderbook(default_conf, caplog) -> None:
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['ask_strategy']['use_order_book'] = True
|
conf['exit_pricing']['use_order_book'] = True
|
||||||
conf['ask_strategy']['order_book_min'] = 2
|
conf['exit_pricing']['order_book_min'] = 2
|
||||||
conf['ask_strategy']['order_book_max'] = 2
|
conf['exit_pricing']['order_book_max'] = 2
|
||||||
|
|
||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
assert log_has_re(r"DEPRECATED: Please use `order_book_top` instead of.*", caplog)
|
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,
|
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)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
|
|
||||||
@ -1023,6 +1023,44 @@ def test__validate_unfilledtimeout(default_conf, caplog) -> None:
|
|||||||
validate_config_consistency(conf)
|
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:
|
def test_load_config_test_comments() -> None:
|
||||||
"""
|
"""
|
||||||
Load config with comments
|
Load config with comments
|
||||||
@ -1434,15 +1472,15 @@ def test_flat_vars_to_nested_dict(caplog):
|
|||||||
'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
|
'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
|
||||||
'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
|
'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
|
||||||
'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
|
'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
|
||||||
'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid',
|
'FREQTRADE__EXIT_PRICING__PRICE_SIDE': 'bid',
|
||||||
'FREQTRADE__ASK_STRATEGY__cccc': '500',
|
'FREQTRADE__EXIT_PRICING__cccc': '500',
|
||||||
'FREQTRADE__STAKE_AMOUNT': '200.05',
|
'FREQTRADE__STAKE_AMOUNT': '200.05',
|
||||||
'FREQTRADE__TELEGRAM__CHAT_ID': '2151',
|
'FREQTRADE__TELEGRAM__CHAT_ID': '2151',
|
||||||
'NOT_RELEVANT': '200.0', # Will be ignored
|
'NOT_RELEVANT': '200.0', # Will be ignored
|
||||||
}
|
}
|
||||||
expected = {
|
expected = {
|
||||||
'stake_amount': 200.05,
|
'stake_amount': 200.05,
|
||||||
'ask_strategy': {
|
'exit_pricing': {
|
||||||
'price_side': 'bid',
|
'price_side': 'bid',
|
||||||
'cccc': 500,
|
'cccc': 500,
|
||||||
},
|
},
|
||||||
|
@ -96,7 +96,7 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
|
|||||||
'stoploss': 'limit',
|
'stoploss': 'limit',
|
||||||
'stoploss_on_exchange': True,
|
'stoploss_on_exchange': True,
|
||||||
}
|
}
|
||||||
conf['bid_strategy']['price_side'] = 'ask'
|
conf['entry_pricing']['price_side'] = 'ask'
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(conf)
|
freqtrade = FreqtradeBot(conf)
|
||||||
if runmode == RunMode.LIVE:
|
if runmode == RunMode.LIVE:
|
||||||
@ -898,7 +898,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
|||||||
# Fail to get price...
|
# Fail to get price...
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
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)
|
freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
|
||||||
|
|
||||||
# In case of custom entry price
|
# 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,
|
get_fee=fee,
|
||||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
_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
|
'ignore_roi_if_buy_signal': False
|
||||||
}
|
}
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
@ -4432,8 +4432,8 @@ def test_order_book_depth_of_market(
|
|||||||
):
|
):
|
||||||
ticker_side = 'ask' if is_short else 'bid'
|
ticker_side = 'ask' if is_short else 'bid'
|
||||||
|
|
||||||
default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
|
default_conf_usdt['entry_pricing']['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']['bids_to_ask_delta'] = delta
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||||
@ -4476,7 +4476,7 @@ def test_order_book_depth_of_market(
|
|||||||
(False, 0.045, 0.046, 2, None),
|
(False, 0.045, 0.046, 2, None),
|
||||||
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
|
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
|
||||||
])
|
])
|
||||||
def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
|
def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
|
||||||
ask, last, order_book_top, order_book, caplog) -> None:
|
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
|
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,
|
fetch_ticker=ticker_usdt_mock,
|
||||||
)
|
)
|
||||||
default_conf_usdt['exchange']['name'] = 'binance'
|
default_conf_usdt['exchange']['name'] = 'binance'
|
||||||
default_conf_usdt['bid_strategy']['use_order_book'] = True
|
default_conf_usdt['entry_pricing']['use_order_book'] = True
|
||||||
default_conf_usdt['bid_strategy']['order_book_top'] = order_book_top
|
default_conf_usdt['entry_pricing']['order_book_top'] = order_book_top
|
||||||
default_conf_usdt['bid_strategy']['ask_last_balance'] = 0
|
default_conf_usdt['entry_pricing']['price_last_balance'] = 0
|
||||||
default_conf_usdt['telegram']['enabled'] = False
|
default_conf_usdt['telegram']['enabled'] = False
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
if exception_thrown:
|
if exception_thrown:
|
||||||
with pytest.raises(PricingError):
|
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(
|
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:
|
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
|
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['telegram']['enabled'] = False
|
||||||
default_conf_usdt['exchange']['name'] = 'binance'
|
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
|
# 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)
|
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
|
assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('is_short', [False, True])
|
@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,
|
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:
|
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)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||||
default_conf_usdt['exchange']['name'] = 'binance'
|
default_conf_usdt['exchange']['name'] = 'binance'
|
||||||
default_conf_usdt['ask_strategy']['use_order_book'] = True
|
default_conf_usdt['exit_pricing']['use_order_book'] = True
|
||||||
default_conf_usdt['ask_strategy']['order_book_top'] = 1
|
default_conf_usdt['exit_pricing']['order_book_top'] = 1
|
||||||
default_conf_usdt['telegram']['enabled'] = False
|
default_conf_usdt['telegram']['enabled'] = False
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -4577,7 +4578,7 @@ def test_order_book_ask_strategy(
|
|||||||
return_value={'bids': [[]], 'asks': [[]]})
|
return_value={'bids': [[]], 'asks': [[]]})
|
||||||
with pytest.raises(PricingError):
|
with pytest.raises(PricingError):
|
||||||
freqtrade.handle_trade(trade)
|
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)
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user