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.
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
| Concept | Description |
|---|---|
| Slack API | Programmatic interface to interact with Slack workspaces. |
| Webhooks | A method for an app to provide other applications with real-time information. |
| Bot Tokens | Used to authenticate your app's requests to the Slack API. |
| Channels | Specific chat rooms within Slack where messages are posted. |
| Rate Limiting | Restricting the number of API requests to prevent abuse. |
| Retry Mechanism | Reattempting failed operations, often with exponential backoff. |
| Logging | Recording events and activities for monitoring and debugging. |
| Trade Alerts | Notifications 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.
pip install slack_sdk python-dotenv tenacity pandas matplotlib seaborn numpyRequirement 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.
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_typeCore 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.
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.
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.
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.
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 bygenerate_trade_alert.
Returns:
str: A formatted message string for Slack.
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 themessage_statusesdeque.
Returns:
pandas.DataFrame: A DataFrame summarizing the message sending performance.
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.
# 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.
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')
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.
| Aspect | Best Practice | Rationale |
|---|---|---|
| Environment Variables | Store sensitive information (API keys, tokens) as environment variables. | Prevents hardcoding credentials, enhances security. |
| Logging | Implement comprehensive logging with appropriate levels. | Essential for monitoring, debugging, and auditing system behavior. |
| Error Handling | Use try/except blocks for all API calls and critical operations. | Gracefully handles failures, prevents crashes, and provides feedback. |
| Retries & Backoff | Implement exponential backoff with jitter for transient failures. | Improves resilience against temporary network issues or API rate limits. |
| Rate Limiting | Respect API rate limits; use Slack's x-ratelimit headers if available. | Prevents IP blocking and ensures fair usage of the API. |
| Asynchronous Sending | For high-volume alerts, use asynchronous message sending. | Avoids blocking the main application thread, improving performance. |
| Message Queues | Integrate with message queues (e.g., Kafka, RabbitMQ) for durability. | Ensures messages are not lost if the sender fails, handles spikes in volume. |
| Alert Prioritization | Classify alerts (e.g., critical, warning, info) and route accordingly. | Ensures important alerts get immediate attention. |
| Monitoring & Alarms | Monitor notification system health and set up alerts for failures. | Proactive identification of issues before they impact operations. |
| Configuration Management | Centralize configuration (e.g., channel IDs, alert thresholds). | Simplifies management and updates across environments. |
| Testing | Thoroughly test notification logic, especially error paths and retries. | Verifies system behavior under various conditions, including failures. |
| Message Content | Make 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.