Alerts·Alert Channel Integrations·Intermediate

Notification Slack

Send real-time trade execution alerts and system notifications to Slack workspaces using incoming webhooks and the Slack Block Kit API for richly formatted messages with structured layout blocks, threaded reply discussions, and channel-based alert routing for team-based trading workflows.

alertsnotifications

Notifications and Alerts: Send Trade Alerts via Slack

This notebook demonstrates how to set up and send trade alerts to a Slack channel using the Slack API. It covers essential components such as configuring Slack, sending messages, and incorporating best practices for robust notification systems.

Key Concepts

ConceptDescription
Slack APIProgrammatic interface to interact with Slack workspaces.
WebhooksA method for an app to provide other applications with real-time information.
Bot TokensUsed to authenticate your app's requests to the Slack API.
ChannelsSpecific chat rooms within Slack where messages are posted.
Rate LimitingRestricting the number of API requests to prevent abuse.
Retry MechanismReattempting failed operations, often with exponential backoff.
LoggingRecording events and activities for monitoring and debugging.
Trade AlertsNotifications sent when specific trading conditions are met.

Dependency Installation

We will install the slack_sdk for interacting with the Slack API, python-dotenv for managing environment variables (API keys), and tenacity for robust retry mechanisms.

[41]
pip install slack_sdk python-dotenv tenacity pandas matplotlib seaborn numpy
Requirement already satisfied: slack_sdk in /usr/local/lib/python3.12/dist-packages (3.42.0)
Requirement already satisfied: python-dotenv in /usr/local/lib/python3.12/dist-packages (1.2.2)
Requirement already satisfied: tenacity in /usr/local/lib/python3.12/dist-packages (9.1.4)
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)
Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (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: 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: 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: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)

Library Imports

This section imports all necessary libraries. Standard libraries are imported first, followed by third-party libraries.

[42]
import os
import logging
import time
import random
from collections import deque
from datetime import datetime, timedelta

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from dotenv import load_dotenv
from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type

Core Functions

This section defines the core functions required for sending Slack notifications. Each function is presented in its own code block, preceded by a markdown header explaining its purpose, algorithm, parameters, and return values.

Function Name: setup_logger

This function initializes and configures a Python logger. It sets up a basic console logger with a specified logging level and format, ensuring that all subsequent log messages are formatted consistently. This is crucial for debugging and monitoring the notification system.

Parameters:

  • logger_name (str): The name of the logger to be set up.
  • log_level (int): The logging level (e.g., logging.INFO, logging.DEBUG).

Returns:

  • logging.Logger: The configured logger instance.
[43]
def setup_logger(logger_name: str, log_level: int = logging.INFO) -> logging.Logger:
    """
    Sets up and configures a Python logger.

    Parameters
    ----------
    logger_name : str
        The name of the logger to be set up.
    log_level : int, optional
        The logging level (e.g., logging.INFO, logging.DEBUG), defaults to logging.INFO.

    Returns
    -------
    logging.Logger
        The configured logger instance.
    """
    logger = logging.getLogger(logger_name)
    logger.setLevel(log_level)
    # Prevent adding multiple handlers if the logger already has one
    if not logger.handlers:
        handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    logger.info(f"Logger '{logger_name}' initialized with level {logging.getLevelName(log_level)}.")
    return logger

Function Name: create_slack_config

This function initializes the configuration for sending messages to Slack. It loads environment variables (specifically the Slack bot token) using python-dotenv and initializes the WebClient for the Slack API. This function ensures that the Slack client is ready for use, handling cases where environment variables might be missing.

Parameters:

  • state (dict): The current state dictionary, which will be updated with Slack client information.

Returns:

  • dict: The updated state dictionary containing the Slack client and channel ID.
