Merge pull request #6648 from freqtrade/exit_type_rename
Exit type rename
This commit is contained in:
		| @@ -139,21 +139,21 @@ | ||||
|             "status": "on", | ||||
|             "warning": "on", | ||||
|             "startup": "on", | ||||
|             "buy": "on", | ||||
|             "buy_fill": "on", | ||||
|             "sell": { | ||||
|             "entry": "on", | ||||
|             "entry_fill": "on", | ||||
|             "exit": { | ||||
|                 "roi": "off", | ||||
|                 "emergency_sell": "off", | ||||
|                 "force_sell": "off", | ||||
|                 "sell_signal": "off", | ||||
|                 "emergency_exit": "off", | ||||
|                 "force_exit": "off", | ||||
|                 "exit_signal": "off", | ||||
|                 "trailing_stop_loss": "off", | ||||
|                 "stop_loss": "off", | ||||
|                 "stoploss_on_exchange": "off", | ||||
|                 "custom_sell": "off" | ||||
|                 "custom_exit": "off" | ||||
|             }, | ||||
|             "sell_fill": "on", | ||||
|             "buy_cancel": "on", | ||||
|             "sell_cancel": "on", | ||||
|             "exit_fill": "on", | ||||
|             "entry_cancel": "on", | ||||
|             "exit_cancel": "on", | ||||
|             "protection_trigger": "off", | ||||
|             "protection_trigger_global": "on" | ||||
|         }, | ||||
|   | ||||
| @@ -279,8 +279,8 @@ A backtesting result will look like that: | ||||
| |:-------------------|--------:|------:|-------:|--------:| | ||||
| | trailing_stop_loss |     205 |   150 |      0 |      55 | | ||||
| | stop_loss          |     166 |     0 |      0 |     166 | | ||||
| | sell_signal        |      56 |    36 |      0 |      20 | | ||||
| | force_sell         |       2 |     0 |      0 |       2 | | ||||
| | exit_signal        |      56 |    36 |      0 |      20 | | ||||
| | force_exit         |       2 |     0 |      0 |       2 | | ||||
| ====================================================== LEFT OPEN TRADES REPORT ====================================================== | ||||
| | 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 | ||||
|  | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| | `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.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.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.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.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.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.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.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.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 | ||||
| | `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 | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
| ### 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. | ||||
| 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_profit=0.0496, | ||||
|   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; | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -393,7 +393,7 @@ class AwesomeStrategy(IStrategy): | ||||
| !!! 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. | ||||
|     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 | ||||
|  | ||||
| @@ -564,13 +564,13 @@ class AwesomeStrategy(IStrategy): | ||||
|         :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). | ||||
|         :param exit_reason: Exit reason. | ||||
|             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 **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 | ||||
|         """ | ||||
|         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 | ||||
|             # This is just a sample, please adjust to your needs | ||||
|             # (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_types` 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 | ||||
|  | ||||
|   | ||||
| @@ -81,21 +81,21 @@ Example configuration showing the different settings: | ||||
|         "status": "silent", | ||||
|         "warning": "on", | ||||
|         "startup": "off", | ||||
|         "buy": "silent", | ||||
|         "sell": { | ||||
|         "entry": "silent", | ||||
|         "exit": { | ||||
|             "roi": "silent", | ||||
|             "emergency_sell": "on", | ||||
|             "force_sell": "on", | ||||
|             "sell_signal": "silent", | ||||
|             "emergency_exit": "on", | ||||
|             "force_exit": "on", | ||||
|             "exit_signal": "silent", | ||||
|             "trailing_stop_loss": "on", | ||||
|             "stop_loss": "on", | ||||
|             "stoploss_on_exchange": "on", | ||||
|             "custom_sell": "silent" | ||||
|             "custom_exit": "silent" | ||||
|         }, | ||||
|         "buy_cancel": "silent", | ||||
|         "sell_cancel": "on", | ||||
|         "buy_fill": "off", | ||||
|         "sell_fill": "off", | ||||
|         "entry_cancel": "silent", | ||||
|         "exit_cancel": "on", | ||||
|         "entry_fill": "off", | ||||
|         "exit_fill": "off", | ||||
|         "protection_trigger": "off", | ||||
|         "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. | ||||
| `sell` notifications are sent when the order is placed, while `sell_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. | ||||
| `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. | ||||
| `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": { | ||||
|         "enabled": true, | ||||
|         "url": "https://maker.ifttt.com/trigger/<YOUREVENT>/with/key/<YOURKEY>/", | ||||
|         "webhookbuy": { | ||||
|         "webhookentry": { | ||||
|             "value1": "Buying {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "{stake_amount:8f} {stake_currency}" | ||||
|         }, | ||||
|         "webhookbuycancel": { | ||||
|         "webhookentrycancel": { | ||||
|             "value1": "Cancelling Open Buy Order for {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "{stake_amount:8f} {stake_currency}" | ||||
|         }, | ||||
|          "webhookbuyfill": { | ||||
|          "webhookentryfill": { | ||||
|             "value1": "Buy Order for {pair} filled", | ||||
|             "value2": "at {open_rate:8f}", | ||||
|             "value3": "" | ||||
|         }, | ||||
|         "webhooksell": { | ||||
|             "value1": "Selling {pair}", | ||||
|         "webhookexit": { | ||||
|             "value1": "Exiting {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" | ||||
|         }, | ||||
|         "webhooksellcancel": { | ||||
|             "value1": "Cancelling Open Sell Order for {pair}", | ||||
|         "webhookexitcancel": { | ||||
|             "value1": "Cancelling Open Exit Order for {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" | ||||
|         }, | ||||
|         "webhooksellfill": { | ||||
|             "value1": "Sell Order for {pair} filled", | ||||
|         "webhookexitfill": { | ||||
|             "value1": "Exit Order for {pair} filled", | ||||
|             "value2": "at {close_rate:8f}.", | ||||
|             "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. | ||||
|  | ||||
| ### 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: | ||||
|  | ||||
| * `trade_id` | ||||
| @@ -118,9 +118,9 @@ Possible parameters are: | ||||
| * `current_rate` | ||||
| * `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: | ||||
|  | ||||
| * `trade_id` | ||||
| @@ -139,9 +139,9 @@ Possible parameters are: | ||||
| * `current_rate` | ||||
| * `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: | ||||
|  | ||||
| * `trade_id` | ||||
| @@ -160,8 +160,9 @@ Possible parameters are: | ||||
| * `current_rate` | ||||
| * `enter_tag` | ||||
|  | ||||
| ### Webhooksell | ||||
| The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. | ||||
| ### Webhookexit | ||||
|  | ||||
| The fields in `webhook.webhookexit` are filled when the bot exits a trade. Parameters are filled using string.format. | ||||
| Possible parameters are: | ||||
|  | ||||
| * `trade_id` | ||||
| @@ -183,9 +184,9 @@ Possible parameters are: | ||||
| * `open_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: | ||||
|  | ||||
| * `trade_id` | ||||
| @@ -208,9 +209,9 @@ Possible parameters are: | ||||
| * `open_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: | ||||
|  | ||||
| * `trade_id` | ||||
|   | ||||
| @@ -82,6 +82,31 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: | ||||
|                                None, 'ignore_roi_if_buy_signal') | ||||
|     process_deprecated_setting(config, 'ask_strategy', '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 ... | ||||
|     process_removed_setting(config, 'experimental', 'use_sell_signal', | ||||
|   | ||||
| @@ -285,21 +285,21 @@ CONF_SCHEMA = { | ||||
|                         'status': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'buy': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'buy_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'buy_fill': {'type': 'string', | ||||
|                                      'enum': TELEGRAM_SETTING_OPTIONS, | ||||
|                                      'default': 'off' | ||||
|                                      }, | ||||
|                         'sell': { | ||||
|                         'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'entry_fill': {'type': 'string', | ||||
|                                        'enum': TELEGRAM_SETTING_OPTIONS, | ||||
|                                        'default': 'off' | ||||
|                                        }, | ||||
|                         'exit': { | ||||
|                             'type': ['string', 'object'], | ||||
|                             'additionalProperties': { | ||||
|                                 'type': 'string', | ||||
|                                 'enum': TELEGRAM_SETTING_OPTIONS | ||||
|                             } | ||||
|                         }, | ||||
|                         'sell_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'sell_fill': { | ||||
|                         'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, | ||||
|                         'exit_fill': { | ||||
|                             'type': 'string', | ||||
|                             'enum': TELEGRAM_SETTING_OPTIONS, | ||||
|                             'default': 'off' | ||||
| @@ -327,12 +327,12 @@ CONF_SCHEMA = { | ||||
|                 'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'}, | ||||
|                 'retries': {'type': 'integer', 'minimum': 0}, | ||||
|                 'retry_delay': {'type': 'number', 'minimum': 0}, | ||||
|                 'webhookbuy': {'type': 'object'}, | ||||
|                 'webhookbuycancel': {'type': 'object'}, | ||||
|                 'webhookbuyfill': {'type': 'object'}, | ||||
|                 'webhooksell': {'type': 'object'}, | ||||
|                 'webhooksellcancel': {'type': 'object'}, | ||||
|                 'webhooksellfill': {'type': 'object'}, | ||||
|                 'webhookentry': {'type': 'object'}, | ||||
|                 'webhookentrycancel': {'type': 'object'}, | ||||
|                 'webhookentryfill': {'type': 'object'}, | ||||
|                 'webhookexit': {'type': 'object'}, | ||||
|                 'webhookexitcancel': {'type': 'object'}, | ||||
|                 'webhookexitfill': {'type': 'object'}, | ||||
|                 'webhookstatus': {'type': 'object'}, | ||||
|             }, | ||||
|         }, | ||||
| @@ -478,7 +478,7 @@ CANCEL_REASON = { | ||||
|     "FULLY_CANCELLED": "fully cancelled", | ||||
|     "ALL_CANCELLED": "cancelled (all unfilled and partially filled open orders cancelled)", | ||||
|     "CANCELLED_ON_EXCHANGE": "cancelled on exchange", | ||||
|     "FORCE_SELL": "forcesold", | ||||
|     "FORCE_EXIT": "forcesold", | ||||
| } | ||||
|  | ||||
| # List of pairs with their timeframes | ||||
|   | ||||
| @@ -470,7 +470,7 @@ class Edge: | ||||
|                 if len(ohlc_columns) - 1 < exit_index: | ||||
|                     break | ||||
|  | ||||
|                 exit_type = ExitType.SELL_SIGNAL | ||||
|                 exit_type = ExitType.EXIT_SIGNAL | ||||
|                 exit_price = ohlc_columns[exit_index, 0] | ||||
|  | ||||
|             trade = {'pair': pair, | ||||
|   | ||||
| @@ -9,10 +9,10 @@ class ExitType(Enum): | ||||
|     STOP_LOSS = "stop_loss" | ||||
|     STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange" | ||||
|     TRAILING_STOP_LOSS = "trailing_stop_loss" | ||||
|     SELL_SIGNAL = "sell_signal" | ||||
|     FORCE_SELL = "force_sell" | ||||
|     EMERGENCY_SELL = "emergency_sell" | ||||
|     CUSTOM_SELL = "custom_sell" | ||||
|     EXIT_SIGNAL = "exit_signal" | ||||
|     FORCE_EXIT = "force_exit" | ||||
|     EMERGENCY_EXIT = "emergency_exit" | ||||
|     CUSTOM_EXIT = "custom_exit" | ||||
|     NONE = "" | ||||
|  | ||||
|     def __str__(self): | ||||
|   | ||||
| @@ -6,19 +6,13 @@ class RPCMessageType(Enum): | ||||
|     WARNING = 'warning' | ||||
|     STARTUP = 'startup' | ||||
|  | ||||
|     BUY = 'buy' | ||||
|     BUY_FILL = 'buy_fill' | ||||
|     BUY_CANCEL = 'buy_cancel' | ||||
|     ENTRY = 'entry' | ||||
|     ENTRY_FILL = 'entry_fill' | ||||
|     ENTRY_CANCEL = 'entry_cancel' | ||||
|  | ||||
|     SHORT = 'short' | ||||
|     SHORT_FILL = 'short_fill' | ||||
|     SHORT_CANCEL = 'short_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' | ||||
|     EXIT = 'exit' | ||||
|     EXIT_FILL = 'exit_fill' | ||||
|     EXIT_CANCEL = 'exit_cancel' | ||||
|  | ||||
|     PROTECTION_TRIGGER = 'protection_trigger' | ||||
|     PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global' | ||||
|   | ||||
| @@ -819,10 +819,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         """ | ||||
|         Sends rpc notification when a entry order occurred. | ||||
|         """ | ||||
|         if fill: | ||||
|             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 | ||||
|         msg_type = RPCMessageType.ENTRY_FILL if fill else RPCMessageType.ENTRY | ||||
|         open_rate = safe_value_fallback(order, 'average', 'price') | ||||
|         if open_rate is None: | ||||
|             open_rate = trade.open_rate | ||||
| @@ -861,10 +858,10 @@ class FreqtradeBot(LoggingMixin): | ||||
|         """ | ||||
|         current_rate = self.exchange.get_rate( | ||||
|             trade.pair, side='entry', is_short=trade.is_short, refresh=False) | ||||
|         msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL | ||||
|  | ||||
|         msg = { | ||||
|             'trade_id': trade.id, | ||||
|             'type': msg_type, | ||||
|             'type': RPCMessageType.ENTRY_CANCEL, | ||||
|             'buy_tag': trade.enter_tag, | ||||
|             'enter_tag': trade.enter_tag, | ||||
|             '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.warning('Exiting the trade forcefully') | ||||
|             self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple( | ||||
|                 exit_type=ExitType.EMERGENCY_SELL)) | ||||
|                 exit_type=ExitType.EMERGENCY_EXIT)) | ||||
|  | ||||
|         except ExchangeError: | ||||
|             trade.stoploss_order_id = None | ||||
| @@ -1162,7 +1159,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|                         try: | ||||
|                             self.execute_trade_exit( | ||||
|                                 trade, order.get('price'), | ||||
|                                 exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_SELL)) | ||||
|                                 exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT)) | ||||
|                         except DependencyException as exception: | ||||
|                             logger.warning( | ||||
|                                 f'Unable to emergency sell trade {trade.pair}: {exception}') | ||||
| @@ -1380,7 +1377,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         trade = self.cancel_stoploss_on_exchange(trade) | ||||
|  | ||||
|         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!) | ||||
|             order_type = self.strategy.order_types.get("emergencyexit", "market") | ||||
|  | ||||
| @@ -1446,8 +1443,8 @@ class FreqtradeBot(LoggingMixin): | ||||
|         gain = "profit" if profit_ratio > 0 else "loss" | ||||
|  | ||||
|         msg = { | ||||
|             'type': (RPCMessageType.SELL_FILL if fill | ||||
|                      else RPCMessageType.SELL), | ||||
|             'type': (RPCMessageType.EXIT_FILL if fill | ||||
|                      else RPCMessageType.EXIT), | ||||
|             'trade_id': trade.id, | ||||
|             'exchange': trade.exchange.capitalize(), | ||||
|             'pair': trade.pair, | ||||
| @@ -1497,7 +1494,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         gain = "profit" if profit_ratio > 0 else "loss" | ||||
|  | ||||
|         msg = { | ||||
|             'type': RPCMessageType.SELL_CANCEL, | ||||
|             'type': RPCMessageType.EXIT_CANCEL, | ||||
|             'trade_id': trade.id, | ||||
|             'exchange': trade.exchange.capitalize(), | ||||
|             'pair': trade.pair, | ||||
|   | ||||
| @@ -529,7 +529,7 @@ class Backtesting: | ||||
|             # call the custom exit price,with default value as previous closerate | ||||
|             current_profit = trade.calc_profit_ratio(closerate) | ||||
|             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 | ||||
|                 if order_type == 'limit': | ||||
|                     closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, | ||||
| @@ -812,7 +812,7 @@ class Backtesting: | ||||
|                     sell_row = data[pair][-1] | ||||
|  | ||||
|                     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) | ||||
|                     LocalTrade.close_bt_trade(trade) | ||||
|                     # 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_pct} initial_stop_loss_pct, | ||||
|             {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, | ||||
|             {strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe, | ||||
|             {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, | ||||
|   | ||||
| @@ -697,17 +697,17 @@ class RPC: | ||||
|  | ||||
|                 if order['side'] == trade.enter_side: | ||||
|                     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: | ||||
|                     # 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: | ||||
|                 # Get current rate and execute sell | ||||
|                 current_rate = self._freqtrade.exchange.get_rate( | ||||
|                     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( | ||||
|                     "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`. | ||||
|         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: | ||||
|             msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount( | ||||
|                 msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) | ||||
|         else: | ||||
|             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}' | ||||
|  | ||||
|         enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['type'] | ||||
|                       in [RPCMessageType.BUY_FILL, RPCMessageType.BUY] | ||||
|         enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['direction'] == 'Long' | ||||
|                       else {'enter': 'Short', 'entered': 'Shorted'}) | ||||
|         message = ( | ||||
|             f"{emoji} *{msg['exchange']}:*" | ||||
| @@ -246,9 +245,9 @@ class Telegram(RPCHandler): | ||||
|         if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0: | ||||
|             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" | ||||
|         elif msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]: | ||||
|         elif msg['type'] in [RPCMessageType.ENTRY]: | ||||
|             message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\ | ||||
|                        f"*Current Rate:* `{msg['current_rate']:.8f}`\n" | ||||
|  | ||||
| @@ -260,7 +259,7 @@ class Telegram(RPCHandler): | ||||
|         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['profit_percent'] = round(msg['profit_ratio'] * 100, 2) | ||||
|         msg['duration'] = msg['close_date'].replace( | ||||
| @@ -284,7 +283,7 @@ class Telegram(RPCHandler): | ||||
|                 f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']})") | ||||
|         else: | ||||
|             msg['profit_extra'] = '' | ||||
|         is_fill = msg['type'] == RPCMessageType.SELL_FILL | ||||
|         is_fill = msg['type'] == RPCMessageType.EXIT_FILL | ||||
|         message = ( | ||||
|             f"{msg['emoji']} *{msg['exchange']}:* " | ||||
|             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"*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" | ||||
|                         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}`" | ||||
|  | ||||
|         return message | ||||
|  | ||||
|     def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: | ||||
|         if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL, RPCMessageType.SHORT, | ||||
|                         RPCMessageType.SHORT_FILL]: | ||||
|             message = self._format_buy_msg(msg) | ||||
|         if msg_type in [RPCMessageType.ENTRY, RPCMessageType.ENTRY_FILL]: | ||||
|             message = self._format_entry_msg(msg) | ||||
|  | ||||
|         elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]: | ||||
|             message = self._format_sell_msg(msg) | ||||
|         elif msg_type in [RPCMessageType.EXIT, RPCMessageType.EXIT_FILL]: | ||||
|             message = self._format_exit_msg(msg) | ||||
|  | ||||
|         elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL, | ||||
|                           RPCMessageType.SELL_CANCEL): | ||||
|             msg['message_side'] = 'enter' if msg_type in [RPCMessageType.BUY_CANCEL, | ||||
|                                                           RPCMessageType.SHORT_CANCEL] else 'exit' | ||||
|         elif msg_type in (RPCMessageType.ENTRY_CANCEL, RPCMessageType.EXIT_CANCEL): | ||||
|             msg['message_side'] = 'enter' if msg_type in [RPCMessageType.ENTRY_CANCEL] else 'exit' | ||||
|             message = ("\N{WARNING SIGN} *{exchange}:* " | ||||
|                        "Cancelling {message_side} Order for {pair} (#{trade_id}). " | ||||
|                        "Reason: {reason}.".format(**msg)) | ||||
| @@ -355,7 +351,7 @@ class Telegram(RPCHandler): | ||||
|  | ||||
|         msg_type = msg['type'] | ||||
|         noti = '' | ||||
|         if msg_type == RPCMessageType.SELL: | ||||
|         if msg_type == RPCMessageType.EXIT: | ||||
|             sell_noti = self._config['telegram'] \ | ||||
|                 .get('notification_settings', {}).get(str(msg_type), {}) | ||||
|             # For backward compatibility sell still can be string | ||||
| @@ -768,9 +764,9 @@ class Telegram(RPCHandler): | ||||
|             'stop_loss': 'Stoploss', | ||||
|             'trailing_stop_loss': 'Trail. Stop', | ||||
|             'stoploss_on_exchange': 'Stoploss', | ||||
|             'sell_signal': 'Sell Signal', | ||||
|             'force_sell': 'Forcesell', | ||||
|             'emergency_sell': 'Emergency Sell', | ||||
|             'exit_signal': 'Exit Signal', | ||||
|             'force_exit': 'Force Exit', | ||||
|             'emergency_exit': 'Emergency Exit', | ||||
|         } | ||||
|         exit_reasons_tabulate = [ | ||||
|             [ | ||||
|   | ||||
| @@ -43,23 +43,23 @@ class Webhook(RPCHandler): | ||||
|     def send_msg(self, msg: Dict[str, Any]) -> None: | ||||
|         """ Send a message to telegram channel """ | ||||
|         try: | ||||
|  | ||||
|             if msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]: | ||||
|                 valuedict = self._config['webhook'].get('webhookbuy', None) | ||||
|             elif msg['type'] in [RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL]: | ||||
|                 valuedict = self._config['webhook'].get('webhookbuycancel', None) | ||||
|             elif msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]: | ||||
|                 valuedict = self._config['webhook'].get('webhookbuyfill', None) | ||||
|             elif msg['type'] == RPCMessageType.SELL: | ||||
|                 valuedict = self._config['webhook'].get('webhooksell', None) | ||||
|             elif msg['type'] == RPCMessageType.SELL_FILL: | ||||
|                 valuedict = self._config['webhook'].get('webhooksellfill', None) | ||||
|             elif msg['type'] == RPCMessageType.SELL_CANCEL: | ||||
|                 valuedict = self._config['webhook'].get('webhooksellcancel', None) | ||||
|             whconfig = self._config['webhook'] | ||||
|             if msg['type'] in [RPCMessageType.ENTRY]: | ||||
|                 valuedict = whconfig.get('webhookentry', None) | ||||
|             elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]: | ||||
|                 valuedict = whconfig.get('webhookentrycancel', None) | ||||
|             elif msg['type'] in [RPCMessageType.ENTRY_FILL]: | ||||
|                 valuedict = whconfig.get('webhookentryfill', None) | ||||
|             elif msg['type'] == RPCMessageType.EXIT: | ||||
|                 valuedict = whconfig.get('webhookexit', None) | ||||
|             elif msg['type'] == RPCMessageType.EXIT_FILL: | ||||
|                 valuedict = whconfig.get('webhookexitfill', None) | ||||
|             elif msg['type'] == RPCMessageType.EXIT_CANCEL: | ||||
|                 valuedict = whconfig.get('webhookexitcancel', None) | ||||
|             elif msg['type'] in (RPCMessageType.STATUS, | ||||
|                                  RPCMessageType.STARTUP, | ||||
|                                  RPCMessageType.WARNING): | ||||
|                 valuedict = self._config['webhook'].get('webhookstatus', None) | ||||
|                 valuedict = whconfig.get('webhookstatus', None) | ||||
|             else: | ||||
|                 raise NotImplementedError('Unknown message type: {}'.format(msg['type'])) | ||||
|             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 exit_reason: Exit reason. | ||||
|             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 **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 | ||||
|         """ | ||||
|         return True | ||||
| @@ -888,14 +888,14 @@ class IStrategy(ABC, HyperStrategyMixin): | ||||
|             pass | ||||
|         elif self.use_sell_signal and not enter: | ||||
|             if exit_: | ||||
|                 exit_signal = ExitType.SELL_SIGNAL | ||||
|                 exit_signal = ExitType.EXIT_SIGNAL | ||||
|             else: | ||||
|                 trade_type = "exit_short" if trade.is_short else "sell" | ||||
|                 custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)( | ||||
|                     pair=trade.pair, trade=trade, current_time=current_time, | ||||
|                     current_rate=current_rate, current_profit=current_profit) | ||||
|                 if custom_reason: | ||||
|                     exit_signal = ExitType.CUSTOM_SELL | ||||
|                     exit_signal = ExitType.CUSTOM_EXIT | ||||
|                     if isinstance(custom_reason, str): | ||||
|                         if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH: | ||||
|                             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] | ||||
|                     else: | ||||
|                         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. " | ||||
|                              f"exit_type=ExitType.{exit_signal.name}" + | ||||
|                              (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 exit_reason: Exit reason. | ||||
|         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 **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 | ||||
|     """ | ||||
|     return True | ||||
| @@ -206,7 +206,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) - | ||||
|     :param trade: trade object. | ||||
|     :param order: Order dictionary as returned from CCXT. | ||||
|     :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 | ||||
|  | ||||
|   | ||||
| @@ -95,8 +95,8 @@ tc1 = BTContainer(data=[ | ||||
|     [6, 5000, 5025, 4975, 4987, 6172, 0, 0],  # should sell | ||||
| ], | ||||
|     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), | ||||
|             BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)] | ||||
|     trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=2), | ||||
|             BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=6)] | ||||
| ) | ||||
|  | ||||
| # 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': '', | ||||
|          'open_rate': 17, | ||||
|          'close_rate': 17, | ||||
|          'exit_type': 'sell_signal'}, | ||||
|          'exit_type': 'exit_signal'}, | ||||
|  | ||||
|         {'pair': 'TEST/BTC', | ||||
|          'stoploss': -0.9, | ||||
| @@ -402,7 +402,7 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc | ||||
|          'trade_duration': '', | ||||
|          'open_rate': 20, | ||||
|          'close_rate': 20, | ||||
|          'exit_type': 'sell_signal'}, | ||||
|          'exit_type': 'exit_signal'}, | ||||
|  | ||||
|         {'pair': 'TEST/BTC', | ||||
|          'stoploss': -0.9, | ||||
| @@ -413,7 +413,7 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc | ||||
|          'trade_duration': '', | ||||
|          'open_rate': 26, | ||||
|          'close_rate': 34, | ||||
|          'exit_type': 'sell_signal'} | ||||
|          'exit_type': 'exit_signal'} | ||||
|     ] | ||||
|  | ||||
|     trades_df = DataFrame(trades) | ||||
|   | ||||
| @@ -23,7 +23,7 @@ tc0 = BTContainer(data=[ | ||||
|     [4, 5010, 5011, 4977, 4995, 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, | ||||
|     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 | ||||
| @@ -424,7 +424,7 @@ tc26 = BTContainer(data=[ | ||||
|     [4, 5010, 5010, 4855, 4995, 6172, 0, 0],  # Triggers stoploss + sellsignal acted on | ||||
|     [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], | ||||
|     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) | ||||
| @@ -441,7 +441,7 @@ tc27 = BTContainer(data=[ | ||||
|     [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, | ||||
|     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) | ||||
| @@ -458,7 +458,7 @@ tc28 = BTContainer(data=[ | ||||
|     [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, | ||||
|     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) | ||||
| # 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 | ||||
|     [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], | ||||
|     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 | ||||
| @@ -708,7 +708,7 @@ tc44 = BTContainer(data=[ | ||||
|     stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, | ||||
|     use_exit_signal=True, | ||||
|     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 | ||||
| @@ -723,7 +723,7 @@ tc45 = BTContainer(data=[ | ||||
|     stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, | ||||
|     use_exit_signal=True, | ||||
|     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 | ||||
| @@ -738,7 +738,7 @@ tc46 = BTContainer(data=[ | ||||
|     stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, | ||||
|     use_exit_signal=True, | ||||
|     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 | ||||
|   | ||||
| @@ -358,7 +358,7 @@ def test_hyperopt_format_results(hyperopt): | ||||
|                                  "is_short": [False, False, False, False], | ||||
|                                  "stake_amount": [0.01, 0.01, 0.01, 0.01], | ||||
|                                  "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS, | ||||
|                                                  ExitType.ROI, ExitType.FORCE_SELL] | ||||
|                                                  ExitType.ROI, ExitType.FORCE_EXIT] | ||||
|                                  }), | ||||
|         'config': hyperopt.config, | ||||
|         'locks': [], | ||||
| @@ -429,7 +429,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: | ||||
|                                  "is_short": [False, False, False, False], | ||||
|                                  "stake_amount": [0.01, 0.01, 0.01, 0.01], | ||||
|                                  "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS, | ||||
|                                                  ExitType.ROI, ExitType.FORCE_SELL] | ||||
|                                                  ExitType.ROI, ExitType.FORCE_EXIT] | ||||
|                                  }), | ||||
|         'config': hyperopt_conf, | ||||
|         'locks': [], | ||||
|   | ||||
| @@ -77,7 +77,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): | ||||
|                                  "is_short": [False, False, False, False], | ||||
|                                  "stake_amount": [0.01, 0.01, 0.01, 0.01], | ||||
|                                  "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS, | ||||
|                                                  ExitType.ROI, ExitType.FORCE_SELL] | ||||
|                                                  ExitType.ROI, ExitType.FORCE_EXIT] | ||||
|                                  }), | ||||
|         'config': default_conf, | ||||
|         'locks': [], | ||||
| @@ -129,7 +129,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): | ||||
|              "is_short": [False, False, False, False], | ||||
|              "stake_amount": [0.01, 0.01, 0.01, 0.01], | ||||
|              "exit_reason": [ExitType.ROI, ExitType.ROI, | ||||
|                              ExitType.STOP_LOSS, ExitType.FORCE_SELL] | ||||
|                              ExitType.STOP_LOSS, ExitType.FORCE_EXIT] | ||||
|              }), | ||||
|         'config': default_conf, | ||||
|         '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, | ||||
|                         sell_reason: str = ExitType.SELL_SIGNAL, | ||||
|                         sell_reason: str = ExitType.EXIT_SIGNAL, | ||||
|                         min_ago_open: int = None, min_ago_close: int = None, | ||||
|                         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 | ||||
|     last_msg = msg_mock.call_args_list[-2][0][0] | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
| @@ -1059,8 +1059,8 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, | ||||
|         'fiat_currency': 'USD', | ||||
|         'buy_tag': ANY, | ||||
|         'enter_tag': ANY, | ||||
|         'sell_reason': ExitType.FORCE_SELL.value, | ||||
|         'exit_reason': ExitType.FORCE_SELL.value, | ||||
|         'sell_reason': ExitType.FORCE_EXIT.value, | ||||
|         'exit_reason': ExitType.FORCE_EXIT.value, | ||||
|         'open_date': ANY, | ||||
|         'close_date': 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] | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
| @@ -1128,8 +1128,8 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, | ||||
|         'fiat_currency': 'USD', | ||||
|         'buy_tag': ANY, | ||||
|         'enter_tag': ANY, | ||||
|         'sell_reason': ExitType.FORCE_SELL.value, | ||||
|         'exit_reason': ExitType.FORCE_SELL.value, | ||||
|         'sell_reason': ExitType.FORCE_EXIT.value, | ||||
|         'exit_reason': ExitType.FORCE_EXIT.value, | ||||
|         'open_date': ANY, | ||||
|         'close_date': 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 | ||||
|     msg = msg_mock.call_args_list[0][0][0] | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
| @@ -1187,8 +1187,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None | ||||
|         'fiat_currency': 'USD', | ||||
|         'buy_tag': ANY, | ||||
|         'enter_tag': ANY, | ||||
|         'sell_reason': ExitType.FORCE_SELL.value, | ||||
|         'exit_reason': ExitType.FORCE_SELL.value, | ||||
|         'sell_reason': ExitType.FORCE_EXIT.value, | ||||
|         'exit_reason': ExitType.FORCE_EXIT.value, | ||||
|         'open_date': ANY, | ||||
|         'close_date': 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', [ | ||||
|     (RPCMessageType.BUY, 'Long', 'long_signal_01', None), | ||||
|     (RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0), | ||||
|     (RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0), | ||||
|     (RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)]) | ||||
|     (RPCMessageType.ENTRY, 'Long', 'long_signal_01', None), | ||||
|     (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 1.0), | ||||
|     (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 5.0), | ||||
|     (RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)]) | ||||
| def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type, | ||||
|                                    enter, enter_signal, leverage) -> None: | ||||
|  | ||||
| @@ -1787,6 +1787,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type, | ||||
|         'leverage': leverage, | ||||
|         'limit': 1.099e-05, | ||||
|         'order_type': 'limit', | ||||
|         'direction': enter, | ||||
|         'stake_amount': 0.01465333, | ||||
|         'stake_amount_fiat': 0.0, | ||||
|         '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', [ | ||||
|     (RPCMessageType.BUY_CANCEL, 'long_signal_01'), | ||||
|     (RPCMessageType.SHORT_CANCEL, 'short_signal_01')]) | ||||
|     (RPCMessageType.ENTRY_CANCEL, 'long_signal_01'), | ||||
|     (RPCMessageType.ENTRY_CANCEL, 'short_signal_01')]) | ||||
| def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None: | ||||
|  | ||||
|     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', [ | ||||
|     (RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0), | ||||
|     (RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0), | ||||
|     (RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0), | ||||
|     (RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_01', 1.0), | ||||
|     (RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_02', 2.0), | ||||
|     (RPCMessageType.ENTRY_FILL, 'Short', 'short_signal_01', 2.0), | ||||
| ]) | ||||
| def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered, | ||||
|                                         enter_signal, leverage) -> None: | ||||
| def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, entered, | ||||
|                                           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.send_msg({ | ||||
| @@ -1893,6 +1894,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, ente | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': leverage, | ||||
|         'stake_amount': 0.01465333, | ||||
|         'direction': entered, | ||||
|         # 'stake_amount_fiat': 0.0, | ||||
|         'stake_currency': 'BTC', | ||||
|         '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 '' | ||||
|     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' | ||||
|         '*Amount:* `1333.33333333`\n' | ||||
|         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 | ||||
|     telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812 | ||||
|     telegram.send_msg({ | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'KEY/ETH', | ||||
| @@ -1954,7 +1956,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: | ||||
|  | ||||
|     msg_mock.reset_mock() | ||||
|     telegram.send_msg({ | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         '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 | ||||
|     telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812 | ||||
|     telegram.send_msg({ | ||||
|         'type': RPCMessageType.SELL_CANCEL, | ||||
|         'type': RPCMessageType.EXIT_CANCEL, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'KEY/ETH', | ||||
| @@ -2008,7 +2010,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: | ||||
|  | ||||
|     msg_mock.reset_mock() | ||||
|     telegram.send_msg({ | ||||
|         'type': RPCMessageType.SELL_CANCEL, | ||||
|         'type': RPCMessageType.EXIT_CANCEL, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         '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, | ||||
|                                          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.send_msg({ | ||||
|         'type': RPCMessageType.SELL_FILL, | ||||
|         'type': RPCMessageType.EXIT_FILL, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         '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', [ | ||||
|     (RPCMessageType.BUY, 'Long', 'long_signal_01', None), | ||||
|     (RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0), | ||||
|     (RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)]) | ||||
|     (RPCMessageType.ENTRY, 'Long', 'long_signal_01', None), | ||||
|     (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 2.0), | ||||
|     (RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)]) | ||||
| def test_send_msg_buy_notification_no_fiat( | ||||
|         default_conf, mocker, message_type, enter, enter_signal, leverage) -> None: | ||||
|     del default_conf['fiat_display_currency'] | ||||
| @@ -2122,6 +2124,7 @@ def test_send_msg_buy_notification_no_fiat( | ||||
|         'leverage': leverage, | ||||
|         'limit': 1.099e-05, | ||||
|         'order_type': 'limit', | ||||
|         'direction': enter, | ||||
|         'stake_amount': 0.01465333, | ||||
|         'stake_amount_fiat': 0.0, | ||||
|         '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.send_msg({ | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'KEY/ETH', | ||||
|   | ||||
| @@ -15,38 +15,38 @@ def get_webhook_dict() -> dict: | ||||
|     return { | ||||
|         "enabled": True, | ||||
|         "url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/", | ||||
|         "webhookbuy": { | ||||
|         "webhookentry": { | ||||
|             "value1": "Buying {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "{stake_amount:8f} {stake_currency}", | ||||
|             "value4": "leverage {leverage:.1f}", | ||||
|             "value5": "direction {direction}" | ||||
|         }, | ||||
|         "webhookbuycancel": { | ||||
|         "webhookentrycancel": { | ||||
|             "value1": "Cancelling Open Buy Order for {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "{stake_amount:8f} {stake_currency}", | ||||
|             "value4": "leverage {leverage:.1f}", | ||||
|             "value5": "direction {direction}" | ||||
|         }, | ||||
|         "webhookbuyfill": { | ||||
|         "webhookentryfill": { | ||||
|             "value1": "Buy Order for {pair} filled", | ||||
|             "value2": "at {open_rate:8f}", | ||||
|             "value3": "{stake_amount:8f} {stake_currency}", | ||||
|             "value4": "leverage {leverage:.1f}", | ||||
|             "value5": "direction {direction}" | ||||
|         }, | ||||
|         "webhooksell": { | ||||
|         "webhookexit": { | ||||
|             "value1": "Selling {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" | ||||
|         }, | ||||
|         "webhooksellcancel": { | ||||
|         "webhookexitcancel": { | ||||
|             "value1": "Cancelling Open Sell Order for {pair}", | ||||
|             "value2": "limit {limit:8f}", | ||||
|             "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})" | ||||
|         }, | ||||
|         "webhooksellfill": { | ||||
|         "webhookexitfill": { | ||||
|             "value1": "Sell Order for {pair} filled", | ||||
|             "value2": "at {close_rate:8f}", | ||||
|             "value3": "" | ||||
| @@ -74,7 +74,7 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) | ||||
|     msg = { | ||||
|         'type': RPCMessageType.BUY, | ||||
|         'type': RPCMessageType.ENTRY, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 1.0, | ||||
| @@ -88,20 +88,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuy"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentry"]["value2"].format(**msg)) | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuy"]["value4"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentry"]["value4"].format(**msg)) | ||||
|     assert (msg_mock.call_args[0][0]["value5"] == | ||||
|             default_conf["webhook"]["webhookbuy"]["value5"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentry"]["value5"].format(**msg)) | ||||
|     # Test short | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SHORT, | ||||
|         'type': RPCMessageType.ENTRY, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 2.0, | ||||
| @@ -115,20 +115,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuy"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentry"]["value2"].format(**msg)) | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuy"]["value4"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentry"]["value4"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.BUY_CANCEL, | ||||
|         'type': RPCMessageType.ENTRY_CANCEL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 1.0, | ||||
| @@ -142,16 +142,16 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SHORT_CANCEL, | ||||
|         'type': RPCMessageType.ENTRY_CANCEL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 2.0, | ||||
| @@ -165,20 +165,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg)) | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.BUY_FILL, | ||||
|         'type': RPCMessageType.ENTRY_FILL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 1.0, | ||||
| @@ -192,20 +192,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg)) | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SHORT_FILL, | ||||
|         'type': RPCMessageType.ENTRY_FILL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'leverage': 2.0, | ||||
| @@ -219,20 +219,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg)) | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg)) | ||||
|     assert (msg_mock.call_args[0][0]["value5"] == | ||||
|             default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg)) | ||||
|     # Test sell | ||||
|     msg_mock.reset_mock() | ||||
|  | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': "profit", | ||||
| @@ -249,15 +249,15 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhooksell"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookexit"]["value2"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SELL_CANCEL, | ||||
|         'type': RPCMessageType.EXIT_CANCEL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': "profit", | ||||
| @@ -274,15 +274,15 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookexitcancel"]["value2"].format(**msg)) | ||||
|     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 | ||||
|     msg_mock.reset_mock() | ||||
|     msg = { | ||||
|         'type': RPCMessageType.SELL_FILL, | ||||
|         'type': RPCMessageType.EXIT_FILL, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': "profit", | ||||
| @@ -299,11 +299,11 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|     webhook.send_msg(msg=msg) | ||||
|     assert msg_mock.call_count == 1 | ||||
|     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"] == | ||||
|             default_conf["webhook"]["webhooksellfill"]["value2"].format(**msg)) | ||||
|             default_conf["webhook"]["webhookexitfill"]["value2"].format(**msg)) | ||||
|     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, | ||||
|                     RPCMessageType.WARNING, | ||||
| @@ -327,20 +327,20 @@ def test_send_msg_webhook(default_conf, mocker): | ||||
|  | ||||
| def test_exception_send_msg(default_conf, mocker, caplog): | ||||
|     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.send_msg({'type': RPCMessageType.BUY}) | ||||
|     assert log_has(f"Message type '{RPCMessageType.BUY}' not configured for webhooks", | ||||
|     webhook.send_msg({'type': RPCMessageType.ENTRY}) | ||||
|     assert log_has(f"Message type '{RPCMessageType.ENTRY}' not configured for webhooks", | ||||
|                    caplog) | ||||
|  | ||||
|     default_conf["webhook"] = get_webhook_dict() | ||||
|     default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}" | ||||
|     default_conf["webhook"]["webhookentry"]["value1"] = "{DEADBEEF:8f}" | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) | ||||
|     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf) | ||||
|     msg = { | ||||
|         'type': RPCMessageType.BUY, | ||||
|         'type': RPCMessageType.ENTRY, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/BTC', | ||||
|         'limit': 0.005, | ||||
|   | ||||
| @@ -503,15 +503,15 @@ def test_custom_exit(default_conf, fee, caplog) -> None: | ||||
|                                enter=False, exit_=False, | ||||
|                                low=None, high=None) | ||||
|     assert res.exit_flag is True | ||||
|     assert res.exit_type == ExitType.CUSTOM_SELL | ||||
|     assert res.exit_reason == 'custom_sell' | ||||
|     assert res.exit_type == ExitType.CUSTOM_EXIT | ||||
|     assert res.exit_reason == 'custom_exit' | ||||
|  | ||||
|     strategy.custom_exit = MagicMock(return_value='hello world') | ||||
|  | ||||
|     res = strategy.should_exit(trade, 1, now, | ||||
|                                enter=False, exit_=False, | ||||
|                                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_reason == 'hello world' | ||||
|  | ||||
| @@ -520,7 +520,7 @@ def test_custom_exit(default_conf, fee, caplog) -> None: | ||||
|     res = strategy.should_exit(trade, 1, now, | ||||
|                                enter=False, exit_=False, | ||||
|                                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_reason == 'h' * 64 | ||||
|     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 trade.stoploss_order_id is None | ||||
|     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]) | ||||
| @@ -1293,7 +1293,7 @@ def test_create_stoploss_order_invalid_order( | ||||
|     caplog.clear() | ||||
|     freqtrade.create_stoploss_order(trade, 200) | ||||
|     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("Exiting the trade forcefully", caplog) | ||||
|  | ||||
| @@ -1305,7 +1305,7 @@ def test_create_stoploss_order_invalid_order( | ||||
|  | ||||
|     # Rpc is sending first buy, then sell | ||||
|     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' | ||||
|  | ||||
|  | ||||
| @@ -2310,7 +2310,7 @@ def test_handle_trade_use_sell_signal( | ||||
|     else: | ||||
|         patch_get_signal(freqtrade, enter_long=False, exit_long=True) | ||||
|     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) | ||||
|  | ||||
|  | ||||
| @@ -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] | ||||
|     assert { | ||||
|         'trade_id': 1, | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/USDT', | ||||
|         '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 | ||||
|     last_msg = rpc_mock.call_args_list[-1][0][0] | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/USDT', | ||||
| @@ -3221,7 +3221,7 @@ def test_execute_trade_exit_custom_exit_price( | ||||
|     freqtrade.execute_trade_exit( | ||||
|         trade=trade, | ||||
|         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 | ||||
| @@ -3232,7 +3232,7 @@ def test_execute_trade_exit_custom_exit_price( | ||||
|     last_msg = rpc_mock.call_args_list[-1][0][0] | ||||
|     assert { | ||||
|         'trade_id': 1, | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'exchange': 'Binance', | ||||
|         'pair': 'ETH/USDT', | ||||
|         'direction': 'Short' if trade.is_short else 'Long', | ||||
| @@ -3249,8 +3249,8 @@ def test_execute_trade_exit_custom_exit_price( | ||||
|         'profit_ratio': profit_ratio, | ||||
|         'stake_currency': 'USDT', | ||||
|         'fiat_currency': 'USD', | ||||
|         'sell_reason': ExitType.SELL_SIGNAL.value, | ||||
|         'exit_reason': ExitType.SELL_SIGNAL.value, | ||||
|         'sell_reason': ExitType.EXIT_SIGNAL.value, | ||||
|         'exit_reason': ExitType.EXIT_SIGNAL.value, | ||||
|         'open_date': ANY, | ||||
|         'close_date': 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] | ||||
|  | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         '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.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value | ||||
|     assert rpc_mock.call_count == 3 | ||||
|     if is_short: | ||||
|         assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT | ||||
|         assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.SHORT_FILL | ||||
|         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 | ||||
|     assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.ENTRY | ||||
|     assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.ENTRY_FILL | ||||
|     assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.EXIT | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
| @@ -3563,7 +3557,7 @@ def test_execute_trade_exit_market_order( | ||||
|     assert rpc_mock.call_count == 3 | ||||
|     last_msg = rpc_mock.call_args_list[-2][0][0] | ||||
|     assert { | ||||
|         'type': RPCMessageType.SELL, | ||||
|         'type': RPCMessageType.EXIT, | ||||
|         'trade_id': 1, | ||||
|         'exchange': 'Binance', | ||||
|         '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', [ | ||||
|     # Enable profit | ||||
|     (True, 2.18, 2.2, False, True, ExitType.SELL_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, False), | ||||
|     (True, 2.18, 2.2, False, True, ExitType.EXIT_SIGNAL.value, True), | ||||
|     # # Disable profit | ||||
|     (False, 3.19, 3.2, True,  False, ExitType.SELL_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, False), | ||||
|     (False, 3.19, 3.2, True,  False, ExitType.EXIT_SIGNAL.value, True), | ||||
|     # # Enable loss | ||||
|     # # * Shouldn't this be ExitType.STOP_LOSS.value | ||||
|     (True, 0.21, 0.22, False, False, None, False), | ||||
|     (True, 2.41, 2.42, False, False, None, True), | ||||
|     # Disable loss | ||||
|     (False, 0.10, 0.22, True, False, ExitType.SELL_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, False), | ||||
|     (False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, True), | ||||
| ]) | ||||
| def test_sell_profit_only( | ||||
|         default_conf_usdt, limit_order, limit_order_open, is_short, | ||||
| @@ -3669,7 +3663,7 @@ def test_sell_profit_only( | ||||
|     }) | ||||
|     freqtrade = FreqtradeBot(default_conf_usdt) | ||||
|     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) | ||||
|     else: | ||||
|         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) | ||||
|     should_sell_mock = MagicMock(side_effect=[ | ||||
|         ExitCheckTuple(exit_type=ExitType.NONE), | ||||
|         ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)] | ||||
|         ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)] | ||||
|     ) | ||||
|     cancel_order_mock = MagicMock() | ||||
|     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 | ||||
|  | ||||
|     trade = trades[2] | ||||
|     assert trade.exit_reason == ExitType.SELL_SIGNAL.value | ||||
|     assert trade.exit_reason == ExitType.EXIT_SIGNAL.value | ||||
|     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=[ | ||||
|         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)] | ||||
|   | ||||
										
											
												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