feat: add support for discord notification

This commit is contained in:
Anuj Shah 2022-06-01 15:54:32 +05:30
parent 34a44b9dd2
commit afd8e85835

View File

@ -2,6 +2,7 @@
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
@ -9,6 +10,7 @@ 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
@ -34,7 +36,6 @@ 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__)
@ -379,9 +380,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:
""" """
@ -489,9 +490,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)
@ -579,16 +580,16 @@ class FreqtradeBot(LoggingMixin):
return False return False
def execute_entry( def execute_entry(
self, self,
pair: str, pair: str,
stake_amount: float, stake_amount: float,
price: Optional[float] = None, price: Optional[float] = None,
*, *,
is_short: bool = False, is_short: bool = False,
ordertype: Optional[str] = None, ordertype: Optional[str] = None,
enter_tag: Optional[str] = None, enter_tag: Optional[str] = None,
trade: Optional[Trade] = None, trade: Optional[Trade] = None,
order_adjust: bool = False order_adjust: bool = False
) -> bool: ) -> bool:
""" """
Executes a limit buy for the given pair Executes a limit buy for the given pair
@ -622,9 +623,9 @@ class FreqtradeBot(LoggingMixin):
if not pos_adjust and not strategy_safe_wrapper( if not pos_adjust and not strategy_safe_wrapper(
self.strategy.confirm_trade_entry, default_retval=True)( self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc), time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
entry_tag=enter_tag, side=trade_side): entry_tag=enter_tag, side=trade_side):
logger.info(f"User requested abortion of buying {pair}") logger.info(f"User requested abortion of buying {pair}")
return False return False
order = self.exchange.create_order( order = self.exchange.create_order(
@ -746,11 +747,11 @@ class FreqtradeBot(LoggingMixin):
return trade return trade
def get_valid_enter_price_and_stake( def get_valid_enter_price_and_stake(
self, pair: str, price: Optional[float], stake_amount: float, self, pair: str, price: Optional[float], stake_amount: float,
trade_side: LongShort, trade_side: LongShort,
entry_tag: Optional[str], entry_tag: Optional[str],
trade: Optional[Trade], trade: Optional[Trade],
order_adjust: bool, order_adjust: bool,
) -> Tuple[float, float, float]: ) -> Tuple[float, float, float]:
if price: if price:
@ -885,9 +886,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:
""" """
@ -1059,10 +1060,10 @@ class FreqtradeBot(LoggingMixin):
# Finally we check if stoploss on exchange should be moved up because of trailing. # Finally we check if stoploss on exchange should be moved up because of trailing.
# Triggered Orders are now real orders - so don't replace stoploss anymore # Triggered Orders are now real orders - so don't replace stoploss anymore
if ( if (
trade.is_open and stoploss_order trade.is_open and stoploss_order
and stoploss_order.get('status_stop') != 'triggered' and stoploss_order.get('status_stop') != 'triggered'
and (self.config.get('trailing_stop', False) and (self.config.get('trailing_stop', False)
or self.config.get('use_custom_stoploss', False)) or self.config.get('use_custom_stoploss', False))
): ):
# if trailing stoploss is enabled we check if stoploss value has changed # if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new # in which case we cancel stoploss order and put another one with new
@ -1145,7 +1146,7 @@ class FreqtradeBot(LoggingMixin):
if not_closed: if not_closed:
if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
trade, order_obj, datetime.now(timezone.utc))): trade, order_obj, datetime.now(timezone.utc))):
self.handle_timedout_order(order, trade) self.handle_timedout_order(order, trade)
else: else:
self.replace_order(order, order_obj, trade) self.replace_order(order, order_obj, trade)
@ -1424,7 +1425,7 @@ class FreqtradeBot(LoggingMixin):
# if stoploss is on exchange and we are on dry_run mode, # if stoploss is on exchange and we are on dry_run mode,
# we consider the sell price stop price # we consider the sell price stop price
if (self.config['dry_run'] and exit_type == 'stoploss' if (self.config['dry_run'] and exit_type == 'stoploss'
and self.strategy.order_types['stoploss_on_exchange']): and self.strategy.order_types['stoploss_on_exchange']):
limit = trade.stop_loss limit = trade.stop_loss
# set custom_exit_price if available # set custom_exit_price if available
@ -1543,6 +1544,43 @@ 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.
@ -1593,9 +1631,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:
@ -1818,3 +1856,22 @@ 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}")