Merge pull request #7549 from froggleston/discord_sendmsg
Add support for dp.send_msg() to webhooks
This commit is contained in:
commit
28f0a35e73
@ -215,16 +215,18 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
|
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
|
||||||
| `telegram.reload` | Allow "reload" buttons on telegram messages. <br>*Defaults to `True`.<br> **Datatype:** boolean
|
| `telegram.reload` | Allow "reload" buttons on telegram messages. <br>*Defaults to `True`.<br> **Datatype:** boolean
|
||||||
| `telegram.notification_settings.*` | Detailed notification settings. Refer to the [telegram documentation](telegram-usage.md) for details.<br> **Datatype:** dictionary
|
| `telegram.notification_settings.*` | Detailed notification settings. Refer to the [telegram documentation](telegram-usage.md) for details.<br> **Datatype:** dictionary
|
||||||
|
| `telegram.allow_custom_messages` | Enable the sending of Telegram messages from strategies via the dataprovider.send_msg() function. <br> **Datatype:** Boolean
|
||||||
| | **Webhook**
|
| | **Webhook**
|
||||||
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
||||||
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.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.entry` | 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.entry_cancel` | 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.entry_fill` | 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.exit` | 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.exit_cancel` | 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.exit_fill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
| `webhook.status` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
|
| `webhook.allow_custom_messages` | Enable the sending of Webhook messages from strategies via the dataprovider.send_msg() function. <br> **Datatype:** Boolean
|
||||||
| | **Rest API / FreqUI / Producer-Consumer**
|
| | **Rest API / FreqUI / Producer-Consumer**
|
||||||
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
||||||
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
||||||
|
@ -66,11 +66,11 @@ We will keep a compatibility layer for 1-2 versions (so both `buy_tag` and `ente
|
|||||||
|
|
||||||
#### Naming changes
|
#### Naming changes
|
||||||
|
|
||||||
Webhook terminology changed from "sell" to "exit", and from "buy" to "entry".
|
Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", removing "webhook" in the process.
|
||||||
|
|
||||||
* `webhookbuy` -> `webhookentry`
|
* `webhookbuy`, `webhookentry` -> `entry`
|
||||||
* `webhookbuyfill` -> `webhookentryfill`
|
* `webhookbuyfill`, `webhookentryfill` -> `entry_fill`
|
||||||
* `webhookbuycancel` -> `webhookentrycancel`
|
* `webhookbuycancel`, `webhookentrycancel` -> `entry_cancel`
|
||||||
* `webhooksell` -> `webhookexit`
|
* `webhooksell`, `webhookexit` -> `exit`
|
||||||
* `webhooksellfill` -> `webhookexitfill`
|
* `webhooksellfill`, `webhookexitfill` -> `exit_fill`
|
||||||
* `webhooksellcancel` -> `webhookexitcancel`
|
* `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel`
|
||||||
|
@ -50,12 +50,12 @@ Note : `forcesell`, `forcebuy`, `emergencysell` are changed to `force_exit`, `fo
|
|||||||
* `force_sell` -> `force_exit`
|
* `force_sell` -> `force_exit`
|
||||||
* `emergency_sell` -> `emergency_exit`
|
* `emergency_sell` -> `emergency_exit`
|
||||||
* Webhook terminology changed from "sell" to "exit", and from "buy" to entry
|
* Webhook terminology changed from "sell" to "exit", and from "buy" to entry
|
||||||
* `webhookbuy` -> `webhookentry`
|
* `webhookbuy` -> `entry`
|
||||||
* `webhookbuyfill` -> `webhookentryfill`
|
* `webhookbuyfill` -> `entry_fill`
|
||||||
* `webhookbuycancel` -> `webhookentrycancel`
|
* `webhookbuycancel` -> `entry_cancel`
|
||||||
* `webhooksell` -> `webhookexit`
|
* `webhooksell` -> `exit`
|
||||||
* `webhooksellfill` -> `webhookexitfill`
|
* `webhooksellfill` -> `exit_fill`
|
||||||
* `webhooksellcancel` -> `webhookexitcancel`
|
* `webhooksellcancel` -> `exit_cancel`
|
||||||
* Telegram notification settings
|
* Telegram notification settings
|
||||||
* `buy` -> `entry`
|
* `buy` -> `entry`
|
||||||
* `buy_fill` -> `entry_fill`
|
* `buy_fill` -> `entry_fill`
|
||||||
|
@ -77,6 +77,7 @@ Example configuration showing the different settings:
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"token": "your_telegram_token",
|
"token": "your_telegram_token",
|
||||||
"chat_id": "your_telegram_chat_id",
|
"chat_id": "your_telegram_chat_id",
|
||||||
|
"allow_custom_messages": true,
|
||||||
"notification_settings": {
|
"notification_settings": {
|
||||||
"status": "silent",
|
"status": "silent",
|
||||||
"warning": "on",
|
"warning": "on",
|
||||||
@ -115,6 +116,7 @@ Example configuration showing the different settings:
|
|||||||
`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
|
`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
|
||||||
|
|
||||||
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
||||||
|
`allow_custom_messages` completely disable strategy messages.
|
||||||
`reload` allows you to disable reload-buttons on selected messages.
|
`reload` allows you to disable reload-buttons on selected messages.
|
||||||
|
|
||||||
## Create a custom keyboard (command shortcut buttons)
|
## Create a custom keyboard (command shortcut buttons)
|
||||||
|
@ -10,37 +10,37 @@ Sample configuration (tested using IFTTT).
|
|||||||
"webhook": {
|
"webhook": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://maker.ifttt.com/trigger/<YOUREVENT>/with/key/<YOURKEY>/",
|
"url": "https://maker.ifttt.com/trigger/<YOUREVENT>/with/key/<YOURKEY>/",
|
||||||
"webhookentry": {
|
"entry": {
|
||||||
"value1": "Buying {pair}",
|
"value1": "Buying {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhookentrycancel": {
|
"entry_cancel": {
|
||||||
"value1": "Cancelling Open Buy Order for {pair}",
|
"value1": "Cancelling Open Buy Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhookentryfill": {
|
"entry_fill": {
|
||||||
"value1": "Buy Order for {pair} filled",
|
"value1": "Buy Order for {pair} filled",
|
||||||
"value2": "at {open_rate:8f}",
|
"value2": "at {open_rate:8f}",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
},
|
},
|
||||||
"webhookexit": {
|
"exit": {
|
||||||
"value1": "Exiting {pair}",
|
"value1": "Exiting {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhookexitcancel": {
|
"exit_cancel": {
|
||||||
"value1": "Cancelling Open Exit Order for {pair}",
|
"value1": "Cancelling Open Exit Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||||
},
|
},
|
||||||
"webhookexitfill": {
|
"exit_fill": {
|
||||||
"value1": "Exit Order for {pair} filled",
|
"value1": "Exit Order for {pair} filled",
|
||||||
"value2": "at {close_rate:8f}.",
|
"value2": "at {close_rate:8f}.",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
},
|
},
|
||||||
"webhookstatus": {
|
"status": {
|
||||||
"value1": "Status: {status}",
|
"value1": "Status: {status}",
|
||||||
"value2": "",
|
"value2": "",
|
||||||
"value3": ""
|
"value3": ""
|
||||||
@ -57,7 +57,7 @@ You can set the POST body format to Form-Encoded (default), JSON-Encoded, or raw
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://<YOURSUBDOMAIN>.cloud.mattermost.com/hooks/<YOURHOOK>",
|
"url": "https://<YOURSUBDOMAIN>.cloud.mattermost.com/hooks/<YOURHOOK>",
|
||||||
"format": "json",
|
"format": "json",
|
||||||
"webhookstatus": {
|
"status": {
|
||||||
"text": "Status: {status}"
|
"text": "Status: {status}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -88,17 +88,30 @@ Optional parameters are available to enable automatic retries for webhook messag
|
|||||||
"url": "https://<YOURHOOKURL>",
|
"url": "https://<YOURHOOKURL>",
|
||||||
"retries": 3,
|
"retries": 3,
|
||||||
"retry_delay": 0.2,
|
"retry_delay": 0.2,
|
||||||
"webhookstatus": {
|
"status": {
|
||||||
"status": "Status: {status}"
|
"status": "Status: {status}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Custom messages can be sent to Webhook endpoints via the `self.dp.send_msg()` function from within the strategy. To enable this, set the `allow_custom_messages` option to `true`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"webhook": {
|
||||||
|
"enabled": true,
|
||||||
|
"url": "https://<YOURHOOKURL>",
|
||||||
|
"allow_custom_messages": true,
|
||||||
|
"strategy_msg": {
|
||||||
|
"status": "StrategyMessage: {msg}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
|
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
|
||||||
|
|
||||||
### Webhookentry
|
### Entry
|
||||||
|
|
||||||
The fields in `webhook.webhookentry` are filled when the bot executes a long/short. Parameters are filled using string.format.
|
The fields in `webhook.entry` are filled when the bot executes a long/short. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -118,9 +131,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhookentrycancel
|
### Entry cancel
|
||||||
|
|
||||||
The fields in `webhook.webhookentrycancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format.
|
The fields in `webhook.entry_cancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -139,9 +152,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhookentryfill
|
### Entry fill
|
||||||
|
|
||||||
The fields in `webhook.webhookentryfill` are filled when the bot filled a long/short order. Parameters are filled using string.format.
|
The fields in `webhook.entry_fill` are filled when the bot filled a long/short order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -160,9 +173,9 @@ Possible parameters are:
|
|||||||
* `current_rate`
|
* `current_rate`
|
||||||
* `enter_tag`
|
* `enter_tag`
|
||||||
|
|
||||||
### Webhookexit
|
### Exit
|
||||||
|
|
||||||
The fields in `webhook.webhookexit` are filled when the bot exits a trade. Parameters are filled using string.format.
|
The fields in `webhook.exit` are filled when the bot exits a trade. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -184,9 +197,9 @@ Possible parameters are:
|
|||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
### Webhookexitfill
|
### Exit fill
|
||||||
|
|
||||||
The fields in `webhook.webhookexitfill` are filled when the bot fills a exit order (closes a Trade). Parameters are filled using string.format.
|
The fields in `webhook.exit_fill` are filled when the bot fills a exit order (closes a Trade). Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -209,9 +222,9 @@ Possible parameters are:
|
|||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
### Webhookexitcancel
|
### Exit cancel
|
||||||
|
|
||||||
The fields in `webhook.webhookexitcancel` are filled when the bot cancels a exit order. Parameters are filled using string.format.
|
The fields in `webhook.exit_cancel` are filled when the bot cancels a exit order. Parameters are filled using string.format.
|
||||||
Possible parameters are:
|
Possible parameters are:
|
||||||
|
|
||||||
* `trade_id`
|
* `trade_id`
|
||||||
@ -234,9 +247,9 @@ Possible parameters are:
|
|||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
### Webhookstatus
|
### Status
|
||||||
|
|
||||||
The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
|
The fields in `webhook.status` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
|
||||||
|
|
||||||
The only possible value here is `{status}`.
|
The only possible value here is `{status}`.
|
||||||
|
|
||||||
@ -280,7 +293,6 @@ You can configure this as follows:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
The above represents the default (`exit_fill` and `entry_fill` are optional and will default to the above configuration) - modifications are obviously possible.
|
The above represents the default (`exit_fill` and `entry_fill` are optional and will default to the above configuration) - modifications are obviously possible.
|
||||||
|
|
||||||
Available fields correspond to the fields for webhooks and are documented in the corresponding webhook sections.
|
Available fields correspond to the fields for webhooks and are documented in the corresponding webhook sections.
|
||||||
@ -288,3 +300,13 @@ Available fields correspond to the fields for webhooks and are documented in the
|
|||||||
The notifications will look as follows by default.
|
The notifications will look as follows by default.
|
||||||
|
|
||||||
![discord-notification](assets/discord_notification.png)
|
![discord-notification](assets/discord_notification.png)
|
||||||
|
|
||||||
|
Custom messages can be sent from a strategy to Discord endpoints via the dataprovider.send_msg() function. To enable this, set the `allow_custom_messages` option to `true`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"discord": {
|
||||||
|
"enabled": true,
|
||||||
|
"webhook_url": "https://discord.com/api/webhooks/<Your webhook URL ...>",
|
||||||
|
"allow_custom_messages": true,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
@ -5,7 +5,7 @@ bot constants
|
|||||||
"""
|
"""
|
||||||
from typing import Any, Dict, List, Literal, Tuple
|
from typing import Any, Dict, List, Literal, Tuple
|
||||||
|
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, RPCMessageType
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = 'config.json'
|
DEFAULT_CONFIG = 'config.json'
|
||||||
@ -282,6 +282,7 @@ CONF_SCHEMA = {
|
|||||||
'enabled': {'type': 'boolean'},
|
'enabled': {'type': 'boolean'},
|
||||||
'token': {'type': 'string'},
|
'token': {'type': 'string'},
|
||||||
'chat_id': {'type': 'string'},
|
'chat_id': {'type': 'string'},
|
||||||
|
'allow_custom_messages': {'type': 'boolean', 'default': True},
|
||||||
'balance_dust_level': {'type': 'number', 'minimum': 0.0},
|
'balance_dust_level': {'type': 'number', 'minimum': 0.0},
|
||||||
'notification_settings': {
|
'notification_settings': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@ -344,6 +345,8 @@ CONF_SCHEMA = {
|
|||||||
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
|
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
|
||||||
'retries': {'type': 'integer', 'minimum': 0},
|
'retries': {'type': 'integer', 'minimum': 0},
|
||||||
'retry_delay': {'type': 'number', 'minimum': 0},
|
'retry_delay': {'type': 'number', 'minimum': 0},
|
||||||
|
**dict([(x, {'type': 'object'}) for x in RPCMessageType]),
|
||||||
|
# Below -> Deprecated
|
||||||
'webhookentry': {'type': 'object'},
|
'webhookentry': {'type': 'object'},
|
||||||
'webhookentrycancel': {'type': 'object'},
|
'webhookentrycancel': {'type': 'object'},
|
||||||
'webhookentryfill': {'type': 'object'},
|
'webhookentryfill': {'type': 'object'},
|
||||||
|
@ -11,13 +11,12 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class Discord(Webhook):
|
class Discord(Webhook):
|
||||||
def __init__(self, rpc: 'RPC', config: Config):
|
def __init__(self, rpc: 'RPC', config: Config):
|
||||||
# super().__init__(rpc, config)
|
self._config = config
|
||||||
self.rpc = rpc
|
self.rpc = rpc
|
||||||
self.config = config
|
|
||||||
self.strategy = config.get('strategy', '')
|
self.strategy = config.get('strategy', '')
|
||||||
self.timeframe = config.get('timeframe', '')
|
self.timeframe = config.get('timeframe', '')
|
||||||
|
|
||||||
self._url = self.config['discord']['webhook_url']
|
self._url = config['discord']['webhook_url']
|
||||||
self._format = 'json'
|
self._format = 'json'
|
||||||
self._retries = 1
|
self._retries = 1
|
||||||
self._retry_delay = 0.1
|
self._retry_delay = 0.1
|
||||||
@ -31,19 +30,21 @@ class Discord(Webhook):
|
|||||||
|
|
||||||
def send_msg(self, msg) -> None:
|
def send_msg(self, msg) -> None:
|
||||||
|
|
||||||
if msg['type'].value in self.config['discord']:
|
if msg['type'].value in self._config['discord']:
|
||||||
logger.info(f"Sending discord message: {msg}")
|
logger.info(f"Sending discord message: {msg}")
|
||||||
|
|
||||||
msg['strategy'] = self.strategy
|
msg['strategy'] = self.strategy
|
||||||
msg['timeframe'] = self.timeframe
|
msg['timeframe'] = self.timeframe
|
||||||
fields = self.config['discord'].get(msg['type'].value)
|
fields = self._config['discord'].get(msg['type'].value)
|
||||||
color = 0x0000FF
|
color = 0x0000FF
|
||||||
if msg['type'] in (RPCMessageType.EXIT, RPCMessageType.EXIT_FILL):
|
if msg['type'] in (RPCMessageType.EXIT, RPCMessageType.EXIT_FILL):
|
||||||
profit_ratio = msg.get('profit_ratio')
|
profit_ratio = msg.get('profit_ratio')
|
||||||
color = (0x00FF00 if profit_ratio > 0 else 0xFF0000)
|
color = (0x00FF00 if profit_ratio > 0 else 0xFF0000)
|
||||||
|
title = msg['type'].value
|
||||||
|
if 'pair' in msg:
|
||||||
|
title = f"Trade: {msg['pair']} {msg['type'].value}"
|
||||||
embeds = [{
|
embeds = [{
|
||||||
'title': f"Trade: {msg['pair']} {msg['type'].value}",
|
'title': title,
|
||||||
'color': color,
|
'color': color,
|
||||||
'fields': [],
|
'fields': [],
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ class Discord(Webhook):
|
|||||||
for f in fields:
|
for f in fields:
|
||||||
for k, v in f.items():
|
for k, v in f.items():
|
||||||
v = v.format(**msg)
|
v = v.format(**msg)
|
||||||
embeds[0]['fields'].append( # type: ignore
|
embeds[0]['fields'].append(
|
||||||
{'name': k, 'value': v, 'inline': True})
|
{'name': k, 'value': v, 'inline': True})
|
||||||
|
|
||||||
# Send the message to discord channel
|
# Send the message to discord channel
|
||||||
|
@ -88,10 +88,13 @@ class RPCManager:
|
|||||||
"""
|
"""
|
||||||
while queue:
|
while queue:
|
||||||
msg = queue.popleft()
|
msg = queue.popleft()
|
||||||
self.send_msg({
|
logger.info('Sending rpc strategy_msg: %s', msg)
|
||||||
'type': RPCMessageType.STRATEGY_MSG,
|
for mod in self.registered_modules:
|
||||||
'msg': msg,
|
if mod._config.get(mod.name, {}).get('allow_custom_messages', False):
|
||||||
})
|
mod.send_msg({
|
||||||
|
'type': RPCMessageType.STRATEGY_MSG,
|
||||||
|
'msg': msg,
|
||||||
|
})
|
||||||
|
|
||||||
def startup_messages(self, config: Config, pairlist, protections) -> None:
|
def startup_messages(self, config: Config, pairlist, protections) -> None:
|
||||||
if config['dry_run']:
|
if config['dry_run']:
|
||||||
|
@ -3,7 +3,7 @@ This module manages webhook communication
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from requests import RequestException, post
|
from requests import RequestException, post
|
||||||
|
|
||||||
@ -41,36 +41,44 @@ class Webhook(RPCHandler):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _get_value_dict(self, msg: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
|
whconfig = self._config['webhook']
|
||||||
|
# Deprecated 2022.10 - only keep generic method.
|
||||||
|
if msg['type'] in [RPCMessageType.ENTRY]:
|
||||||
|
valuedict = whconfig.get('webhookentry')
|
||||||
|
elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]:
|
||||||
|
valuedict = whconfig.get('webhookentrycancel')
|
||||||
|
elif msg['type'] in [RPCMessageType.ENTRY_FILL]:
|
||||||
|
valuedict = whconfig.get('webhookentryfill')
|
||||||
|
elif msg['type'] == RPCMessageType.EXIT:
|
||||||
|
valuedict = whconfig.get('webhookexit')
|
||||||
|
elif msg['type'] == RPCMessageType.EXIT_FILL:
|
||||||
|
valuedict = whconfig.get('webhookexitfill')
|
||||||
|
elif msg['type'] == RPCMessageType.EXIT_CANCEL:
|
||||||
|
valuedict = whconfig.get('webhookexitcancel')
|
||||||
|
elif msg['type'] in (RPCMessageType.STATUS,
|
||||||
|
RPCMessageType.STARTUP,
|
||||||
|
RPCMessageType.WARNING):
|
||||||
|
valuedict = whconfig.get('webhookstatus')
|
||||||
|
elif msg['type'].value in whconfig:
|
||||||
|
# Allow all types ...
|
||||||
|
valuedict = whconfig.get(msg['type'].value)
|
||||||
|
elif msg['type'] in (
|
||||||
|
RPCMessageType.PROTECTION_TRIGGER,
|
||||||
|
RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
|
||||||
|
RPCMessageType.WHITELIST,
|
||||||
|
RPCMessageType.ANALYZED_DF,
|
||||||
|
RPCMessageType.STRATEGY_MSG):
|
||||||
|
# Don't fail for non-implemented types
|
||||||
|
return None
|
||||||
|
return valuedict
|
||||||
|
|
||||||
def send_msg(self, msg: Dict[str, Any]) -> None:
|
def send_msg(self, msg: Dict[str, Any]) -> None:
|
||||||
""" Send a message to telegram channel """
|
""" Send a message to telegram channel """
|
||||||
try:
|
try:
|
||||||
whconfig = self._config['webhook']
|
|
||||||
if msg['type'] in [RPCMessageType.ENTRY]:
|
valuedict = self._get_value_dict(msg)
|
||||||
valuedict = whconfig.get('webhookentry')
|
|
||||||
elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]:
|
|
||||||
valuedict = whconfig.get('webhookentrycancel')
|
|
||||||
elif msg['type'] in [RPCMessageType.ENTRY_FILL]:
|
|
||||||
valuedict = whconfig.get('webhookentryfill')
|
|
||||||
elif msg['type'] == RPCMessageType.EXIT:
|
|
||||||
valuedict = whconfig.get('webhookexit')
|
|
||||||
elif msg['type'] == RPCMessageType.EXIT_FILL:
|
|
||||||
valuedict = whconfig.get('webhookexitfill')
|
|
||||||
elif msg['type'] == RPCMessageType.EXIT_CANCEL:
|
|
||||||
valuedict = whconfig.get('webhookexitcancel')
|
|
||||||
elif msg['type'] in (RPCMessageType.STATUS,
|
|
||||||
RPCMessageType.STARTUP,
|
|
||||||
RPCMessageType.WARNING):
|
|
||||||
valuedict = whconfig.get('webhookstatus')
|
|
||||||
elif msg['type'] in (
|
|
||||||
RPCMessageType.PROTECTION_TRIGGER,
|
|
||||||
RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
|
|
||||||
RPCMessageType.WHITELIST,
|
|
||||||
RPCMessageType.ANALYZED_DF,
|
|
||||||
RPCMessageType.STRATEGY_MSG):
|
|
||||||
# Don't fail for non-implemented types
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
|
||||||
if not valuedict:
|
if not valuedict:
|
||||||
logger.info("Message type '%s' not configured for webhooks", msg['type'])
|
logger.info("Message type '%s' not configured for webhooks", msg['type'])
|
||||||
return
|
return
|
||||||
|
@ -99,6 +99,7 @@ def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
||||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
||||||
|
default_conf['telegram']['allow_custom_messages'] = True
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
@ -108,8 +109,8 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
|||||||
queue.append('Test message 2')
|
queue.append('Test message 2')
|
||||||
rpc_manager.process_msg_queue(queue)
|
rpc_manager.process_msg_queue(queue)
|
||||||
|
|
||||||
assert log_has("Sending rpc message: {'type': strategy_msg, 'msg': 'Test message'}", caplog)
|
assert log_has("Sending rpc strategy_msg: Test message", caplog)
|
||||||
assert log_has("Sending rpc message: {'type': strategy_msg, 'msg': 'Test message 2'}", caplog)
|
assert log_has("Sending rpc strategy_msg: Test message 2", caplog)
|
||||||
assert telegram_mock.call_count == 2
|
assert telegram_mock.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
|
|
||||||
from freqtrade.enums import ExitType, RPCMessageType
|
from freqtrade.enums import ExitType, RPCMessageType
|
||||||
@ -337,34 +336,18 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
|||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
default_conf["webhook"]["webhookentry"]["value1"] = "{DEADBEEF:8f}"
|
default_conf["webhook"]["strategy_msg"] = {"value1": "{DEADBEEF:8f}"}
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.ENTRY,
|
'type': RPCMessageType.STRATEGY_MSG,
|
||||||
'exchange': 'Binance',
|
'msg': 'hello world',
|
||||||
'pair': 'ETH/BTC',
|
|
||||||
'limit': 0.005,
|
|
||||||
'order_type': 'limit',
|
|
||||||
'stake_amount': 0.8,
|
|
||||||
'stake_amount_fiat': 500,
|
|
||||||
'stake_currency': 'BTC',
|
|
||||||
'fiat_currency': 'EUR'
|
|
||||||
}
|
}
|
||||||
webhook.send_msg(msg)
|
webhook.send_msg(msg)
|
||||||
assert log_has("Problem calling Webhook. Please check your webhook configuration. "
|
assert log_has("Problem calling Webhook. Please check your webhook configuration. "
|
||||||
"Exception: 'DEADBEEF'", caplog)
|
"Exception: 'DEADBEEF'", caplog)
|
||||||
|
|
||||||
msg_mock = MagicMock()
|
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
|
||||||
msg = {
|
|
||||||
'type': 'DEADBEEF',
|
|
||||||
'status': 'whatever'
|
|
||||||
}
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
webhook.send_msg(msg)
|
|
||||||
|
|
||||||
# Test no failure for not implemented but known messagetypes
|
# Test no failure for not implemented but known messagetypes
|
||||||
for e in RPCMessageType:
|
for e in RPCMessageType:
|
||||||
msg = {
|
msg = {
|
||||||
|
Loading…
Reference in New Issue
Block a user