Alerts·Alert Channel Integrations·Intermediate

Notification Discord

Send trading alerts and system status notifications to Discord servers via webhook integration with visually rich embedded message formatting, color-coded severity level indicators, and structured data fields for clear and scannable information display in designated alert channels.

alertsnotifications

Notification and Alerts: Send Trade Alerts via Discord

This notebook demonstrates how to set up and send real-time trade alerts to a Discord channel using webhooks. We will cover the core components necessary to integrate a Python script with Discord, allowing for automated notifications based on simulated trading signals.

Key Concepts

ConceptDescription
Discord WebhooksA way for external services to send messages to Discord channels.
Python requestsLibrary for making HTTP requests to interact with web services.
LoggingRecording events and errors for debugging and monitoring.
Simulated DataGenerating artificial data to mimic real-world trade signals for demonstration.
Error HandlingMechanisms to gracefully manage issues during webhook communication.
Exponential BackoffA strategy for retrying failed operations with increasing delays.
[21]
# Dependency Installation
# Install necessary libraries
!pip install requests
!pip install pandas
!pip install numpy
!pip install matplotlib
!pip install seaborn
Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (2.32.4)
Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests) (3.4.7)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests) (3.18)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.12/dist-packages (from requests) (2026.5.20)
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: numpy>=1.26.0 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.0.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.63.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.5.0)
Requirement already satisfied: numpy>=1.23 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (2.0.2)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.2)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)
Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)
Requirement already satisfied: numpy!=1.24.0,>=1.20 in /usr/local/lib/python3.12/dist-packages (from seaborn) (2.0.2)
Requirement already satisfied: pandas>=1.2 in /usr/local/lib/python3.12/dist-packages (from seaborn) (2.2.2)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in /usr/local/lib/python3.12/dist-packages (from seaborn) (3.10.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.63.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.5.0)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (26.2)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.3.2)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.12/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.2->seaborn) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.2->seaborn) (2026.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)
[22]
# Library Imports
import requests
import json
import logging
import time
import random
from collections import deque
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Dict, Any, List, Tuple

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

Function Name: create_alert_system_state

This function initializes the state dictionary for the alert system. It sets up common parameters like retry limits and base backoff time, which will be used in functions that handle sending alerts.

Parameters:

  • max_retries (int): The maximum number of times to retry sending an alert if it fails. Defaults to 5.
  • base_backoff_sec (int): The base number of seconds to wait before the first retry, which will be increased exponentially. Defaults to 1.

Returns:

  • (dict): A dictionary containing the initial state of the alert system.
[23]
def create_alert_system_state(max_retries: int = 5, base_backoff_sec: int = 1) -> Dict[str, Any]:
    """
    Initializes the state dictionary for the alert system.

    Parameters
    ----------
    max_retries : int, optional
        The maximum number of times to retry sending an alert, defaults to 5.
    base_backoff_sec : int, optional
        The base number of seconds for exponential backoff, defaults to 1.

    Returns
    -------
    dict
        A dictionary containing the initial state of the alert system.
    """
    state = {
        'max_retries': max_retries,
        'base_backoff_sec': base_backoff_sec,
        'sent_alerts_count': 0,
        'failed_alerts_count': 0
    }
    logger.info(f"Alert system state initialized with max_retries={max_retries}, base_backoff_sec={base_backoff_sec}")
    return state

Function Name: send_discord_webhook_message

This function sends a message to a Discord webhook URL. It implements an exponential backoff retry mechanism with random jitter to handle transient network issues or rate limiting from Discord. The function constructs a JSON payload with the message content and makes a POST request.

Parameters:

  • state (dict): The current state dictionary of the alert system, containing max_retries and base_backoff_sec.
  • webhook_url (str): The Discord webhook URL to send the message to.
  • message_content (str): The text content of the message to be sent.

Returns:

  • (bool): True if the message was sent successfully after all retries, False otherwise.