[44]
def create_slack_config(state: dict) -> dict:
    """
    Initializes the Slack configuration by loading environment variables and creating a Slack WebClient.

    Parameters
    ----------
    state : dict
        Current state dictionary.

    Returns
    -------
    dict
        Updated state with Slack client and channel ID.
    """
    logger = logging.getLogger('slack_config')
    load_dotenv()  # Load environment variables from .env file

    slack_bot_token = os.environ.get("SLACK_BOT_TOKEN")
    slack_bot_token = "xoxb-11337840340049-11329656469459-KBbBdP3BtKYaaN3Vrt35fwpH"
    # slack_channel_id = os.environ.get("SLACK_CHANNEL_ID", "https://testnotebookworkspace.slack.com/archives/C0B9ZJ87MPE") # Default to #general
    # slack_channel_id = "https://testnotebookworkspace.slack.com/archives/C0B9ZJ87MPE"
    slack_channel_id = "C0B9ZJ87MPE"
    if not slack_bot_token:
        logger.warning("SLACK_BOT_TOKEN environment variable not found. Slack functionality may be limited.")
        state['slack_client'] = None
    else:
        state['slack_client'] = WebClient(token=slack_bot_token)
        logger.info("Slack WebClient initialized successfully.")

    state['slack_channel_id'] = slack_channel_id
    logger.info(f"Slack channel ID set to: {slack_channel_id}")

    return state

Function Name: send_slack_message

This function sends a message to a specified Slack channel using the initialized Slack client. It incorporates a robust retry mechanism with exponential backoff using tenacity to handle transient network issues or Slack API rate limiting. A small random jitter is added to the backoff to prevent thundering herd problems. It logs the success or failure of the message delivery.

Parameters:

  • state (dict): The current state dictionary containing the Slack client and channel ID.
  • message (str): The text message to be sent to Slack.

Returns:

  • dict: The updated state dictionary, possibly with metrics on message sending.
[45]
def _add_jitter(value: float) -> float:
    """
    Adds a small random jitter to a given value.

    Parameters
    ----------
    value : float
        The original value.

    Returns
    -------
    float
        The value with added jitter.
    """
    return value * (1 + random.uniform(-0.1, 0.1)) # +/- 10% jitter

@retry(
    wait=wait_exponential(multiplier=1, min=_add_jitter(4), max=_add_jitter(60)),
    stop=stop_after_attempt(5),
    retry=retry_if_exception_type(SlackApiError),
    reraise=True
)
def send_slack_message(state: dict, message: str) -> dict:
    """
    Sends a message to the configured Slack channel with retries and exponential backoff.

    Parameters
    ----------
    state : dict
        Current state dictionary containing the Slack client and channel ID.
    message : str
        The text message to be sent to Slack.

    Returns
    -------
    dict
        Updated state dictionary.

    Examples
    --------
    >>> state = create_slack_config({})
    >>> state = send_slack_message(state, "Hello from Colab!")
    """
    logger = logging.getLogger('slack_sender')
    client = state.get('slack_client')
    channel_id = state.get('slack_channel_id')

    if not client:
        logger.error("Slack client not initialized. Cannot send message.")
        # Track failure for demonstration
        if 'message_statuses' not in state:
            state['message_statuses'] = deque()
        state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': 'no_client'})
        return state

    try:
        response = client.chat_postMessage(
            channel=channel_id,
            text=message
        )
        if response['ok']:
            logger.info(f"Message sent successfully to {channel_id}: {message[:50]}...")
            if 'message_statuses' not in state:
                state['message_statuses'] = deque()
            state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'sent', 'reason': None})
        else:
            error_msg = response['error']
            logger.error(f"Failed to send message to {channel_id}: {error_msg}")
            if 'message_statuses' not in state:
                state['message_statuses'] = deque()
            state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': error_msg})

    except SlackApiError as e:
        logger.warning(f"Slack API error encountered, retrying: {e.response['error']}")
        # The 'retry' decorator will handle re-raising and waiting
        raise # Re-raise to trigger tenacity retry
    except Exception as e:
        logger.critical(f"An unexpected error occurred while sending Slack message: {e}")
        if 'message_statuses' not in state:
            state['message_statuses'] = deque()
        state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': str(e)})

    return state

Function Name: generate_trade_alert

This function simulates the generation of a trade alert based on hypothetical market data. It creates a simple dictionary representing a trade alert, including details like symbol, price, quantity, and a timestamp. This serves as a placeholder for actual trading logic that would determine when an alert should be sent.

Parameters:

  • state (dict): The current state dictionary (not directly used for alert generation but for consistency).
  • symbol (str): The stock or asset symbol (e.g., 'AAPL').
  • price (float): The price at which the trade alert is triggered.
  • quantity (int): The quantity involved in the trade.
  • action (str): The trade action (e.g., 'BUY', 'SELL').

Returns:

  • dict: A dictionary representing the generated trade alert.
