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()
"""
import copy
import json
import logging
import traceback
from datetime import datetime, time, timezone
@ -9,6 +10,7 @@ from math import isclose
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple
import requests
from schedule import Scheduler
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.wallets import Wallets
logger = logging.getLogger(__name__)
@ -379,9 +380,9 @@ class FreqtradeBot(LoggingMixin):
except ExchangeError:
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:
"""
@ -489,9 +490,9 @@ class FreqtradeBot(LoggingMixin):
else:
return False
#
# BUY / increase positions / DCA logic and methods
#
#
# BUY / increase positions / DCA logic and methods
#
def process_open_trade_positions(self):
"""
Tries to execute additional buy or sell orders for open trades (positions)
@ -579,16 +580,16 @@ class FreqtradeBot(LoggingMixin):
return False
def execute_entry(
self,
pair: str,
stake_amount: float,
price: Optional[float] = None,
*,
is_short: bool = False,
ordertype: Optional[str] = None,
enter_tag: Optional[str] = None,
trade: Optional[Trade] = None,
order_adjust: bool = False
self,
pair: str,
stake_amount: float,
price: Optional[float] = None,
*,
is_short: bool = False,
ordertype: Optional[str] = None,
enter_tag: Optional[str] = None,
trade: Optional[Trade] = None,
order_adjust: bool = False
) -> bool:
"""
Executes a limit buy for the given pair
@ -622,9 +623,9 @@ class FreqtradeBot(LoggingMixin):
if not pos_adjust and not strategy_safe_wrapper(
self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
entry_tag=enter_tag, side=trade_side):
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
entry_tag=enter_tag, side=trade_side):
logger.info(f"User requested abortion of buying {pair}")
return False
order = self.exchange.create_order(
@ -746,11 +747,11 @@ class FreqtradeBot(LoggingMixin):
return trade
def get_valid_enter_price_and_stake(
self, pair: str, price: Optional[float], stake_amount: float,
trade_side: LongShort,
entry_tag: Optional[str],
trade: Optional[Trade],
order_adjust: bool,
self, pair: str, price: Optional[float], stake_amount: float,
trade_side: LongShort,
entry_tag: Optional[str],
trade: Optional[Trade],
order_adjust: bool,
) -> Tuple[float, float, float]:
if price:
@ -885,9 +886,9 @@ class FreqtradeBot(LoggingMixin):
# Send the message
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:
"""
@ -1059,10 +1060,10 @@ class FreqtradeBot(LoggingMixin):
# 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
if (
trade.is_open and stoploss_order
and stoploss_order.get('status_stop') != 'triggered'
and (self.config.get('trailing_stop', False)
or self.config.get('use_custom_stoploss', False))
trade.is_open and stoploss_order
and stoploss_order.get('status_stop') != 'triggered'
and (self.config.get('trailing_stop', False)
or self.config.get('use_custom_stoploss', False))
):
# 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
@ -1145,7 +1146,7 @@ class FreqtradeBot(LoggingMixin):
if not_closed:
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)
else:
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,
# we consider the sell price stop price
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
# set custom_exit_price if available
@ -1543,6 +1544,43 @@ class FreqtradeBot(LoggingMixin):
# Send the message
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:
"""
Sends rpc notification when a sell cancel occurred.
@ -1593,9 +1631,9 @@ class FreqtradeBot(LoggingMixin):
# Send the message
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,
stoploss_order: bool = False, send_msg: bool = True) -> bool:
@ -1818,3 +1856,22 @@ class FreqtradeBot(LoggingMixin):
return max(
min(valid_custom_price, max_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}")