Merge pull request #6648 from freqtrade/exit_type_rename
Exit type rename
This commit is contained in:
commit
d3e6fa19d5
@ -139,21 +139,21 @@
|
|||||||
"status": "on",
|
"status": "on",
|
||||||
"warning": "on",
|
"warning": "on",
|
||||||
"startup": "on",
|
"startup": "on",
|
||||||
"buy": "on",
|
"entry": "on",
|
||||||
"buy_fill": "on",
|
"entry_fill": "on",
|
||||||
"sell": {
|
"exit": {
|
||||||
"roi": "off",
|
"roi": "off",
|
||||||
"emergency_sell": "off",
|
"emergency_exit": "off",
|
||||||
"force_sell": "off",
|
"force_exit": "off",
|
||||||
"sell_signal": "off",
|
"exit_signal": "off",
|
||||||
"trailing_stop_loss": "off",
|
"trailing_stop_loss": "off",
|
||||||
"stop_loss": "off",
|
"stop_loss": "off",
|
||||||
"stoploss_on_exchange": "off",
|
"stoploss_on_exchange": "off",
|
||||||
"custom_sell": "off"
|
"custom_exit": "off"
|
||||||
},
|
},
|
||||||
"sell_fill": "on",
|
"exit_fill": "on",
|
||||||
"buy_cancel": "on",
|
"entry_cancel": "on",
|
||||||
"sell_cancel": "on",
|
"exit_cancel": "on",
|
||||||
"protection_trigger": "off",
|
"protection_trigger": "off",
|
||||||
"protection_trigger_global": "on"
|
"protection_trigger_global": "on"
|
||||||
},
|
},
|
||||||
|
@ -279,8 +279,8 @@ A backtesting result will look like that:
|
|||||||
|:-------------------|--------:|------:|-------:|--------:|
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||||
| stop_loss | 166 | 0 | 0 | 166 |
|
| stop_loss | 166 | 0 | 0 | 166 |
|
||||||
| sell_signal | 56 | 36 | 0 | 20 |
|
| exit_signal | 56 | 36 | 0 | 20 |
|
||||||
| force_sell | 2 | 0 | 0 | 2 |
|
| force_exit | 2 | 0 | 0 | 2 |
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||||
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||||
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
||||||
@ -362,7 +362,7 @@ Hence, keep in mind that your performance is an integral mix of all different el
|
|||||||
### Exit reasons table
|
### Exit reasons table
|
||||||
|
|
||||||
The 2nd table contains a recap of exit reasons.
|
The 2nd table contains a recap of exit reasons.
|
||||||
This table can tell you which area needs some additional work (e.g. all or many of the `sell_signal` trades are losses, so you should work on improving the sell signal, or consider disabling it).
|
This table can tell you which area needs some additional work (e.g. all or many of the `exit_signal` trades are losses, so you should work on improving the sell signal, or consider disabling it).
|
||||||
|
|
||||||
### Left open trades table
|
### Left open trades table
|
||||||
|
|
||||||
|
@ -150,10 +150,12 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
|
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
|
||||||
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
||||||
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.webhookentry` | Payload to send on entry. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookbuycancel` | Payload to send on buy order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.webhookentrycancel` | Payload to send on entry order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.webhookentryfill` | Payload to send on entry order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhooksellcancel` | Payload to send on sell order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.webhookexit` | Payload to send on exit. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
|
| `webhook.webhookexitcancel` | Payload to send on exit order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
|
| `webhook.webhookexitfill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
||||||
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
||||||
|
@ -57,7 +57,19 @@ While we may drop support for the current interface sometime in the future, we w
|
|||||||
|
|
||||||
Please follow the [Strategy migration](strategy_migration.md) guide to migrate your strategy to the new format to start using the new functionalities.
|
Please follow the [Strategy migration](strategy_migration.md) guide to migrate your strategy to the new format to start using the new functionalities.
|
||||||
|
|
||||||
### webhooks - `buy_tag` has been renamed to `enter_tag`
|
### webhooks - changes with 2022.4
|
||||||
|
|
||||||
|
#### `buy_tag` has been renamed to `enter_tag`
|
||||||
|
|
||||||
This should apply only to your strategy and potentially to webhooks.
|
This should apply only to your strategy and potentially to webhooks.
|
||||||
We will keep a compatibility layer for 1-2 versions (so both `buy_tag` and `enter_tag` will still work), but support for this in webhooks will disappear after that.
|
We will keep a compatibility layer for 1-2 versions (so both `buy_tag` and `enter_tag` will still work), but support for this in webhooks will disappear after that.
|
||||||
|
|
||||||
|
#### Naming changes
|
||||||
|
|
||||||
|
Webhook terminology changed from "sell" to "exit", and from "buy" to "entry".
|
||||||
|
*`webhookbuy` -> `webhookentry`
|
||||||
|
* `webhookbuyfill` -> `webhookentryfill`
|
||||||
|
*`webhookbuycancel` -> `webhookentrycancel`
|
||||||
|
* `webhooksell` -> `webhookexit`
|
||||||
|
*`webhooksellfill` -> `webhookexitfill`
|
||||||
|
* `webhooksellcancel` -> `webhookexitcancel`
|
||||||
|
@ -78,7 +78,7 @@ SET is_open=0,
|
|||||||
close_rate=0.19638016,
|
close_rate=0.19638016,
|
||||||
close_profit=0.0496,
|
close_profit=0.0496,
|
||||||
close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
|
close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
|
||||||
exit_reason='force_sell'
|
exit_reason='force_exit'
|
||||||
WHERE id=31;
|
WHERE id=31;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
!!! Warning "Backtesting"
|
!!! Warning "Backtesting"
|
||||||
Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range.
|
Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range.
|
||||||
Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle.
|
Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle.
|
||||||
`custom_exit_price()` is only called for sells of type Sell_signal and Custom exit. All other exit-types will use regular backtesting prices.
|
`custom_exit_price()` is only called for sells of type exit_signal and Custom exit. All other exit-types will use regular backtesting prices.
|
||||||
|
|
||||||
## Custom order timeout rules
|
## Custom order timeout rules
|
||||||
|
|
||||||
@ -564,13 +564,13 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
:param exit_reason: Exit reason.
|
:param exit_reason: Exit reason.
|
||||||
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'exit_signal', 'force_exit', 'emergency_exit']
|
||||||
:param current_time: datetime object, containing the current datetime
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the sell-order is placed on the exchange.
|
:return bool: When True is returned, then the exit-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
"""
|
"""
|
||||||
if exit_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0:
|
if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
|
||||||
# Reject force-sells with negative profit
|
# Reject force-sells with negative profit
|
||||||
# This is just a sample, please adjust to your needs
|
# This is just a sample, please adjust to your needs
|
||||||
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
||||||
|
@ -41,6 +41,28 @@ You can use the quick summary as checklist. Please refer to the detailed section
|
|||||||
* `order_time_in_force` buy -> entry, sell -> exit.
|
* `order_time_in_force` buy -> entry, sell -> exit.
|
||||||
* `order_types` buy -> entry, sell -> exit.
|
* `order_types` buy -> entry, sell -> exit.
|
||||||
* `unfilledtimeout` buy -> entry, sell -> exit.
|
* `unfilledtimeout` buy -> entry, sell -> exit.
|
||||||
|
* Terminology changes
|
||||||
|
* Sell reasons changed to reflect the new naming of "exit" instead of sells. Be careful in your strategy if you're using `exit_reason` checks and eventually update your strategy.
|
||||||
|
* `sell_signal` -> `exit_signal`
|
||||||
|
* `custom_sell` -> `custom_exit`
|
||||||
|
* `force_sell` -> `force_exit`
|
||||||
|
* `emergency_sell` -> `emergency_exit`
|
||||||
|
* Webhook terminology changed from "sell" to "exit", and from "buy" to entry
|
||||||
|
* `webhookbuy` -> `webhookentry`
|
||||||
|
* `webhookbuyfill` -> `webhookentryfill`
|
||||||
|
* `webhookbuycancel` -> `webhookentrycancel`
|
||||||
|
* `webhooksell` -> `webhookexit`
|
||||||
|
* `webhooksellfill` -> `webhookexitfill`
|
||||||
|
* `webhooksellcancel` -> `webhookexitcancel`
|
||||||
|
* Telegram notification settings
|
||||||
|
* `buy` -> `entry`
|
||||||
|
* `buy_fill` -> `entry_fill`
|
||||||
|
* `buy_cancel` -> `entry_cancel`
|
||||||
|
* `sell` -> `exit`
|
||||||
|
* `sell_fill` -> `exit_fill`
|
||||||
|
* `sell_cancel` -> `exit_cancel`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Extensive explanation
|
## Extensive explanation
|
||||||
|
|
||||||
|
@ -81,21 +81,21 @@ Example configuration showing the different settings:
|
|||||||
"status": "silent",
|
"status": "silent",
|
||||||
"warning": "on",
|
"warning": "on",
|
||||||
"startup": "off",
|
"startup": "off",
|
||||||
"buy": "silent",
|
"entry": "silent",
|
||||||
"sell": {
|
"exit": {
|
||||||
"roi": "silent",
|
"roi": "silent",
|
||||||
"emergency_sell": "on",
|
"emergency_exit": "on",
|
||||||
"force_sell": "on",
|
"force_exit": "on",
|
||||||
"sell_signal": "silent",
|
"exit_signal": "silent",
|
||||||
"trailing_stop_loss": "on",
|
"trailing_stop_loss": "on",
|
||||||
"stop_loss": "on",
|
"stop_loss": "on",
|
||||||
"stoploss_on_exchange": "on",
|
"stoploss_on_exchange": "on",
|
||||||
"custom_sell": "silent"
|
"custom_exit": "silent"
|
||||||
},
|
},
|
||||||
"buy_cancel": "silent",
|
"entry_cancel": "silent",
|
||||||
"sell_cancel": "on",
|
"exit_cancel": "on",
|
||||||
"buy_fill": "off",
|
"entry_fill": "off",
|
||||||
"sell_fill": "off",
|
"exit_fill": "off",
|
||||||
"protection_trigger": "off",
|
"protection_trigger": "off",
|
||||||
"protection_trigger_global": "on"
|
"protection_trigger_global": "on"
|
||||||
},
|
},
|
||||||
@ -104,8 +104,8 @@ Example configuration showing the different settings:
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
`buy` notifications are sent when the order is placed, while `buy_fill` notifications are sent when the order is filled on the exchange.
|
`entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`sell` notifications are sent when the order is placed, while `sell_fill` notifications are sent when the order is filled on the exchange.
|
`exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`*_fill` notifications are off by default and must be explicitly enabled.
|
`*_fill` notifications are off by default and must be explicitly enabled.
|
||||||
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
|
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
|
||||||
|
|
||||||
|
@ -10,33 +10,33 @@ Sample configuration (tested using IFTTT).
|
|||||||
"webhook": {
|
"webhook": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://maker.ifttt.com/trigger/<YOUREVENT>/with/key/<YOURKEY>/",
|
"url": "https://maker.ifttt.com/trigger/<YOUREVENT>/with/key/<YOURKEY>/",
|
||||||
"webhookbuy": {
|
"webhookentry": {
|
||||||
"value1": "Buying {pair}",
|
"value1": "Buying {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhookbuycancel": {
|
"webhookentrycancel": {
|
||||||
"value1": "Cancelling Open Buy Order for {pair}",
|
"value1": "Cancelling Open Buy Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhookbuyfill": {
|
"webhookentryfill": {
|
||||||
"value1": "Buy Order for {pair} filled",
|
"value1": "Buy Order for {pair} filled",
|
||||||
"value2": "at {open_rate:8f}",
|
"value2": "at {open_rate:8f}",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
},
|
},
|
||||||
"webhooksell": {
|
"webhookexit": {
|
||||||
"value1": "Selling {pair}",
|
"value1": "Exiting {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhooksellcancel": {
|
"webhookexitcancel": {
|
||||||
"value1": "Cancelling Open Sell Order for {pair}",
|
"value1": "Cancelling Open Exit Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhooksellfill": {
|
"webhookexitfill": {
|
||||||
"value1": "Sell Order for {pair} filled",
|
"value1": "Exit Order for {pair} filled",
|
||||||
"value2": "at {close_rate:8f}.",
|
"value2": "at {close_rate:8f}.",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
},
|
},
|
||||||
@ -96,9 +96,9 @@ Optional parameters are available to enable automatic retries for webhook messag
|
|||||||
|
|
||||||
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
|
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
|
||||||
|
|
||||||
### Webhookbuy
|
### Webhookentry
|
||||||
|
|
||||||
The fields in `webhook.webhookbuy` are filled when the bot executes a long/short. Parameters are filled using string.format.
|
The fields in `webhook.webhookentry` are filled when the bot executes a long/short. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -118,9 +118,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhookbuycancel
|
### Webhookentrycancel
|
||||||
|
|
||||||
The fields in `webhook.webhookbuycancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format.
|
The fields in `webhook.webhookentrycancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -139,9 +139,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhookbuyfill
|
### Webhookentryfill
|
||||||
|
|
||||||
The fields in `webhook.webhookbuyfill` are filled when the bot filled a long/short order. Parameters are filled using string.format.
|
The fields in `webhook.webhookentryfill` are filled when the bot filled a long/short order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -160,8 +160,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhooksell
|
### Webhookexit
|
||||||
The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format.
|
|
||||||
|
The fields in `webhook.webhookexit` are filled when the bot exits a trade. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -183,9 +184,9 @@ Possible parameters are:
|
|||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
### Webhooksellfill
|
### Webhookexitfill
|
||||||
|
|
||||||
The fields in `webhook.webhooksellfill` are filled when the bot fills a sell order (closes a Trae). Parameters are filled using string.format.
|
The fields in `webhook.webhookexitfill` are filled when the bot fills a exit order (closes a Trade). Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -208,9 +209,9 @@ Possible parameters are:
|
|||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
### Webhooksellcancel
|
### Webhookexitcancel
|
||||||
|
|
||||||
The fields in `webhook.webhooksellcancel` are filled when the bot cancels a sell order. Parameters are filled using string.format.
|
The fields in `webhook.webhookexitcancel` are filled when the bot cancels a exit order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
|
@ -82,6 +82,31 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
|||||||
None, 'ignore_roi_if_buy_signal')
|
None, 'ignore_roi_if_buy_signal')
|
||||||
process_deprecated_setting(config, 'ask_strategy', 'ignore_buying_expired_candle_after',
|
process_deprecated_setting(config, 'ask_strategy', 'ignore_buying_expired_candle_after',
|
||||||
None, 'ignore_buying_expired_candle_after')
|
None, 'ignore_buying_expired_candle_after')
|
||||||
|
# New settings
|
||||||
|
if config.get('telegram'):
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'sell',
|
||||||
|
'notification_settings', 'exit')
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'sell_fill',
|
||||||
|
'notification_settings', 'exit_fill')
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'sell_cancel',
|
||||||
|
'notification_settings', 'exit_cancel')
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'buy',
|
||||||
|
'notification_settings', 'entry')
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'buy_fill',
|
||||||
|
'notification_settings', 'entry_fill')
|
||||||
|
process_deprecated_setting(config['telegram'], 'notification_settings', 'buy_cancel',
|
||||||
|
'notification_settings', 'entry_cancel')
|
||||||
|
if config.get('webhook'):
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhookbuy', 'webhook', 'webhookentry')
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhookbuycancel',
|
||||||
|
'webhook', 'webhookentrycancel')
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhookbuyfill',
|
||||||
|
'webhook', 'webhookentryfill')
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhooksell', 'webhook', 'webhookexit')
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhooksellcancel',
|
||||||
|
'webhook', 'webhookexitcancel')
|
||||||
|
process_deprecated_setting(config, 'webhook', 'webhooksellfill',
|
||||||
|
'webhook', 'webhookexitfill')
|
||||||
|
|
||||||
# Legacy way - having them in experimental ...
|
# Legacy way - having them in experimental ...
|
||||||
process_removed_setting(config, 'experimental', 'use_sell_signal',
|
process_removed_setting(config, 'experimental', 'use_sell_signal',
|
||||||
|
@ -285,21 +285,21 @@ CONF_SCHEMA = {
|
|||||||
'status': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'status': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'buy': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'buy_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'buy_fill': {'type': 'string',
|
'entry_fill': {'type': 'string',
|
||||||
'enum': TELEGRAM_SETTING_OPTIONS,
|
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||||
'default': 'off'
|
'default': 'off'
|
||||||
},
|
},
|
||||||
'sell': {
|
'exit': {
|
||||||
'type': ['string', 'object'],
|
'type': ['string', 'object'],
|
||||||
'additionalProperties': {
|
'additionalProperties': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': TELEGRAM_SETTING_OPTIONS
|
'enum': TELEGRAM_SETTING_OPTIONS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'sell_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
|
||||||
'sell_fill': {
|
'exit_fill': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': TELEGRAM_SETTING_OPTIONS,
|
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||||
'default': 'off'
|
'default': 'off'
|
||||||
@ -327,12 +327,12 @@ CONF_SCHEMA = {
|
|||||||
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
|
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
|
||||||
'retries': {'type': 'integer', 'minimum': 0},
|
'retries': {'type': 'integer', 'minimum': 0},
|
||||||
'retry_delay': {'type': 'number', 'minimum': 0},
|
'retry_delay': {'type': 'number', 'minimum': 0},
|
||||||
'webhookbuy': {'type': 'object'},
|
'webhookentry': {'type': 'object'},
|
||||||
'webhookbuycancel': {'type': 'object'},
|
'webhookentrycancel': {'type': 'object'},
|
||||||
'webhookbuyfill': {'type': 'object'},
|
'webhookentryfill': {'type': 'object'},
|
||||||
'webhooksell': {'type': 'object'},
|
'webhookexit': {'type': 'object'},
|
||||||
'webhooksellcancel': {'type': 'object'},
|
'webhookexitcancel': {'type': 'object'},
|
||||||
'webhooksellfill': {'type': 'object'},
|
'webhookexitfill': {'type': 'object'},
|
||||||
'webhookstatus': {'type': 'object'},
|
'webhookstatus': {'type': 'object'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -478,7 +478,7 @@ CANCEL_REASON = {
|
|||||||
"FULLY_CANCELLED": "fully cancelled",
|
"FULLY_CANCELLED": "fully cancelled",
|
||||||
"ALL_CANCELLED": "cancelled (all unfilled and partially filled open orders cancelled)",
|
"ALL_CANCELLED": "cancelled (all unfilled and partially filled open orders cancelled)",
|
||||||
"CANCELLED_ON_EXCHANGE": "cancelled on exchange",
|
"CANCELLED_ON_EXCHANGE": "cancelled on exchange",
|
||||||
"FORCE_SELL": "forcesold",
|
"FORCE_EXIT": "forcesold",
|
||||||
}
|
}
|
||||||
|
|
||||||
# List of pairs with their timeframes
|
# List of pairs with their timeframes
|
||||||
|
@ -470,7 +470,7 @@ class Edge:
|
|||||||
if len(ohlc_columns) - 1 < exit_index:
|
if len(ohlc_columns) - 1 < exit_index:
|
||||||
break
|
break
|
||||||
|
|
||||||
exit_type = ExitType.SELL_SIGNAL
|
exit_type = ExitType.EXIT_SIGNAL
|
||||||
exit_price = ohlc_columns[exit_index, 0]
|
exit_price = ohlc_columns[exit_index, 0]
|
||||||
|
|
||||||
trade = {'pair': pair,
|
trade = {'pair': pair,
|
||||||
|
@ -9,10 +9,10 @@ class ExitType(Enum):
|
|||||||
STOP_LOSS = "stop_loss"
|
STOP_LOSS = "stop_loss"
|
||||||
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
|
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
|
||||||
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
||||||
SELL_SIGNAL = "sell_signal"
|
EXIT_SIGNAL = "exit_signal"
|
||||||
FORCE_SELL = "force_sell"
|
FORCE_EXIT = "force_exit"
|
||||||
EMERGENCY_SELL = "emergency_sell"
|
EMERGENCY_EXIT = "emergency_exit"
|
||||||
CUSTOM_SELL = "custom_sell"
|
CUSTOM_EXIT = "custom_exit"
|
||||||
NONE = ""
|
NONE = ""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -6,19 +6,13 @@ class RPCMessageType(Enum):
|
|||||||
WARNING = 'warning'
|
WARNING = 'warning'
|
||||||
STARTUP = 'startup'
|
STARTUP = 'startup'
|
||||||
|
|
||||||
BUY = 'buy'
|
ENTRY = 'entry'
|
||||||
BUY_FILL = 'buy_fill'
|
ENTRY_FILL = 'entry_fill'
|
||||||
BUY_CANCEL = 'buy_cancel'
|
ENTRY_CANCEL = 'entry_cancel'
|
||||||
|
|
||||||
SHORT = 'short'
|
EXIT = 'exit'
|
||||||
SHORT_FILL = 'short_fill'
|
EXIT_FILL = 'exit_fill'
|
||||||
SHORT_CANCEL = 'short_cancel'
|
EXIT_CANCEL = 'exit_cancel'
|
||||||
|
|
||||||
# TODO: The below messagetypes should be renamed to "exit"!
|
|
||||||
# Careful - has an impact on webhooks, therefore needs proper communication
|
|
||||||
SELL = 'sell'
|
|
||||||
SELL_FILL = 'sell_fill'
|
|
||||||
SELL_CANCEL = 'sell_cancel'
|
|
||||||
|
|
||||||
PROTECTION_TRIGGER = 'protection_trigger'
|
PROTECTION_TRIGGER = 'protection_trigger'
|
||||||
PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global'
|
PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global'
|
||||||
|
@ -819,10 +819,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a entry order occurred.
|
Sends rpc notification when a entry order occurred.
|
||||||
"""
|
"""
|
||||||
if fill:
|
msg_type = RPCMessageType.ENTRY_FILL if fill else RPCMessageType.ENTRY
|
||||||
msg_type = RPCMessageType.SHORT_FILL if trade.is_short else RPCMessageType.BUY_FILL
|
|
||||||
else:
|
|
||||||
msg_type = RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY
|
|
||||||
open_rate = safe_value_fallback(order, 'average', 'price')
|
open_rate = safe_value_fallback(order, 'average', 'price')
|
||||||
if open_rate is None:
|
if open_rate is None:
|
||||||
open_rate = trade.open_rate
|
open_rate = trade.open_rate
|
||||||
@ -861,10 +858,10 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
current_rate = self.exchange.get_rate(
|
current_rate = self.exchange.get_rate(
|
||||||
trade.pair, side='entry', is_short=trade.is_short, refresh=False)
|
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 = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
'type': msg_type,
|
'type': RPCMessageType.ENTRY_CANCEL,
|
||||||
'buy_tag': trade.enter_tag,
|
'buy_tag': trade.enter_tag,
|
||||||
'enter_tag': trade.enter_tag,
|
'enter_tag': trade.enter_tag,
|
||||||
'exchange': self.exchange.name.capitalize(),
|
'exchange': self.exchange.name.capitalize(),
|
||||||
@ -981,7 +978,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Exiting the trade forcefully')
|
logger.warning('Exiting the trade forcefully')
|
||||||
self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple(
|
self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple(
|
||||||
exit_type=ExitType.EMERGENCY_SELL))
|
exit_type=ExitType.EMERGENCY_EXIT))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
@ -1162,7 +1159,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
self.execute_trade_exit(
|
self.execute_trade_exit(
|
||||||
trade, order.get('price'),
|
trade, order.get('price'),
|
||||||
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_SELL))
|
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
||||||
@ -1380,7 +1377,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade = self.cancel_stoploss_on_exchange(trade)
|
trade = self.cancel_stoploss_on_exchange(trade)
|
||||||
|
|
||||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
order_type = ordertype or self.strategy.order_types[exit_type]
|
||||||
if exit_check.exit_type == ExitType.EMERGENCY_SELL:
|
if exit_check.exit_type == ExitType.EMERGENCY_EXIT:
|
||||||
# Emergency sells (default to market!)
|
# Emergency sells (default to market!)
|
||||||
order_type = self.strategy.order_types.get("emergencyexit", "market")
|
order_type = self.strategy.order_types.get("emergencyexit", "market")
|
||||||
|
|
||||||
@ -1446,8 +1443,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': (RPCMessageType.SELL_FILL if fill
|
'type': (RPCMessageType.EXIT_FILL if fill
|
||||||
else RPCMessageType.SELL),
|
else RPCMessageType.EXIT),
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
'exchange': trade.exchange.capitalize(),
|
'exchange': trade.exchange.capitalize(),
|
||||||
'pair': trade.pair,
|
'pair': trade.pair,
|
||||||
@ -1497,7 +1494,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SELL_CANCEL,
|
'type': RPCMessageType.EXIT_CANCEL,
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
'exchange': trade.exchange.capitalize(),
|
'exchange': trade.exchange.capitalize(),
|
||||||
'pair': trade.pair,
|
'pair': trade.pair,
|
||||||
|
@ -529,7 +529,7 @@ class Backtesting:
|
|||||||
# call the custom exit price,with default value as previous closerate
|
# call the custom exit price,with default value as previous closerate
|
||||||
current_profit = trade.calc_profit_ratio(closerate)
|
current_profit = trade.calc_profit_ratio(closerate)
|
||||||
order_type = self.strategy.order_types['exit']
|
order_type = self.strategy.order_types['exit']
|
||||||
if sell.exit_type in (ExitType.SELL_SIGNAL, ExitType.CUSTOM_SELL):
|
if sell.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT):
|
||||||
# Custom exit pricing only for sell-signals
|
# Custom exit pricing only for sell-signals
|
||||||
if order_type == 'limit':
|
if order_type == 'limit':
|
||||||
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
||||||
@ -812,7 +812,7 @@ class Backtesting:
|
|||||||
sell_row = data[pair][-1]
|
sell_row = data[pair][-1]
|
||||||
|
|
||||||
trade.close_date = sell_row[DATE_IDX].to_pydatetime()
|
trade.close_date = sell_row[DATE_IDX].to_pydatetime()
|
||||||
trade.exit_reason = ExitType.FORCE_SELL.value
|
trade.exit_reason = ExitType.FORCE_EXIT.value
|
||||||
trade.close(sell_row[OPEN_IDX], show_msg=False)
|
trade.close(sell_row[OPEN_IDX], show_msg=False)
|
||||||
LocalTrade.close_bt_trade(trade)
|
LocalTrade.close_bt_trade(trade)
|
||||||
# Deepcopy object to have wallets update correctly
|
# Deepcopy object to have wallets update correctly
|
||||||
|
@ -153,7 +153,13 @@ def migrate_trades_and_orders_table(
|
|||||||
{initial_stop_loss} initial_stop_loss,
|
{initial_stop_loss} initial_stop_loss,
|
||||||
{initial_stop_loss_pct} initial_stop_loss_pct,
|
{initial_stop_loss_pct} initial_stop_loss_pct,
|
||||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||||
{max_rate} max_rate, {min_rate} min_rate, {exit_reason} exit_reason,
|
{max_rate} max_rate, {min_rate} min_rate,
|
||||||
|
case when {exit_reason} == 'sell_signal' then 'exit_signal'
|
||||||
|
when {exit_reason} == 'custom_sell' then 'custom_exit'
|
||||||
|
when {exit_reason} == 'force_sell' then 'force_exit'
|
||||||
|
when {exit_reason} == 'emergency_sell' then 'emergency_exit'
|
||||||
|
else {exit_reason}
|
||||||
|
end exit_reason,
|
||||||
{exit_order_status} exit_order_status,
|
{exit_order_status} exit_order_status,
|
||||||
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
|
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
|
||||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||||
|
@ -697,17 +697,17 @@ class RPC:
|
|||||||
|
|
||||||
if order['side'] == trade.enter_side:
|
if order['side'] == trade.enter_side:
|
||||||
fully_canceled = self._freqtrade.handle_cancel_enter(
|
fully_canceled = self._freqtrade.handle_cancel_enter(
|
||||||
trade, order, CANCEL_REASON['FORCE_SELL'])
|
trade, order, CANCEL_REASON['FORCE_EXIT'])
|
||||||
|
|
||||||
if order['side'] == trade.exit_side:
|
if order['side'] == trade.exit_side:
|
||||||
# Cancel order - so it is placed anew with a fresh price.
|
# Cancel order - so it is placed anew with a fresh price.
|
||||||
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
|
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT'])
|
||||||
|
|
||||||
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, side='exit', is_short=trade.is_short, refresh=True)
|
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_EXIT)
|
||||||
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"])
|
||||||
|
|
||||||
|
@ -224,17 +224,16 @@ class Telegram(RPCHandler):
|
|||||||
# This can take up to `timeout` from the call to `start_polling`.
|
# This can take up to `timeout` from the call to `start_polling`.
|
||||||
self._updater.stop()
|
self._updater.stop()
|
||||||
|
|
||||||
def _format_buy_msg(self, msg: Dict[str, Any]) -> str:
|
def _format_entry_msg(self, msg: Dict[str, Any]) -> str:
|
||||||
if self._rpc._fiat_converter:
|
if self._rpc._fiat_converter:
|
||||||
msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount(
|
msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount(
|
||||||
msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
|
msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
|
||||||
else:
|
else:
|
||||||
msg['stake_amount_fiat'] = 0
|
msg['stake_amount_fiat'] = 0
|
||||||
is_fill = msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]
|
is_fill = msg['type'] in [RPCMessageType.ENTRY_FILL]
|
||||||
emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}'
|
emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}'
|
||||||
|
|
||||||
enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['type']
|
enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['direction'] == 'Long'
|
||||||
in [RPCMessageType.BUY_FILL, RPCMessageType.BUY]
|
|
||||||
else {'enter': 'Short', 'entered': 'Shorted'})
|
else {'enter': 'Short', 'entered': 'Shorted'})
|
||||||
message = (
|
message = (
|
||||||
f"{emoji} *{msg['exchange']}:*"
|
f"{emoji} *{msg['exchange']}:*"
|
||||||
@ -246,9 +245,9 @@ class Telegram(RPCHandler):
|
|||||||
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0:
|
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0:
|
||||||
message += f"*Leverage:* `{msg['leverage']}`\n"
|
message += f"*Leverage:* `{msg['leverage']}`\n"
|
||||||
|
|
||||||
if msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]:
|
if msg['type'] in [RPCMessageType.ENTRY_FILL]:
|
||||||
message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
|
message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
|
||||||
elif msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]:
|
elif msg['type'] in [RPCMessageType.ENTRY]:
|
||||||
message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\
|
message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\
|
||||||
f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
||||||
|
|
||||||
@ -260,7 +259,7 @@ class Telegram(RPCHandler):
|
|||||||
message += ")`"
|
message += ")`"
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _format_sell_msg(self, msg: Dict[str, Any]) -> str:
|
def _format_exit_msg(self, msg: Dict[str, Any]) -> str:
|
||||||
msg['amount'] = round(msg['amount'], 8)
|
msg['amount'] = round(msg['amount'], 8)
|
||||||
msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2)
|
msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2)
|
||||||
msg['duration'] = msg['close_date'].replace(
|
msg['duration'] = msg['close_date'].replace(
|
||||||
@ -284,7 +283,7 @@ class Telegram(RPCHandler):
|
|||||||
f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']})")
|
f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']})")
|
||||||
else:
|
else:
|
||||||
msg['profit_extra'] = ''
|
msg['profit_extra'] = ''
|
||||||
is_fill = msg['type'] == RPCMessageType.SELL_FILL
|
is_fill = msg['type'] == RPCMessageType.EXIT_FILL
|
||||||
message = (
|
message = (
|
||||||
f"{msg['emoji']} *{msg['exchange']}:* "
|
f"{msg['emoji']} *{msg['exchange']}:* "
|
||||||
f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n"
|
f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n"
|
||||||
@ -298,27 +297,24 @@ class Telegram(RPCHandler):
|
|||||||
f"*Amount:* `{msg['amount']:.8f}`\n"
|
f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||||
f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
|
f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
|
||||||
)
|
)
|
||||||
if msg['type'] == RPCMessageType.SELL:
|
if msg['type'] == RPCMessageType.EXIT:
|
||||||
message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
||||||
f"*Close Rate:* `{msg['limit']:.8f}`")
|
f"*Close Rate:* `{msg['limit']:.8f}`")
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.SELL_FILL:
|
elif msg['type'] == RPCMessageType.EXIT_FILL:
|
||||||
message += f"*Close Rate:* `{msg['close_rate']:.8f}`"
|
message += f"*Close Rate:* `{msg['close_rate']:.8f}`"
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
|
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
|
||||||
if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL, RPCMessageType.SHORT,
|
if msg_type in [RPCMessageType.ENTRY, RPCMessageType.ENTRY_FILL]:
|
||||||
RPCMessageType.SHORT_FILL]:
|
message = self._format_entry_msg(msg)
|
||||||
message = self._format_buy_msg(msg)
|
|
||||||
|
|
||||||
elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]:
|
elif msg_type in [RPCMessageType.EXIT, RPCMessageType.EXIT_FILL]:
|
||||||
message = self._format_sell_msg(msg)
|
message = self._format_exit_msg(msg)
|
||||||
|
|
||||||
elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL,
|
elif msg_type in (RPCMessageType.ENTRY_CANCEL, RPCMessageType.EXIT_CANCEL):
|
||||||
RPCMessageType.SELL_CANCEL):
|
msg['message_side'] = 'enter' if msg_type in [RPCMessageType.ENTRY_CANCEL] else 'exit'
|
||||||
msg['message_side'] = 'enter' if msg_type in [RPCMessageType.BUY_CANCEL,
|
|
||||||
RPCMessageType.SHORT_CANCEL] else 'exit'
|
|
||||||
message = ("\N{WARNING SIGN} *{exchange}:* "
|
message = ("\N{WARNING SIGN} *{exchange}:* "
|
||||||
"Cancelling {message_side} Order for {pair} (#{trade_id}). "
|
"Cancelling {message_side} Order for {pair} (#{trade_id}). "
|
||||||
"Reason: {reason}.".format(**msg))
|
"Reason: {reason}.".format(**msg))
|
||||||
@ -355,7 +351,7 @@ class Telegram(RPCHandler):
|
|||||||
|
|
||||||
msg_type = msg['type']
|
msg_type = msg['type']
|
||||||
noti = ''
|
noti = ''
|
||||||
if msg_type == RPCMessageType.SELL:
|
if msg_type == RPCMessageType.EXIT:
|
||||||
sell_noti = self._config['telegram'] \
|
sell_noti = self._config['telegram'] \
|
||||||
.get('notification_settings', {}).get(str(msg_type), {})
|
.get('notification_settings', {}).get(str(msg_type), {})
|
||||||
# For backward compatibility sell still can be string
|
# For backward compatibility sell still can be string
|
||||||
@ -768,9 +764,9 @@ class Telegram(RPCHandler):
|
|||||||
'stop_loss': 'Stoploss',
|
'stop_loss': 'Stoploss',
|
||||||
'trailing_stop_loss': 'Trail. Stop',
|
'trailing_stop_loss': 'Trail. Stop',
|
||||||
'stoploss_on_exchange': 'Stoploss',
|
'stoploss_on_exchange': 'Stoploss',
|
||||||
'sell_signal': 'Sell Signal',
|
'exit_signal': 'Exit Signal',
|
||||||
'force_sell': 'Forcesell',
|
'force_exit': 'Force Exit',
|
||||||
'emergency_sell': 'Emergency Sell',
|
'emergency_exit': 'Emergency Exit',
|
||||||
}
|
}
|
||||||
exit_reasons_tabulate = [
|
exit_reasons_tabulate = [
|
||||||
[
|
[
|
||||||
|
@ -43,23 +43,23 @@ class Webhook(RPCHandler):
|
|||||||
def send_msg(self, msg: Dict[str, Any]) -> None:
|
def send_msg(self, msg: Dict[str, Any]) -> None:
|
||||||
""" Send a message to telegram channel """
|
""" Send a message to telegram channel """
|
||||||
try:
|
try:
|
||||||
|
whconfig = self._config['webhook']
|
||||||
if msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]:
|
if msg['type'] in [RPCMessageType.ENTRY]:
|
||||||
valuedict = self._config['webhook'].get('webhookbuy', None)
|
valuedict = whconfig.get('webhookentry', None)
|
||||||
elif msg['type'] in [RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL]:
|
elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]:
|
||||||
valuedict = self._config['webhook'].get('webhookbuycancel', None)
|
valuedict = whconfig.get('webhookentrycancel', None)
|
||||||
elif msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]:
|
elif msg['type'] in [RPCMessageType.ENTRY_FILL]:
|
||||||
valuedict = self._config['webhook'].get('webhookbuyfill', None)
|
valuedict = whconfig.get('webhookentryfill', None)
|
||||||
elif msg['type'] == RPCMessageType.SELL:
|
elif msg['type'] == RPCMessageType.EXIT:
|
||||||
valuedict = self._config['webhook'].get('webhooksell', None)
|
valuedict = whconfig.get('webhookexit', None)
|
||||||
elif msg['type'] == RPCMessageType.SELL_FILL:
|
elif msg['type'] == RPCMessageType.EXIT_FILL:
|
||||||
valuedict = self._config['webhook'].get('webhooksellfill', None)
|
valuedict = whconfig.get('webhookexitfill', None)
|
||||||
elif msg['type'] == RPCMessageType.SELL_CANCEL:
|
elif msg['type'] == RPCMessageType.EXIT_CANCEL:
|
||||||
valuedict = self._config['webhook'].get('webhooksellcancel', None)
|
valuedict = whconfig.get('webhookexitcancel', None)
|
||||||
elif msg['type'] in (RPCMessageType.STATUS,
|
elif msg['type'] in (RPCMessageType.STATUS,
|
||||||
RPCMessageType.STARTUP,
|
RPCMessageType.STARTUP,
|
||||||
RPCMessageType.WARNING):
|
RPCMessageType.WARNING):
|
||||||
valuedict = self._config['webhook'].get('webhookstatus', None)
|
valuedict = whconfig.get('webhookstatus', None)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
||||||
if not valuedict:
|
if not valuedict:
|
||||||
|
@ -308,10 +308,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
:param exit_reason: Exit reason.
|
:param exit_reason: Exit reason.
|
||||||
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'exit_signal', 'force_exit', 'emergency_exit']
|
||||||
:param current_time: datetime object, containing the current datetime
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True, then the sell-order/exit_short-order is placed on the exchange.
|
:return bool: When True, then the exit-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
@ -888,14 +888,14 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
pass
|
pass
|
||||||
elif self.use_sell_signal and not enter:
|
elif self.use_sell_signal and not enter:
|
||||||
if exit_:
|
if exit_:
|
||||||
exit_signal = ExitType.SELL_SIGNAL
|
exit_signal = ExitType.EXIT_SIGNAL
|
||||||
else:
|
else:
|
||||||
trade_type = "exit_short" if trade.is_short else "sell"
|
trade_type = "exit_short" if trade.is_short else "sell"
|
||||||
custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
|
custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
|
||||||
pair=trade.pair, trade=trade, current_time=current_time,
|
pair=trade.pair, trade=trade, current_time=current_time,
|
||||||
current_rate=current_rate, current_profit=current_profit)
|
current_rate=current_rate, current_profit=current_profit)
|
||||||
if custom_reason:
|
if custom_reason:
|
||||||
exit_signal = ExitType.CUSTOM_SELL
|
exit_signal = ExitType.CUSTOM_EXIT
|
||||||
if isinstance(custom_reason, str):
|
if isinstance(custom_reason, str):
|
||||||
if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH:
|
if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH:
|
||||||
logger.warning(f'Custom {trade_type} reason returned from '
|
logger.warning(f'Custom {trade_type} reason returned from '
|
||||||
@ -904,7 +904,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH]
|
custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH]
|
||||||
else:
|
else:
|
||||||
custom_reason = None
|
custom_reason = None
|
||||||
if exit_signal in (ExitType.CUSTOM_SELL, ExitType.SELL_SIGNAL):
|
if exit_signal in (ExitType.CUSTOM_EXIT, ExitType.EXIT_SIGNAL):
|
||||||
logger.debug(f"{trade.pair} - Sell signal received. "
|
logger.debug(f"{trade.pair} - Sell signal received. "
|
||||||
f"exit_type=ExitType.{exit_signal.name}" +
|
f"exit_type=ExitType.{exit_signal.name}" +
|
||||||
(f", custom_reason={custom_reason}" if custom_reason else ""))
|
(f", custom_reason={custom_reason}" if custom_reason else ""))
|
||||||
|
@ -162,10 +162,10 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
|
|||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
:param exit_reason: Exit reason.
|
:param exit_reason: Exit reason.
|
||||||
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'exit_signal', 'force_exit', 'emergency_exit']
|
||||||
:param current_time: datetime object, containing the current datetime
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the sell-order is placed on the exchange.
|
:return bool: When True is returned, then the exit-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
@ -206,7 +206,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -
|
|||||||
:param trade: trade object.
|
:param trade: trade object.
|
||||||
:param order: Order dictionary as returned from CCXT.
|
:param order: Order dictionary as returned from CCXT.
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the sell-order is cancelled.
|
:return bool: When True is returned, then the exit-order is cancelled.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -95,8 +95,8 @@ tc1 = BTContainer(data=[
|
|||||||
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
||||||
],
|
],
|
||||||
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=2),
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=2),
|
||||||
BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=6)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
||||||
@ -391,7 +391,7 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
|
|||||||
'trade_duration': '',
|
'trade_duration': '',
|
||||||
'open_rate': 17,
|
'open_rate': 17,
|
||||||
'close_rate': 17,
|
'close_rate': 17,
|
||||||
'exit_type': 'sell_signal'},
|
'exit_type': 'exit_signal'},
|
||||||
|
|
||||||
{'pair': 'TEST/BTC',
|
{'pair': 'TEST/BTC',
|
||||||
'stoploss': -0.9,
|
'stoploss': -0.9,
|
||||||
@ -402,7 +402,7 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
|
|||||||
'trade_duration': '',
|
'trade_duration': '',
|
||||||
'open_rate': 20,
|
'open_rate': 20,
|
||||||
'close_rate': 20,
|
'close_rate': 20,
|
||||||
'exit_type': 'sell_signal'},
|
'exit_type': 'exit_signal'},
|
||||||
|
|
||||||
{'pair': 'TEST/BTC',
|
{'pair': 'TEST/BTC',
|
||||||
'stoploss': -0.9,
|
'stoploss': -0.9,
|
||||||
@ -413,7 +413,7 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
|
|||||||
'trade_duration': '',
|
'trade_duration': '',
|
||||||
'open_rate': 26,
|
'open_rate': 26,
|
||||||
'close_rate': 34,
|
'close_rate': 34,
|
||||||
'exit_type': 'sell_signal'}
|
'exit_type': 'exit_signal'}
|
||||||
]
|
]
|
||||||
|
|
||||||
trades_df = DataFrame(trades)
|
trades_df = DataFrame(trades)
|
||||||
|
@ -23,7 +23,7 @@ tc0 = BTContainer(data=[
|
|||||||
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
|
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
|
||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
|
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 1: Stop-Loss Triggered 1% loss
|
# Test 1: Stop-Loss Triggered 1% loss
|
||||||
@ -424,7 +424,7 @@ tc26 = BTContainer(data=[
|
|||||||
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
|
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
|
||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
|
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 27: (copy of test26 with leverage)
|
# Test 27: (copy of test26 with leverage)
|
||||||
@ -441,7 +441,7 @@ tc27 = BTContainer(data=[
|
|||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
|
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
|
||||||
leverage=5.0,
|
leverage=5.0,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 28: (copy of test26 with leverage and as short)
|
# Test 28: (copy of test26 with leverage and as short)
|
||||||
@ -458,7 +458,7 @@ tc28 = BTContainer(data=[
|
|||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]],
|
||||||
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
|
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
|
||||||
leverage=5.0,
|
leverage=5.0,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
|
||||||
)
|
)
|
||||||
# Test 29: Sell with signal sell in candle 3 (ROI at signal candle)
|
# Test 29: Sell with signal sell in candle 3 (ROI at signal candle)
|
||||||
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
|
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
|
||||||
@ -486,7 +486,7 @@ tc30 = BTContainer(data=[
|
|||||||
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
|
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
|
||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
|
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 31: trailing_stop should raise so candle 3 causes a stoploss
|
# Test 31: trailing_stop should raise so candle 3 causes a stoploss
|
||||||
@ -708,7 +708,7 @@ tc44 = BTContainer(data=[
|
|||||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01,
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01,
|
||||||
use_exit_signal=True,
|
use_exit_signal=True,
|
||||||
custom_exit_price=4552,
|
custom_exit_price=4552,
|
||||||
trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=3)]
|
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=3)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 45: Custom exit price above all candles
|
# Test 45: Custom exit price above all candles
|
||||||
@ -723,7 +723,7 @@ tc45 = BTContainer(data=[
|
|||||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
|
||||||
use_exit_signal=True,
|
use_exit_signal=True,
|
||||||
custom_exit_price=6052,
|
custom_exit_price=6052,
|
||||||
trades=[BTrade(exit_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4)]
|
trades=[BTrade(exit_reason=ExitType.FORCE_EXIT, open_tick=1, close_tick=4)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 46: (Short of tc45) Custom short exit price above below candles
|
# Test 46: (Short of tc45) Custom short exit price above below candles
|
||||||
@ -738,7 +738,7 @@ tc46 = BTContainer(data=[
|
|||||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
|
||||||
use_exit_signal=True,
|
use_exit_signal=True,
|
||||||
custom_exit_price=4700,
|
custom_exit_price=4700,
|
||||||
trades=[BTrade(exit_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4, is_short=True)]
|
trades=[BTrade(exit_reason=ExitType.FORCE_EXIT, open_tick=1, close_tick=4, is_short=True)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 47: Colliding long and short signal
|
# Test 47: Colliding long and short signal
|
||||||
|
@ -358,7 +358,7 @@ def test_hyperopt_format_results(hyperopt):
|
|||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||||
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
||||||
ExitType.ROI, ExitType.FORCE_SELL]
|
ExitType.ROI, ExitType.FORCE_EXIT]
|
||||||
}),
|
}),
|
||||||
'config': hyperopt.config,
|
'config': hyperopt.config,
|
||||||
'locks': [],
|
'locks': [],
|
||||||
@ -429,7 +429,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
|||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||||
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
||||||
ExitType.ROI, ExitType.FORCE_SELL]
|
ExitType.ROI, ExitType.FORCE_EXIT]
|
||||||
}),
|
}),
|
||||||
'config': hyperopt_conf,
|
'config': hyperopt_conf,
|
||||||
'locks': [],
|
'locks': [],
|
||||||
|
@ -77,7 +77,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
|||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||||
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
|
||||||
ExitType.ROI, ExitType.FORCE_SELL]
|
ExitType.ROI, ExitType.FORCE_EXIT]
|
||||||
}),
|
}),
|
||||||
'config': default_conf,
|
'config': default_conf,
|
||||||
'locks': [],
|
'locks': [],
|
||||||
@ -129,7 +129,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
|||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||||
"exit_reason": [ExitType.ROI, ExitType.ROI,
|
"exit_reason": [ExitType.ROI, ExitType.ROI,
|
||||||
ExitType.STOP_LOSS, ExitType.FORCE_SELL]
|
ExitType.STOP_LOSS, ExitType.FORCE_EXIT]
|
||||||
}),
|
}),
|
||||||
'config': default_conf,
|
'config': default_conf,
|
||||||
'locks': [],
|
'locks': [],
|
||||||
|
@ -11,7 +11,7 @@ from tests.conftest import get_patched_freqtradebot, log_has_re
|
|||||||
|
|
||||||
|
|
||||||
def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||||
sell_reason: str = ExitType.SELL_SIGNAL,
|
sell_reason: str = ExitType.EXIT_SIGNAL,
|
||||||
min_ago_open: int = None, min_ago_close: int = None,
|
min_ago_open: int = None, min_ago_close: int = None,
|
||||||
profit_rate: float = 0.9
|
profit_rate: float = 0.9
|
||||||
):
|
):
|
||||||
|
@ -1040,7 +1040,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
assert msg_mock.call_count == 4
|
assert msg_mock.call_count == 4
|
||||||
last_msg = msg_mock.call_args_list[-2][0][0]
|
last_msg = msg_mock.call_args_list[-2][0][0]
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
@ -1059,8 +1059,8 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': ExitType.FORCE_SELL.value,
|
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||||
'exit_reason': ExitType.FORCE_SELL.value,
|
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -1109,7 +1109,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
last_msg = msg_mock.call_args_list[-2][0][0]
|
last_msg = msg_mock.call_args_list[-2][0][0]
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
@ -1128,8 +1128,8 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': ExitType.FORCE_SELL.value,
|
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||||
'exit_reason': ExitType.FORCE_SELL.value,
|
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -1168,7 +1168,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
assert msg_mock.call_count == 8
|
assert msg_mock.call_count == 8
|
||||||
msg = msg_mock.call_args_list[0][0][0]
|
msg = msg_mock.call_args_list[0][0][0]
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
@ -1187,8 +1187,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': ExitType.FORCE_SELL.value,
|
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||||
'exit_reason': ExitType.FORCE_SELL.value,
|
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -1771,10 +1771,10 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
|
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
|
||||||
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
|
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', None),
|
||||||
(RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0),
|
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', 1.0),
|
||||||
(RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0),
|
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', 5.0),
|
||||||
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
|
(RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)])
|
||||||
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
||||||
enter, enter_signal, leverage) -> None:
|
enter, enter_signal, leverage) -> None:
|
||||||
|
|
||||||
@ -1787,6 +1787,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
|||||||
'leverage': leverage,
|
'leverage': leverage,
|
||||||
'limit': 1.099e-05,
|
'limit': 1.099e-05,
|
||||||
'order_type': 'limit',
|
'order_type': 'limit',
|
||||||
|
'direction': enter,
|
||||||
'stake_amount': 0.01465333,
|
'stake_amount': 0.01465333,
|
||||||
'stake_amount_fiat': 0.0,
|
'stake_amount_fiat': 0.0,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
@ -1827,8 +1828,8 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('message_type,enter_signal', [
|
@pytest.mark.parametrize('message_type,enter_signal', [
|
||||||
(RPCMessageType.BUY_CANCEL, 'long_signal_01'),
|
(RPCMessageType.ENTRY_CANCEL, 'long_signal_01'),
|
||||||
(RPCMessageType.SHORT_CANCEL, 'short_signal_01')])
|
(RPCMessageType.ENTRY_CANCEL, 'short_signal_01')])
|
||||||
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
|
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
|
||||||
|
|
||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
@ -1875,14 +1876,14 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
|
@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
|
||||||
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0),
|
(RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_01', 1.0),
|
||||||
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0),
|
(RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_02', 2.0),
|
||||||
(RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0),
|
(RPCMessageType.ENTRY_FILL, 'Short', 'short_signal_01', 2.0),
|
||||||
])
|
])
|
||||||
def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered,
|
def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, entered,
|
||||||
enter_signal, leverage) -> None:
|
enter_signal, leverage) -> None:
|
||||||
|
|
||||||
default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
|
default_conf['telegram']['notification_settings']['entry_fill'] = 'on'
|
||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
@ -1893,6 +1894,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, ente
|
|||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': leverage,
|
'leverage': leverage,
|
||||||
'stake_amount': 0.01465333,
|
'stake_amount': 0.01465333,
|
||||||
|
'direction': entered,
|
||||||
# 'stake_amount_fiat': 0.0,
|
# 'stake_amount_fiat': 0.0,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
@ -1902,7 +1904,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, ente
|
|||||||
})
|
})
|
||||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
|
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
|
||||||
assert msg_mock.call_args[0][0] == (
|
assert msg_mock.call_args[0][0] == (
|
||||||
f'\N{CHECK MARK} *Binance:* {entered} ETH/BTC (#1)\n'
|
f'\N{CHECK MARK} *Binance:* {entered}ed ETH/BTC (#1)\n'
|
||||||
f'*Enter Tag:* `{enter_signal}`\n'
|
f'*Enter Tag:* `{enter_signal}`\n'
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
f"{leverage_text}"
|
f"{leverage_text}"
|
||||||
@ -1918,7 +1920,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
@ -1954,7 +1956,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
@ -1996,7 +1998,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
|||||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL_CANCEL,
|
'type': RPCMessageType.EXIT_CANCEL,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
@ -2008,7 +2010,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL_CANCEL,
|
'type': RPCMessageType.EXIT_CANCEL,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
@ -2028,11 +2030,11 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
|||||||
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
||||||
enter_signal, leverage) -> None:
|
enter_signal, leverage) -> None:
|
||||||
|
|
||||||
default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
|
default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
|
||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL_FILL,
|
'type': RPCMessageType.EXIT_FILL,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
@ -2105,9 +2107,9 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
|
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
|
||||||
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
|
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', None),
|
||||||
(RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0),
|
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', 2.0),
|
||||||
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
|
(RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)])
|
||||||
def test_send_msg_buy_notification_no_fiat(
|
def test_send_msg_buy_notification_no_fiat(
|
||||||
default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
|
default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
|
||||||
del default_conf['fiat_display_currency']
|
del default_conf['fiat_display_currency']
|
||||||
@ -2122,6 +2124,7 @@ def test_send_msg_buy_notification_no_fiat(
|
|||||||
'leverage': leverage,
|
'leverage': leverage,
|
||||||
'limit': 1.099e-05,
|
'limit': 1.099e-05,
|
||||||
'order_type': 'limit',
|
'order_type': 'limit',
|
||||||
|
'direction': enter,
|
||||||
'stake_amount': 0.01465333,
|
'stake_amount': 0.01465333,
|
||||||
'stake_amount_fiat': 0.0,
|
'stake_amount_fiat': 0.0,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
@ -2155,7 +2158,7 @@ def test_send_msg_sell_notification_no_fiat(
|
|||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
|
@ -15,38 +15,38 @@ def get_webhook_dict() -> dict:
|
|||||||
return {
|
return {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||||
"webhookbuy": {
|
"webhookentry": {
|
||||||
"value1": "Buying {pair}",
|
"value1": "Buying {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}",
|
"value3": "{stake_amount:8f} {stake_currency}",
|
||||||
"value4": "leverage {leverage:.1f}",
|
"value4": "leverage {leverage:.1f}",
|
||||||
"value5": "direction {direction}"
|
"value5": "direction {direction}"
|
||||||
},
|
},
|
||||||
"webhookbuycancel": {
|
"webhookentrycancel": {
|
||||||
"value1": "Cancelling Open Buy Order for {pair}",
|
"value1": "Cancelling Open Buy Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}",
|
"value3": "{stake_amount:8f} {stake_currency}",
|
||||||
"value4": "leverage {leverage:.1f}",
|
"value4": "leverage {leverage:.1f}",
|
||||||
"value5": "direction {direction}"
|
"value5": "direction {direction}"
|
||||||
},
|
},
|
||||||
"webhookbuyfill": {
|
"webhookentryfill": {
|
||||||
"value1": "Buy Order for {pair} filled",
|
"value1": "Buy Order for {pair} filled",
|
||||||
"value2": "at {open_rate:8f}",
|
"value2": "at {open_rate:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}",
|
"value3": "{stake_amount:8f} {stake_currency}",
|
||||||
"value4": "leverage {leverage:.1f}",
|
"value4": "leverage {leverage:.1f}",
|
||||||
"value5": "direction {direction}"
|
"value5": "direction {direction}"
|
||||||
},
|
},
|
||||||
"webhooksell": {
|
"webhookexit": {
|
||||||
"value1": "Selling {pair}",
|
"value1": "Selling {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhooksellcancel": {
|
"webhookexitcancel": {
|
||||||
"value1": "Cancelling Open Sell Order for {pair}",
|
"value1": "Cancelling Open Sell Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhooksellfill": {
|
"webhookexitfill": {
|
||||||
"value1": "Sell Order for {pair} filled",
|
"value1": "Sell Order for {pair} filled",
|
||||||
"value2": "at {close_rate:8f}",
|
"value2": "at {close_rate:8f}",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
@ -74,7 +74,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.BUY,
|
'type': RPCMessageType.ENTRY,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 1.0,
|
'leverage': 1.0,
|
||||||
@ -88,20 +88,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
|
||||||
# Test short
|
# Test short
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SHORT,
|
'type': RPCMessageType.ENTRY,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 2.0,
|
'leverage': 2.0,
|
||||||
@ -115,20 +115,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
|
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
|
||||||
# Test buy cancel
|
# Test buy cancel
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.BUY_CANCEL,
|
'type': RPCMessageType.ENTRY_CANCEL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 1.0,
|
'leverage': 1.0,
|
||||||
@ -142,16 +142,16 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value3"].format(**msg))
|
||||||
# Test short cancel
|
# Test short cancel
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SHORT_CANCEL,
|
'type': RPCMessageType.ENTRY_CANCEL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 2.0,
|
'leverage': 2.0,
|
||||||
@ -165,20 +165,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value3"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
|
||||||
# Test buy fill
|
# Test buy fill
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.BUY_FILL,
|
'type': RPCMessageType.ENTRY_FILL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 1.0,
|
'leverage': 1.0,
|
||||||
@ -192,20 +192,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value3"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
|
||||||
# Test short fill
|
# Test short fill
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SHORT_FILL,
|
'type': RPCMessageType.ENTRY_FILL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'leverage': 2.0,
|
'leverage': 2.0,
|
||||||
@ -219,20 +219,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookentryfill"]["value3"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||||
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
|
default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
|
||||||
# Test sell
|
# Test sell
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'gain': "profit",
|
'gain': "profit",
|
||||||
@ -249,15 +249,15 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhooksell"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookexit"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhooksell"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookexit"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookexit"]["value3"].format(**msg))
|
||||||
# Test sell cancel
|
# Test sell cancel
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SELL_CANCEL,
|
'type': RPCMessageType.EXIT_CANCEL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'gain': "profit",
|
'gain': "profit",
|
||||||
@ -274,15 +274,15 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhooksellcancel"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookexitcancel"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookexitcancel"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookexitcancel"]["value3"].format(**msg))
|
||||||
# Test Sell fill
|
# Test Sell fill
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.SELL_FILL,
|
'type': RPCMessageType.EXIT_FILL,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'gain': "profit",
|
'gain': "profit",
|
||||||
@ -299,11 +299,11 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
default_conf["webhook"]["webhooksellfill"]["value1"].format(**msg))
|
default_conf["webhook"]["webhookexitfill"]["value1"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
default_conf["webhook"]["webhooksellfill"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookexitfill"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhooksellfill"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookexitfill"]["value3"].format(**msg))
|
||||||
|
|
||||||
for msgtype in [RPCMessageType.STATUS,
|
for msgtype in [RPCMessageType.STATUS,
|
||||||
RPCMessageType.WARNING,
|
RPCMessageType.WARNING,
|
||||||
@ -327,20 +327,20 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
|
|
||||||
def test_exception_send_msg(default_conf, mocker, caplog):
|
def test_exception_send_msg(default_conf, mocker, caplog):
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
del default_conf["webhook"]["webhookbuy"]
|
del default_conf["webhook"]["webhookentry"]
|
||||||
|
|
||||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||||
webhook.send_msg({'type': RPCMessageType.BUY})
|
webhook.send_msg({'type': RPCMessageType.ENTRY})
|
||||||
assert log_has(f"Message type '{RPCMessageType.BUY}' not configured for webhooks",
|
assert log_has(f"Message type '{RPCMessageType.ENTRY}' not configured for webhooks",
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}"
|
default_conf["webhook"]["webhookentry"]["value1"] = "{DEADBEEF:8f}"
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.BUY,
|
'type': RPCMessageType.ENTRY,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'limit': 0.005,
|
'limit': 0.005,
|
||||||
|
@ -503,15 +503,15 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
|
|||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.exit_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.exit_type == ExitType.CUSTOM_SELL
|
assert res.exit_type == ExitType.CUSTOM_EXIT
|
||||||
assert res.exit_reason == 'custom_sell'
|
assert res.exit_reason == 'custom_exit'
|
||||||
|
|
||||||
strategy.custom_exit = MagicMock(return_value='hello world')
|
strategy.custom_exit = MagicMock(return_value='hello world')
|
||||||
|
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.exit_type == ExitType.CUSTOM_SELL
|
assert res.exit_type == ExitType.CUSTOM_EXIT
|
||||||
assert res.exit_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.exit_reason == 'hello world'
|
assert res.exit_reason == 'hello world'
|
||||||
|
|
||||||
@ -520,7 +520,7 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
|
|||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.exit_type == ExitType.CUSTOM_SELL
|
assert res.exit_type == ExitType.CUSTOM_EXIT
|
||||||
assert res.exit_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.exit_reason == 'h' * 64
|
assert res.exit_reason == 'h' * 64
|
||||||
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
|
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
|
||||||
|
@ -1210,7 +1210,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
assert trade.exit_reason == str(ExitType.EMERGENCY_SELL)
|
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
@ -1293,7 +1293,7 @@ def test_create_stoploss_order_invalid_order(
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
freqtrade.create_stoploss_order(trade, 200)
|
freqtrade.create_stoploss_order(trade, 200)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.exit_reason == ExitType.EMERGENCY_SELL.value
|
assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value
|
||||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||||
assert log_has("Exiting the trade forcefully", caplog)
|
assert log_has("Exiting the trade forcefully", caplog)
|
||||||
|
|
||||||
@ -1305,7 +1305,7 @@ def test_create_stoploss_order_invalid_order(
|
|||||||
|
|
||||||
# Rpc is sending first buy, then sell
|
# Rpc is sending first buy, then sell
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == ExitType.EMERGENCY_SELL.value
|
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == ExitType.EMERGENCY_EXIT.value
|
||||||
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
|
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
|
||||||
|
|
||||||
|
|
||||||
@ -2310,7 +2310,7 @@ def test_handle_trade_use_sell_signal(
|
|||||||
else:
|
else:
|
||||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||||
assert freqtrade.handle_trade(trade)
|
assert freqtrade.handle_trade(trade)
|
||||||
assert log_has("ETH/USDT - Sell signal received. exit_type=ExitType.SELL_SIGNAL",
|
assert log_has("ETH/USDT - Sell signal received. exit_type=ExitType.EXIT_SIGNAL",
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
@ -3091,7 +3091,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
|||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
assert {
|
assert {
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/USDT',
|
'pair': 'ETH/USDT',
|
||||||
'gain': 'profit',
|
'gain': 'profit',
|
||||||
@ -3150,7 +3150,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
|||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/USDT',
|
'pair': 'ETH/USDT',
|
||||||
@ -3221,7 +3221,7 @@ def test_execute_trade_exit_custom_exit_price(
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
exit_check=ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)
|
exit_check=ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sell price must be different to default bid price
|
# Sell price must be different to default bid price
|
||||||
@ -3232,7 +3232,7 @@ def test_execute_trade_exit_custom_exit_price(
|
|||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
assert {
|
assert {
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/USDT',
|
'pair': 'ETH/USDT',
|
||||||
'direction': 'Short' if trade.is_short else 'Long',
|
'direction': 'Short' if trade.is_short else 'Long',
|
||||||
@ -3249,8 +3249,8 @@ def test_execute_trade_exit_custom_exit_price(
|
|||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': ExitType.SELL_SIGNAL.value,
|
'sell_reason': ExitType.EXIT_SIGNAL.value,
|
||||||
'exit_reason': ExitType.SELL_SIGNAL.value,
|
'exit_reason': ExitType.EXIT_SIGNAL.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -3299,7 +3299,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
|||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/USDT',
|
'pair': 'ETH/USDT',
|
||||||
@ -3487,15 +3487,9 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
|||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
|
assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||||
assert rpc_mock.call_count == 3
|
assert rpc_mock.call_count == 3
|
||||||
if is_short:
|
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.ENTRY
|
||||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT
|
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.ENTRY_FILL
|
||||||
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.SHORT_FILL
|
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.EXIT
|
||||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
|
||||||
|
|
||||||
else:
|
|
||||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
|
|
||||||
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
|
|
||||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -3563,7 +3557,7 @@ def test_execute_trade_exit_market_order(
|
|||||||
assert rpc_mock.call_count == 3
|
assert rpc_mock.call_count == 3
|
||||||
last_msg = rpc_mock.call_args_list[-2][0][0]
|
last_msg = rpc_mock.call_args_list[-2][0][0]
|
||||||
assert {
|
assert {
|
||||||
'type': RPCMessageType.SELL,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'ETH/USDT',
|
'pair': 'ETH/USDT',
|
||||||
@ -3630,18 +3624,18 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
|
|||||||
|
|
||||||
@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,exit_type,is_short', [
|
@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,exit_type,is_short', [
|
||||||
# Enable profit
|
# Enable profit
|
||||||
(True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, False),
|
(True, 2.18, 2.2, False, True, ExitType.EXIT_SIGNAL.value, False),
|
||||||
(True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, True),
|
(True, 2.18, 2.2, False, True, ExitType.EXIT_SIGNAL.value, True),
|
||||||
# # Disable profit
|
# # Disable profit
|
||||||
(False, 3.19, 3.2, True, False, ExitType.SELL_SIGNAL.value, False),
|
(False, 3.19, 3.2, True, False, ExitType.EXIT_SIGNAL.value, False),
|
||||||
(False, 3.19, 3.2, True, False, ExitType.SELL_SIGNAL.value, True),
|
(False, 3.19, 3.2, True, False, ExitType.EXIT_SIGNAL.value, True),
|
||||||
# # Enable loss
|
# # Enable loss
|
||||||
# # * Shouldn't this be ExitType.STOP_LOSS.value
|
# # * Shouldn't this be ExitType.STOP_LOSS.value
|
||||||
(True, 0.21, 0.22, False, False, None, False),
|
(True, 0.21, 0.22, False, False, None, False),
|
||||||
(True, 2.41, 2.42, False, False, None, True),
|
(True, 2.41, 2.42, False, False, None, True),
|
||||||
# Disable loss
|
# Disable loss
|
||||||
(False, 0.10, 0.22, True, False, ExitType.SELL_SIGNAL.value, False),
|
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, False),
|
||||||
(False, 0.10, 0.22, True, False, ExitType.SELL_SIGNAL.value, True),
|
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, True),
|
||||||
])
|
])
|
||||||
def test_sell_profit_only(
|
def test_sell_profit_only(
|
||||||
default_conf_usdt, limit_order, limit_order_open, is_short,
|
default_conf_usdt, limit_order, limit_order_open, is_short,
|
||||||
@ -3669,7 +3663,7 @@ def test_sell_profit_only(
|
|||||||
})
|
})
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
if exit_type == ExitType.SELL_SIGNAL.value:
|
if exit_type == ExitType.EXIT_SIGNAL.value:
|
||||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
||||||
else:
|
else:
|
||||||
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple(
|
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple(
|
||||||
|
@ -53,7 +53,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
# Sell 3rd trade (not called for the first trade)
|
# Sell 3rd trade (not called for the first trade)
|
||||||
should_sell_mock = MagicMock(side_effect=[
|
should_sell_mock = MagicMock(side_effect=[
|
||||||
ExitCheckTuple(exit_type=ExitType.NONE),
|
ExitCheckTuple(exit_type=ExitType.NONE),
|
||||||
ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)]
|
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]
|
||||||
)
|
)
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||||
@ -123,7 +123,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
assert trade.is_open
|
assert trade.is_open
|
||||||
|
|
||||||
trade = trades[2]
|
trade = trades[2]
|
||||||
assert trade.exit_reason == ExitType.SELL_SIGNAL.value
|
assert trade.exit_reason == ExitType.EXIT_SIGNAL.value
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
|
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
|
|||||||
)
|
)
|
||||||
should_sell_mock = MagicMock(side_effect=[
|
should_sell_mock = MagicMock(side_effect=[
|
||||||
ExitCheckTuple(exit_type=ExitType.NONE),
|
ExitCheckTuple(exit_type=ExitType.NONE),
|
||||||
ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL),
|
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL),
|
||||||
ExitCheckTuple(exit_type=ExitType.NONE),
|
ExitCheckTuple(exit_type=ExitType.NONE),
|
||||||
ExitCheckTuple(exit_type=ExitType.NONE),
|
ExitCheckTuple(exit_type=ExitType.NONE),
|
||||||
ExitCheckTuple(exit_type=ExitType.NONE)]
|
ExitCheckTuple(exit_type=ExitType.NONE)]
|
||||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user