[24]
def send_discord_webhook_message(state: Dict[str, Any], webhook_url: str, message_content: str) -> bool:
    """
    Sends a message to a Discord webhook URL with exponential backoff and jitter.

    Parameters
    ----------
    state : dict
        Current state dictionary with 'max_retries' and 'base_backoff_sec'.
    webhook_url : str
        The Discord webhook URL.
    message_content : str
        The text content of the message to be sent.

    Returns
    -------
    bool
        True if the message was sent successfully, False otherwise.
    """
    payload = {
        'content': message_content
    }
    headers = {
        'Content-Type': 'application/json'
    }

    for i in range(state['max_retries']):
        try:
            response = requests.post(webhook_url, data=json.dumps(payload), headers=headers, timeout=10)
            response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
            logger.info(f"Alert successfully sent to Discord on attempt {i+1}.")
            state['sent_alerts_count'] += 1
            return True
        except requests.exceptions.RequestException as e:
            wait_time = state['base_backoff_sec'] * (2 ** i) + random.uniform(0, 1)
            logger.warning(f"Attempt {i+1} failed to send alert: {e}. Retrying in {wait_time:.2f} seconds...")
            time.sleep(wait_time)

    logger.error(f"Failed to send alert to Discord after {state['max_retries']} attempts.")
    state['failed_alerts_count'] += 1
    return False

Function Name: create_trade_data_state

This function initializes a dictionary to hold simulated trade data. It generates a synthetic price series and sets up parameters for simulating trading signals. The price series is generated as a random walk with some drift.

Parameters:

  • num_data_points (int): The number of simulated price data points to generate. Defaults to 100.
  • initial_price (float): The starting price for the simulated series. Defaults to 100.0.
  • volatility (float): The standard deviation of daily price changes. Defaults to 0.5.

Returns:

  • (dict): A dictionary containing the simulated price data and related parameters.
[25]
def create_trade_data_state(num_data_points: int = 100, initial_price: float = 100.0, volatility: float = 0.5) -> Dict[str, Any]:
    """
    Initializes a dictionary with simulated trade data.

    Generates a synthetic price series for demonstration.

    Parameters
    ----------
    num_data_points : int, optional
        Number of simulated price data points, defaults to 100.
    initial_price : float, optional
        Starting price for the simulated series, defaults to 100.0.
    volatility : float, optional
        Standard deviation of daily price changes, defaults to 0.5.

    Returns
    -------
    dict
        A dictionary containing the simulated price data and parameters.
    """
    prices = [initial_price]
    for _ in range(1, num_data_points):
        change = np.random.normal(0, volatility)
        prices.append(prices[-1] + change)

    df = pd.DataFrame({
        'timestamp': pd.to_datetime(pd.date_range(start='2023-01-01', periods=num_data_points, freq='H')),
        'price': prices
    })

    state = {
        'trade_data': df,
        'num_data_points': num_data_points,
        'initial_price': initial_price,
        'volatility': volatility
    }
    logger.info(f"Simulated trade data generated with {num_data_points} points.")
    return state

Function Name: generate_trade_signal

This function simulates a trade signal (e.g., 'BUY', 'SELL', or 'HOLD') based on a simple random probability. In a real-world scenario, this would involve complex analysis of market data. This simplified version serves to demonstrate the alerting mechanism.

Parameters:

  • state (dict): The current state dictionary (not directly used for signal generation in this simple example, but kept for pattern consistency).
  • price (float): The current price (used for context in the alert message).

Returns:

  • (Tuple[str, str]): A tuple containing the signal type ('BUY', 'SELL', 'HOLD') and a descriptive message.
[26]
def generate_trade_signal(state: Dict[str, Any], price: float) -> Tuple[str, str]:
    """
    Simulates a trade signal (BUY, SELL, HOLD) for demonstration.

    Parameters
    ----------
    state : dict
        Current state dictionary (not used in this simplified signal generation).
    price : float
        The current price, used for context in the alert message.

    Returns
    -------
    Tuple[str, str]
        A tuple containing the signal type and a descriptive message.
    """
    signal_type = random.choice(['BUY', 'SELL', 'HOLD'])
    message = f"Simulated signal: {signal_type} at price ${price:.2f}"
    logger.debug(f"Generated signal: {signal_type} for price {price:.2f}")
    return signal_type, message

