refactor into discord rpc module
This commit is contained in:
parent
afd8e85835
commit
45c47bda60
@ -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)
|
||||||
@ -580,16 +579,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
|
||||||
@ -623,9 +622,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(
|
||||||
@ -747,11 +746,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:
|
||||||
@ -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:
|
||||||
"""
|
"""
|
||||||
@ -1060,10 +1059,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
|
||||||
@ -1146,7 +1145,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)
|
||||||
@ -1425,7 +1424,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
|
||||||
@ -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
101
freqtrade/rpc/discord.py
Normal 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}")
|
@ -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 ...')
|
||||||
|
Loading…
Reference in New Issue
Block a user