[46]
def generate_trade_alert(state: dict, symbol: str, price: float, quantity: int, action: str) -> dict:
    """
    Generates a simulated trade alert dictionary.

    Parameters
    ----------
    state : dict
        Current state dictionary.
    symbol : str
        The stock or asset symbol (e.g., 'AAPL').
    price : float
        The price at which the trade alert is triggered.
    quantity : int
        The quantity involved in the trade.
    action : str
        The trade action (e.g., 'BUY', 'SELL').

    Returns
    -------
    dict
        A dictionary representing the generated trade alert.

    Examples
    --------
    >>> alert = generate_trade_alert({}, 'MSFT', 300.50, 100, 'BUY')
    >>> print(alert)
    """
    logger = logging.getLogger('trade_alert_generator')
    alert = {
        'timestamp': datetime.now().isoformat(),
        'symbol': symbol,
        'action': action,
        'price': price,
        'quantity': quantity,
        'message': f"TRADE ALERT: {action} {quantity} shares of {symbol} at ${price:.2f}"
    }
    logger.info(f"Generated alert for {symbol}: {action} at {price}")
    return alert

Function Name: format_slack_message

This function takes a trade alert dictionary and formats it into a human-readable string suitable for a Slack message. It can be extended to include rich Slack message blocks for more complex notifications, but for this demonstration, a simple text format is used.

Parameters:

  • state (dict): The current state dictionary (not directly used for formatting but for consistency).
  • alert (dict): The trade alert dictionary generated by generate_trade_alert.

Returns:

  • str: A formatted message string for Slack.
[47]
def format_slack_message(state: dict, alert: dict) -> str:
    """
    Formats a trade alert dictionary into a string suitable for a Slack message.

    Parameters
    ----------
    state : dict
        Current state dictionary.
    alert : dict
        The trade alert dictionary.

    Returns
    -------
    str
        A formatted message string for Slack.

    Examples
    --------
    >>> alert_data = {'timestamp': '...', 'symbol': 'GOOG', 'action': 'SELL', 'price': 150.0, 'quantity': 50, 'message': '...'}
    >>> msg = format_slack_message({}, alert_data)
    >>> print(msg)
    """
    logger = logging.getLogger('message_formatter')
    message = (
        f":loudspeaker: *TRADE ALERT* :loudspeaker:\n"
        f"> *Symbol:* `{alert['symbol']}`\n"
        f"> *Action:* `{alert['action']}`\n"
        f"> *Price:* `${alert['price']:.2f}`\n"
        f"> *Quantity:* `{alert['quantity']}`\n"
        f"> _Timestamp:_ `{alert['timestamp']}`\n"
        f"> :bulb: _{alert['message']}_"
    )
    logger.debug("Message formatted for Slack.")
    return message

Function Name: summarize_message_statuses

This function processes a deque of message statuses and generates a summary DataFrame. It calculates the total messages sent, successful messages, failed messages, and the success rate. This function is useful for tracking the performance of the notification system over time or during a batch of operations.

Parameters:

  • state (dict): The current state dictionary containing the message_statuses deque.

Returns:

  • pandas.DataFrame: A DataFrame summarizing the message sending performance.
[48]
def summarize_message_statuses(state: dict) -> pd.DataFrame:
    """
    Summarizes the message sending performance from the 'message_statuses' deque.

    Parameters
    ----------
    state : dict
        Current state dictionary containing the 'message_statuses' deque.

    Returns
    -------
    pd.DataFrame
        A DataFrame summarizing the message sending performance.
    """
    logger = logging.getLogger('summary_generator')
    statuses = state.get('message_statuses', deque())
    if not statuses:
        logger.info("No message statuses to summarize.")
        return pd.DataFrame(columns=['Metric', 'Value'])

    df_statuses = pd.DataFrame(list(statuses))
    total_messages = len(df_statuses)
    successful_messages = df_statuses[df_statuses['status'] == 'sent'].shape[0]
    failed_messages = total_messages - successful_messages
    success_rate = (successful_messages / total_messages * 100) if total_messages > 0 else 0

    summary_data = {
        'Metric': ['Total Messages', 'Successful Messages', 'Failed Messages', 'Success Rate (%)'],
        'Value': [total_messages, successful_messages, failed_messages, f"{success_rate:.2f}"]
    }
    summary_df = pd.DataFrame(summary_data)
    logger.info("Message status summary generated.")
    return summary_df

Demonstration/Visualization

This section demonstrates the end-to-end process of generating trade alerts and sending them to Slack. It simulates multiple alerts, visualizes the success rate of message delivery, and shows how to handle potential failures.

