Merge pull request #6648 from freqtrade/exit_type_rename
Exit type rename
This commit is contained in:
		| @@ -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
											
										
									
								
							
		Reference in New Issue
	
	Block a user