refactor into discord rpc module

This commit is contained in:
Anuj Shah 2022-06-01 21:14:48 +05:30
parent afd8e85835
commit 45c47bda60
3 changed files with 144 additions and 94 deletions

View File

@ -2,7 +2,6 @@
Freqtrade is the main module of this bot. It contains the class Freqtrade() Freqtrade is the main module of this bot. It contains the class Freqtrade()
""" """
import copy import copy
import json
import logging import logging
import traceback import traceback
from datetime import datetime, time, timezone from datetime import datetime, time, timezone
@ -10,7 +9,6 @@ from math import isclose
from threading import Lock from threading import Lock
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
import requests
from schedule import Scheduler from schedule import Scheduler
from freqtrade import __version__, constants from freqtrade import __version__, constants
@ -36,6 +34,7 @@ from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -380,9 +379,9 @@ class FreqtradeBot(LoggingMixin):
except ExchangeError: except ExchangeError:
logger.warning(f"Error updating {order.order_id}.") logger.warning(f"Error updating {order.order_id}.")
# #
# BUY / enter positions / open trades logic and methods # BUY / enter positions / open trades logic and methods
# #
def enter_positions(self) -> int: def enter_positions(self) -> int:
""" """
@ -490,9 +489,9 @@ class FreqtradeBot(LoggingMixin):
else: else:
return False return False
# #
# BUY / increase positions / DCA logic and methods # BUY / increase positions / DCA logic and methods
# #
def process_open_trade_positions(self): def process_open_trade_positions(self):
""" """
Tries to execute additional buy or sell orders for open trades (positions) Tries to execute additional buy or sell orders for open trades (positions)
@ -886,9 +885,9 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
# #
# SELL / exit positions / close trades logic and methods # SELL / exit positions / close trades logic and methods
# #
def exit_positions(self, trades: List[Any]) -> int: def exit_positions(self, trades: List[Any]) -> int:
""" """
@ -1544,43 +1543,6 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
open_date = trade.open_date.strftime('%Y-%m-%d %H:%M:%S')
close_date = trade.close_date.strftime('%Y-%m-%d %H:%M:%S') if trade.close_date else None
# Send the message to the discord bot
embeds = [{
'title': '{} Trade: {}'.format(
'Profit' if profit_ratio > 0 else 'Loss',
trade.pair),
'color': (0x00FF00 if profit_ratio > 0 else 0xFF0000),
'fields': [
{'name': 'Trade ID', 'value': trade.id, 'inline': True},
{'name': 'Exchange', 'value': trade.exchange.capitalize(), 'inline': True},
{'name': 'Pair', 'value': trade.pair, 'inline': True},
{'name': 'Direction', 'value': 'Short' if trade.is_short else 'Long', 'inline': True},
{'name': 'Open rate', 'value': trade.open_rate, 'inline': True},
{'name': 'Close rate', 'value': trade.close_rate, 'inline': True},
{'name': 'Amount', 'value': trade.amount, 'inline': True},
{'name': 'Open order', 'value': trade.open_order_id, 'inline': True},
{'name': 'Open date', 'value': open_date, 'inline': True},
{'name': 'Close date', 'value': close_date, 'inline': True},
{'name': 'Profit', 'value': profit_trade, 'inline': True},
{'name': 'Profitability', 'value': '{:.2f}%'.format(profit_ratio * 100), 'inline': True},
{'name': 'Stake currency', 'value': self.config['stake_currency'], 'inline': True},
{'name': 'Fiat currency', 'value': self.config.get('fiat_display_currency', None), 'inline': True},
{'name': 'Buy Tag', 'value': trade.enter_tag, 'inline': True},
{'name': 'Sell Reason', 'value': trade.exit_reason, 'inline': True},
{'name': 'Strategy', 'value': trade.strategy, 'inline': True},
{'name': 'Timeframe', 'value': trade.timeframe, 'inline': True},
],
}]
# convert all value in fields to string
for embed in embeds:
for field in embed['fields']:
field['value'] = str(field['value'])
if fill:
self.discord_send(embeds)
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
""" """
Sends rpc notification when a sell cancel occurred. Sends rpc notification when a sell cancel occurred.
@ -1631,9 +1593,9 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
# #
# Common update trade state methods # Common update trade state methods
# #
def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None, def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None,
stoploss_order: bool = False, send_msg: bool = True) -> bool: stoploss_order: bool = False, send_msg: bool = True) -> bool:
@ -1856,22 +1818,3 @@ class FreqtradeBot(LoggingMixin):
return max( return max(
min(valid_custom_price, max_custom_price_allowed), min(valid_custom_price, max_custom_price_allowed),
min_custom_price_allowed) min_custom_price_allowed)
def discord_send(self, embeds):
if not 'discord' in self.config or self.config['discord']['enabled'] == False:
return
if self.config['runmode'].value in ('dry_run', 'live'):
webhook_url = self.config['discord']['webhook_url']
payload = {
"embeds": embeds
}
headers = {
"Content-Type": "application/json"
}
try:
requests.post(webhook_url, data=json.dumps(payload), headers=headers)
except Exception as e:
logger.error(f"Error sending discord message: {e}")