To run this section, you will need a Slack workspace and a bot token with chat:write permissions in your .env file or as an environment variable (SLACK_BOT_TOKEN). You also need to specify a SLACK_CHANNEL_ID.

[49]
# 1. Initialize Global State and Logger
initial_state = {}
main_logger = setup_logger('main_app', logging.DEBUG)

# 2. Configure Slack Client
initial_state = create_slack_config(initial_state)

# Ensure message_statuses deque is initialized in state
if 'message_statuses' not in initial_state:
    initial_state['message_statuses'] = deque()

# 3. Simulate Trade Alerts and Send Notifications
main_logger.info("Starting trade alert simulation...")

simulated_alerts_data = [
    {'symbol': 'BTC', 'price': 65000.00, 'quantity': 0.5, 'action': 'BUY'},
    {'symbol': 'ETH', 'price': 3200.00, 'quantity': 2.0, 'action': 'SELL'},
    {'symbol': 'ADA', 'price': 0.45, 'quantity': 1000, 'action': 'BUY'},
    {'symbol': 'SOL', 'price': 150.00, 'quantity': 10, 'action': 'BUY'},
    {'symbol': 'XRP', 'price': 0.52, 'quantity': 500, 'action': 'SELL'}
]

# Introduce some failures for demonstration (e.g., by temporarily invalidating token)
# For a real demonstration, you might want to manually make the token invalid for some calls
# For simulation, we'll just log an error without actually trying to send for certain alerts

for i, alert_data in enumerate(simulated_alerts_data):
    alert = generate_trade_alert(initial_state, **alert_data)
    slack_message = format_slack_message(initial_state, alert)

    # Simulate a failure for the 3rd alert
    if i == 2 and initial_state['slack_client'] is not None: # Only if client is actually present
        main_logger.warning("Simulating a temporary Slack API error for demonstration...")
        original_token = initial_state['slack_client'].token
        initial_state['slack_client'].token = "invalid_token_for_demo" # Temporarily invalidate
        try:
            initial_state = send_slack_message(initial_state, slack_message)
        except SlackApiError as e:
            main_logger.error(f"Simulated error caught: {e.response['error']}")
            # Manually append a failed status if tenacity reraises after exhaustion
            initial_state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': e.response['error']})
        finally:
            initial_state['slack_client'].token = original_token # Restore token
    elif i == 4 and initial_state['slack_client'] is not None: # Another simulated failure
         main_logger.warning("Simulating another temporary network issue for demonstration...")
         # Simulate a network error by raising an arbitrary exception, or by a very short timeout
         if random.random() < 0.7: # High chance of failure for demo
            try:
                # Override send_slack_message's retry for this specific call to show immediate failure
                # In a real scenario, tenacity would retry. Here, we force a single attempt failure.
                temp_client = WebClient(token="invalid_token")
                temp_client.chat_postMessage(channel=initial_state['slack_channel_id'], text=slack_message)
            except Exception as e:
                main_logger.error(f"Forced error during send: {e}")
                initial_state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': "Simulated network error"})
         else:
            initial_state = send_slack_message(initial_state, slack_message)
    else:
        try:
            initial_state = send_slack_message(initial_state, slack_message)
        except Exception as e:
            main_logger.error(f"Failed to send message after retries: {e}")
            # Tenacity will reraise if all retries fail. This catches the final reraise.
            initial_state['message_statuses'].append({'timestamp': datetime.now(), 'status': 'failed', 'reason': str(e)})

    time.sleep(random.uniform(0.5, 1.5)) # Simulate processing time

main_logger.info("Trade alert simulation complete.")