Function Name: summarize_alert_metrics

This function takes the alert system's state and calculates useful metrics such as the success rate of sending alerts. It returns these metrics in a structured dictionary, which can be easily displayed.

Parameters:

  • state (dict): The current state dictionary of the alert system, containing sent_alerts_count and failed_alerts_count.

Returns:

  • (dict): A dictionary containing summary metrics of the alert sending process.
[27]
def summarize_alert_metrics(state: Dict[str, Any]) -> Dict[str, Any]:
    """
    Summarizes the alert sending metrics from the alert system state.

    Parameters
    ----------
    state : dict
        Current state dictionary with 'sent_alerts_count' and 'failed_alerts_count'.

    Returns
    -------
    dict
        A dictionary containing summary metrics.
    """
    total_attempts = state['sent_alerts_count'] + state['failed_alerts_count']
    success_rate = (state['sent_alerts_count'] / total_attempts) * 100 if total_attempts > 0 else 0

    metrics = {
        'total_alert_attempts': total_attempts,
        'successful_alerts': state['sent_alerts_count'],
        'failed_alerts': state['failed_alerts_count'],
        'success_rate_percent': f"{success_rate:.2f}%"
    }
    logger.info(f"Alert metrics summarized: {metrics}")
    return metrics

Demonstration and Visualization

In this section, we will demonstrate the end-to-end process of generating simulated trade signals and sending them as alerts to a Discord channel. We will also visualize the simulated price data and the points where alerts would have been triggered.

Note: To run this demonstration, you need to create a Discord webhook URL. Follow these steps:

  1. Go to your Discord server settings.
  2. Navigate to 'Integrations' -> 'Webhooks' -> 'Create Webhook'.
  3. Give it a name and choose a channel.
  4. Copy the Webhook URL and paste it below.
[28]
# @title Discord Webhook Configuration (Run this cell to set up your webhook)
# Replace with your actual Discord Webhook URL
DISCORD_WEBHOOK_URL = "ENTER DISCORD URL" # @param {type:"string"}

if DISCORD_WEBHOOK_URL == "ENTER DISCORD URL" or not DISCORD_WEBHOOK_URL.startswith('https://discord.com/api/webhooks'):
    logger.warning("Please update DISCORD_WEBHOOK_URL with a valid Discord webhook URL to receive alerts.")
else:
    logger.info("Discord Webhook URL configured.")
WARNING:__main__:Please update DISCORD_WEBHOOK_URL with a valid Discord webhook URL to receive alerts.
[29]
# Initialize states
alert_system_state = create_alert_system_state(max_retries=3, base_backoff_sec=0.5)
trade_data_state = create_trade_data_state(num_data_points=20, initial_price=100.0, volatility=0.8)

df_trades = trade_data_state['trade_data'].copy()
df_trades['signal'] = 'HOLD'
df_trades['alert_message'] = None

# Simulate trade signals and send alerts
# We will only send alerts for BUY/SELL signals to avoid spamming
for i, row in df_trades.iterrows():
    signal_type, message = generate_trade_signal(alert_system_state, row['price'])
    df_trades.loc[i, 'signal'] = signal_type

    if signal_type in ['BUY', 'SELL']:
        alert_message = f"Trade Alert for Asset XYZ \nSignal: **{signal_type}**\nPrice: `${row['price']:.2f}`\nTime: {row['timestamp']}"
        df_trades.loc[i, 'alert_message'] = alert_message
        if DISCORD_WEBHOOK_URL != "YOUR_DISCORD_WEBHOOK_URL_HERE":
            send_discord_webhook_message(alert_system_state, DISCORD_WEBHOOK_URL, alert_message)
            # Add a small delay to respect Discord's rate limits, even with backoff
            time.sleep(random.uniform(0.1, 0.5))
        else:
            logger.info(f"Skipping sending alert for {signal_type} (webhook not configured). Message: {alert_message}")
    else:
        logger.debug(f"No alert sent for HOLD signal at price {row['price']:.2f}")