101
freqtrade/rpc/discord.py Normal file
View File

@ -0,0 +1,101 @@
import json
import logging
from typing import Dict, Any
import requests
from freqtrade.enums import RPCMessageType
from freqtrade.rpc import RPCHandler, RPC
class Discord(RPCHandler):
def __init__(self, rpc: 'RPC', config: Dict[str, Any]):
super().__init__(rpc, config)
self.logger = logging.getLogger(__name__)
self.strategy = config.get('strategy', '')
self.timeframe = config.get('timeframe', '')
self.config = config
def send_msg(self, msg: Dict[str, str]) -> None:
self._send_msg(msg)
def _send_msg(self, msg):
"""
msg = {
'type': (RPCMessageType.EXIT_FILL if fill
else RPCMessageType.EXIT),
'trade_id': trade.id,
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'leverage': trade.leverage,
'direction': 'Short' if trade.is_short else 'Long',
'gain': gain,
'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
'close_rate': trade.close_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.exit_reason, # Deprecated
'exit_reason': trade.exit_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
}
"""
self.logger.info(f"Sending discord message: {msg}")
# TODO: handle other message types
if msg['type'] == RPCMessageType.EXIT_FILL:
profit_ratio = msg.get('profit_ratio')
open_date = msg.get('open_date').strftime('%Y-%m-%d %H:%M:%S')
close_date = msg.get('close_date').strftime('%Y-%m-%d %H:%M:%S') if msg.get('close_date') else ''
embeds = [{
'title': '{} Trade: {}'.format(
'Profit' if profit_ratio > 0 else 'Loss',
msg.get('pair')),
'color': (0x00FF00 if profit_ratio > 0 else 0xFF0000),
'fields': [
{'name': 'Trade ID', 'value': msg.get('id'), 'inline': True},
{'name': 'Exchange', 'value': msg.get('exchange').capitalize(), 'inline': True},
{'name': 'Pair', 'value': msg.get('pair'), 'inline': True},
{'name': 'Direction', 'value': 'Short' if msg.get('is_short') else 'Long', 'inline': True},
{'name': 'Open rate', 'value': msg.get('open_rate'), 'inline': True},
{'name': 'Close rate', 'value': msg.get('close_rate'), 'inline': True},
{'name': 'Amount', 'value': msg.get('amount'), 'inline': True},
{'name': 'Open order', 'value': msg.get('open_order_id'), 'inline': True},
{'name': 'Open date', 'value': open_date, 'inline': True},
{'name': 'Close date', 'value': close_date, 'inline': True},
{'name': 'Profit', 'value': msg.get('profit_amount'), 'inline': True},
{'name': 'Profitability', 'value': '{:.2f}%'.format(profit_ratio * 100), 'inline': True},
{'name': 'Stake currency', 'value': msg.get('stake_currency'), 'inline': True},
{'name': 'Fiat currency', 'value': msg.get('fiat_display_currency'), 'inline': True},
{'name': 'Buy Tag', 'value': msg.get('enter_tag'), 'inline': True},
{'name': 'Sell Reason', 'value': msg.get('exit_reason'), 'inline': True},
{'name': 'Strategy', 'value': self.strategy, 'inline': True},
{'name': 'Timeframe', 'value': self.timeframe, 'inline': True},
],
}]
# convert all value in fields to string for discord
for embed in embeds:
for field in embed['fields']:
field['value'] = str(field['value'])
# Send the message to discord channel
payload = {
'embeds': embeds,
}
headers = {
'Content-Type': 'application/json',
}
try:
requests.post(self.config['discord']['webhook_url'], data=json.dumps(payload), headers=headers)
except Exception as e:
self.logger.error(f"Failed to send discord message: {e}")

View File

@ -27,6 +27,12 @@ class RPCManager:
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(self._rpc, config)) self.registered_modules.append(Telegram(self._rpc, config))
# Enable discord
if config.get('discord', {}).get('enabled', False):
logger.info('Enabling rpc.discord ...')
from freqtrade.rpc.discord import Discord
self.registered_modules.append(Discord(self._rpc, config))
# Enable Webhook # Enable Webhook
if config.get('webhook', {}).get('enabled', False): if config.get('webhook', {}).get('enabled', False):
logger.info('Enabling rpc.webhook ...') logger.info('Enabling rpc.webhook ...')