# 4. Summarize and Display Results
summary_df = summarize_message_statuses(initial_state)
display(summary_df)
2026-06-11 11:50:20,787 - main_app - INFO - Logger 'main_app' initialized with level DEBUG.
INFO:main_app:Logger 'main_app' initialized with level DEBUG.
2026-06-11 11:50:20,792 - main_app - INFO - Starting trade alert simulation...
INFO:main_app:Starting trade alert simulation...
2026-06-11 11:50:22,900 - main_app - WARNING - Simulating a temporary Slack API error for demonstration...
WARNING:main_app:Simulating a temporary Slack API error for demonstration...
WARNING:slack_sender:Slack API error encountered, retrying: invalid_auth
WARNING:slack_sender:Slack API error encountered, retrying: invalid_auth
WARNING:slack_sender:Slack API error encountered, retrying: invalid_auth
WARNING:slack_sender:Slack API error encountered, retrying: invalid_auth
WARNING:slack_sender:Slack API error encountered, retrying: invalid_auth
2026-06-11 11:50:43,360 - main_app - ERROR - Simulated error caught: invalid_auth
ERROR:main_app:Simulated error caught: invalid_auth
2026-06-11 11:50:45,180 - main_app - WARNING - Simulating another temporary network issue for demonstration...
WARNING:main_app:Simulating another temporary network issue for demonstration...
2026-06-11 11:50:45,280 - main_app - ERROR - Forced error during send: The request to the Slack API failed. (url: https://slack.com/api/chat.postMessage)
The server responded with: {'ok': False, 'error': 'invalid_auth'}
ERROR:main_app:Forced error during send: The request to the Slack API failed. (url: https://slack.com/api/chat.postMessage)
The server responded with: {'ok': False, 'error': 'invalid_auth'}
2026-06-11 11:50:45,935 - main_app - INFO - Trade alert simulation complete.
INFO:main_app:Trade alert simulation complete.
Metric Value
0 Total Messages 5
1 Successful Messages 3
2 Failed Messages 2
3 Success Rate (%) 60.00

Visualization of Message Delivery Status

This plot shows the distribution of successful versus failed message deliveries during the simulation. It helps to quickly understand the reliability of the notification system.

[50]
if 'message_statuses' in initial_state and initial_state['message_statuses']:
    df_plot = pd.DataFrame(list(initial_state['message_statuses']))

    plt.figure(figsize=(8, 6))
    sns.countplot(x='status', data=df_plot, palette='viridis')
    plt.title('Distribution of Slack Message Delivery Status')
    plt.xlabel('Delivery Status')
    plt.ylabel('Number of Messages')
    plt.grid(axis='y', linestyle='--', alpha=0.7)
    plt.show()
else:
    main_logger.info("No message statuses available to plot.")
/tmp/ipykernel_5117/1390445542.py:5: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.countplot(x='status', data=df_plot, palette='viridis')
cell output

Production Considerations

When deploying a notification system like this in a production environment, several best practices should be followed to ensure reliability, security, and maintainability. This table outlines key considerations.

AspectBest PracticeRationale
Environment VariablesStore sensitive information (API keys, tokens) as environment variables.Prevents hardcoding credentials, enhances security.
LoggingImplement comprehensive logging with appropriate levels.Essential for monitoring, debugging, and auditing system behavior.
Error HandlingUse try/except blocks for all API calls and critical operations.Gracefully handles failures, prevents crashes, and provides feedback.
Retries & BackoffImplement exponential backoff with jitter for transient failures.Improves resilience against temporary network issues or API rate limits.
Rate LimitingRespect API rate limits; use Slack's x-ratelimit headers if available.Prevents IP blocking and ensures fair usage of the API.
Asynchronous SendingFor high-volume alerts, use asynchronous message sending.Avoids blocking the main application thread, improving performance.
Message QueuesIntegrate with message queues (e.g., Kafka, RabbitMQ) for durability.Ensures messages are not lost if the sender fails, handles spikes in volume.
Alert PrioritizationClassify alerts (e.g., critical, warning, info) and route accordingly.Ensures important alerts get immediate attention.
Monitoring & AlarmsMonitor notification system health and set up alerts for failures.Proactive identification of issues before they impact operations.
Configuration ManagementCentralize configuration (e.g., channel IDs, alert thresholds).Simplifies management and updates across environments.
TestingThoroughly test notification logic, especially error paths and retries.Verifies system behavior under various conditions, including failures.
Message ContentMake messages concise, clear, and actionable. Include relevant context.Ensures recipients understand the alert and can act quickly.

Conclusion

This notebook has provided a comprehensive guide to building a Slack notification system for trade alerts. We've covered:

  • Setting up the development environment with necessary dependencies.
  • Initializing a robust Slack client and loading configurations securely.
  • Implementing core functions for generating, formatting, and sending messages to Slack with built-in retry logic and exponential backoff.
  • Simulating trade alerts and demonstrating the end-to-end flow.
  • Visualizing message delivery statuses to assess system reliability.
  • Outlining critical production considerations for a resilient and secure notification service.

By following these patterns, you can create a reliable and scalable alert system to keep stakeholders informed of critical events in real-time.

Notification Slack · BitPredict