logger.info("Trade signal simulation and alert sending complete.")
/tmp/ipykernel_3617/326230883.py:27: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  'timestamp': pd.to_datetime(pd.date_range(start='2023-01-01', periods=num_data_points, freq='H')),

Summary of Alert Performance

Let's look at how many alerts were attempted, succeeded, and failed.

[30]
# Summarize alert metrics
metrics = summarize_alert_metrics(alert_system_state)
metrics_df = pd.DataFrame([metrics])
display(metrics_df)

# Display a snippet of the trades with signals
print("\nSample of generated trade data with signals:")
display(df_trades[df_trades['signal'] != 'HOLD'].head())
total_alert_attempts successful_alerts failed_alerts success_rate_percent
0 13 13 0 100.00%

Sample of generated trade data with signals:
timestamp price signal alert_message
0 2023-01-01 00:00:00 100.000000 BUY Trade Alert for Asset XYZ \nSignal: **BUY**\nP...
1 2023-01-01 01:00:00 99.869786 BUY Trade Alert for Asset XYZ \nSignal: **BUY**\nP...
3 2023-01-01 03:00:00 99.382524 SELL Trade Alert for Asset XYZ \nSignal: **SELL**\n...
5 2023-01-01 05:00:00 99.485482 SELL Trade Alert for Asset XYZ \nSignal: **SELL**\n...
6 2023-01-01 06:00:00 99.182570 BUY Trade Alert for Asset XYZ \nSignal: **BUY**\nP...

Visualization of Simulated Price and Signals

We will plot the simulated asset price over time and highlight the points where 'BUY' and 'SELL' signals were generated.

[31]
# Plotting the simulated price and trade signals
plt.figure(figsize=(16, 8))
sns.lineplot(x='timestamp', y='price', data=df_trades, label='Asset Price', color='blue')

buy_signals = df_trades[df_trades['signal'] == 'BUY']
sell_signals = df_trades[df_trades['signal'] == 'SELL']

sns.scatterplot(x='timestamp', y='price', data=buy_signals, color='green', marker='^', s=200, label='BUY Signal', zorder=5)
sns.scatterplot(x='timestamp', y='price', data=sell_signals, color='red', marker='v', s=200, label='SELL Signal', zorder=5)

plt.title('Simulated Asset Price with Trade Signals')
plt.xlabel('Timestamp')
plt.ylabel('Price')
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
cell output

Production Considerations

Deploying a robust notification system for trade alerts requires careful consideration beyond basic functionality. Here are some best practices:

AspectBest Practice
SecurityNever hardcode webhook URLs. Use environment variables or secure secret management.
Rate LimitingDiscord has strict rate limits. Implement robust exponential backoff with jitter and consider dedicated queues for alerts.
Asynchronous SendingFor high-volume alerts, send webhooks asynchronously to avoid blocking your main application logic. Use libraries like asyncio or message queues.
ConfigurationExternalize webhook URLs and other configurations (e.g., alert thresholds) for easy updates without code changes.
Error MonitoringIntegrate with an error tracking service (e.g., Sentry, Prometheus) to monitor webhook failures and latency.
Message FormattingUse Discord's rich embed features for more informative and visually appealing alerts.
Alert PrioritizationImplement logic to prioritize critical alerts and potentially use different channels or louder notifications for them.
IdempotencyDesign your alert system so that sending the same alert multiple times doesn't cause issues (e.g., duplicate processing on the receiving end).
TestingThoroughly test alert delivery, especially under load and failure conditions. Use mock webhooks for testing without hitting live Discord.

Conclusion

This notebook has demonstrated a fundamental approach to setting up a notification system for sending trade alerts to Discord using webhooks. We covered:

  • Initialization of alert system state and simulated trade data.
  • Core functions for sending messages with exponential backoff and generating simple trade signals.
  • A demonstration of simulating signals, attempting to send alerts, and visualizing the results.
  • Key production considerations for building a reliable and scalable alerting solution.

By extending these concepts, you can integrate real-time data sources, implement sophisticated trading strategies, and ensure your critical alerts reach you promptly and reliably.