Alerts·Alert Type Implementations·Intermediate

Price Level Alert

Build configurable price level alert triggers that notify when the market price reaches user-defined technical analysis levels, psychological round-number price levels, or key support and resistance zones with configurable alert cooldown periods to prevent notification spam during level retests.

alertsnotifications

Notifications & Alerts: Alert when Price Hits Key Level

This notebook demonstrates how to build a system for setting and managing price alerts. The core idea is to continuously monitor a financial asset's price and trigger a notification when it crosses a predefined key level. This is a fundamental concept in algorithmic trading and financial monitoring.

Key Concepts

ConceptDescription
Price MonitoringContinuously observing the current price of an asset.
Key LevelsPredefined price thresholds (e.g., support/resistance, psychological levels) where an alert should be triggered.
Alert ConditionThe logic that determines when a price has crossed a key level (e.g., price > upper_limit, price < lower_limit).
Notification SystemThe mechanism used to inform the user when an alert is triggered (e.g., print to console, email, SMS).
State ManagementKeeping track of the system's current status, including active alerts, last observed prices, and notification history.
Debouncing/ThrottlingPreventing excessive notifications by only sending an alert once per trigger event or within a certain time window.
Error HandlingGracefully managing issues like API call failures, network interruptions, or invalid price data.
Simulated DataUsing generated data to mimic real-world price movements for demonstration and testing without relying on live APIs.

Dependency Installation

We will install necessary libraries for data manipulation, plotting, and logging. tqdm is included for progress bars.

[57]
# Install necessary libraries
!pip install pandas matplotlib seaborn tqdm
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: tqdm in /usr/local/lib/python3.12/dist-packages (4.67.3)
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: 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 required Python libraries. Standard libraries are imported first, followed by third-party libraries.

[58]
import collections
import datetime
import logging
import math
import random
import time
from typing import Dict, List, Any

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from tqdm.notebook import tqdm

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

Core Functions

This section defines the core functions for our price alert system. Each function is presented in its own block, along with a detailed markdown header explaining its purpose, algorithm, parameters, and return values.

Function Name: create_alert_system_state

This function initializes the state dictionary for our alert system. It sets up structures to hold active alerts, a history of triggered alerts, and configurations for simulated price data generation. Using a dictionary allows flexible state management without classes.

Parameters:

  • asset_name (str): The name of the asset to be monitored (e.g., 'AAPL', 'BTC/USD').
  • initial_price (float): The starting price for simulated data.
  • volatility (float): The magnitude of price fluctuations in simulated data.
  • drift (float): The general upward or downward trend in simulated data.

Returns:

  • (Dict[str, Any]): An initialized state dictionary for the alert system.
[59]
def create_alert_system_state(
    asset_name: str,
    initial_price: float,
    volatility: float = 0.01,
    drift: float = 0.0001
) -> Dict[str, Any]:
    """
    Initializes the state dictionary with expanded history capacity.
    """
    logging.info(f"Initializing alert system state for {asset_name}")
    state = {
        'asset_name': asset_name,
        'current_price': initial_price,
        'alerts': {},
        'triggered_alerts_history': collections.deque(maxlen=5000), # Increased capacity
        'simulation_params': {
            'initial_price': initial_price,
            'volatility': volatility,
            'drift': drift,
            'current_time': datetime.datetime.now()
        },
        'price_history': collections.deque(maxlen=5000) # Increased capacity to match simulation cycles
    }
    return state

Function Name: add_alert

This function adds a new price alert to the system's state. An alert is defined by a unique ID, a target price, and a condition (e.g., 'above', 'below'). It also includes a cooldown_period_seconds to prevent immediate re-triggering of the same alert. The alert is stored as a dictionary within the alerts section of the main state.

Parameters:

  • state (Dict[str, Any]): The current alert system state.
  • alert_id (str): A unique identifier for the alert.
  • target_price (float): The price level at which the alert should trigger.
  • condition (str): The trigger condition, either 'above' or 'below'.
  • cooldown_period_seconds (int, optional): The minimum time in seconds before this alert can re-trigger, defaults to 300 (5 minutes).

Returns:

  • (Dict[str, Any]): The updated state dictionary with the new alert added.

Algorithm:

  1. Validate the condition parameter.
  2. Create an alert dictionary including the target_price, condition, cooldown_period_seconds, and last_triggered_time (initialized to None).
  3. Add the alert to the state['alerts'] dictionary using alert_id as the key.
[60]
def add_alert(
    state: Dict[str, Any],
    alert_id: str,
    target_price: float,
    condition: str,
    cooldown_period_seconds: int = 300
) -> Dict[str, Any]:
    """
    Adds a new price alert to the system.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.
    alert_id : str
        A unique identifier for the alert.
    target_price : float
        The price level at which the alert should trigger.
    condition : str
        The trigger condition: 'above' or 'below'.
    cooldown_period_seconds : int, optional
        Minimum time in seconds before the alert can re-trigger, defaults to 300.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary with the new alert.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> state = add_alert(state, 'test_alert_1', 105.0, 'above')
    >>> assert 'test_alert_1' in state['alerts']
    """
    if condition not in ['above', 'below']:
        logging.error(f"Invalid condition '{condition}' for alert_id '{alert_id}'. Must be 'above' or 'below'.")
        raise ValueError("Condition must be 'above' or 'below'.")

    if alert_id in state['alerts']:
        logging.warning(f"Alert with ID '{alert_id}' already exists. Overwriting.")

    state['alerts'][alert_id] = {
        'target_price': target_price,
        'condition': condition,
        'cooldown_period_seconds': cooldown_period_seconds,
        'last_triggered_time': None
    }
    logging.info(f"Alert '{alert_id}' added: target={target_price}, condition={condition}.")
    return state

Function Name: remove_alert

This function removes an existing price alert from the system's state based on its alert_id. If the alert does not exist, a warning is logged.

Parameters:

  • state (Dict[str, Any]): The current alert system state.
  • alert_id (str): The unique identifier of the alert to remove.

Returns:

  • (Dict[str, Any]): The updated state dictionary with the alert removed.

Algorithm:

  1. Check if alert_id exists in state['alerts'].
  2. If it exists, remove the entry from the dictionary.
  3. If it does not exist, log a warning.
[61]
def remove_alert(
    state: Dict[str, Any],
    alert_id: str
) -> Dict[str, Any]:
    """
    Removes an existing price alert from the system.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.
    alert_id : str
        The unique identifier of the alert to remove.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary with the alert removed.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> state = add_alert(state, 'test_alert_1', 105.0, 'above')
    >>> state = remove_alert(state, 'test_alert_1')
    >>> assert 'test_alert_1' not in state['alerts']
    """
    if alert_id in state['alerts']:
        del state['alerts'][alert_id]
        logging.info(f"Alert '{alert_id}' removed successfully.")
    else:
        logging.warning(f"Attempted to remove non-existent alert with ID '{alert_id}'.")
    return state

Function Name: simulate_price_data

This function simulates price movements based on a geometric Brownian motion model. It takes the current state and updates the current_price based on a drift and volatility parameter, along with a random component. This allows us to test the alert system without needing a live data feed.

Parameters:

  • state (Dict[str, Any]): The current alert system state.

Returns:

  • (Dict[str, Any]): The updated state dictionary with the new current_price.

Algorithm:

  1. Retrieve simulation parameters (current_price, volatility, drift) from state['simulation_params'].
  2. Calculate the random price change using a normal distribution for the stochastic component.
  3. Update current_price by applying drift and the random change.
  4. Ensure the price does not go below a reasonable minimum (e.g., 0.01).
  5. Update state['current_price'] and state['simulation_params']['current_time'].
  6. Append the new price and time to state['price_history'].
[62]
def simulate_price_data(
    state: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Simulates a new price for the asset based on current parameters.

    Uses a simplified geometric Brownian motion model for price simulation.
    Updates the 'current_price' in the state.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary with the new current_price.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0, volatility=0.01, drift=0.0001)
    >>> original_price = state['current_price']
    >>> state = simulate_price_data(state)
    >>> assert state['current_price'] != original_price
    """
    params = state['simulation_params']
    current_price = state['current_price']

    dt = 1 / 252.0  # Simulate daily steps, scaled for shorter intervals
    price_change = current_price * (params['drift'] * dt + params['volatility'] * math.sqrt(dt) * random.gauss(0, 1))
    new_price = current_price + price_change

    # Ensure price doesn't go negative or too low
    state['current_price'] = max(0.01, new_price)
    state['simulation_params']['current_time'] = datetime.datetime.now()
    state['price_history'].append((state['simulation_params']['current_time'], state['current_price']))
    logging.debug(f"Simulated new price: {state['current_price']:.2f}")
    return state

Function Name: check_alerts

This function iterates through all active alerts in the system's state and checks if any alert conditions are met by the current price. It also enforces a cooldown_period_seconds to prevent an alert from triggering too frequently. When an alert triggers, it's recorded in the triggered_alerts_history.

Parameters:

  • state (Dict[str, Any]): The current alert system state.

Returns:

  • (Dict[str, Any]): The updated state dictionary, with last_triggered_time updated for triggered alerts and new entries in triggered_alerts_history.

Algorithm:

  1. Get the current_price from the state.
  2. Iterate through each alert_id and its details in state['alerts'].
  3. For each alert, check if it's currently in its cooldown_period_seconds.
  4. If not in cooldown, evaluate the condition ('above' or 'below') against the target_price and current_price.
  5. If the condition is met: a. Update the last_triggered_time for the alert. b. Record the triggered event in state['triggered_alerts_history']. c. Log an info message about the triggered alert.
  6. Return the updated state.
[63]
def check_alerts(
    state: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Checks all active alerts against the current price.

    Triggers alerts if conditions are met and cooldown period has passed.
    Updates the 'last_triggered_time' for triggered alerts.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> state = add_alert(state, 'alert_high', 101.0, 'above')
    >>> state = add_alert(state, 'alert_low', 99.0, 'below')
    >>> state['current_price'] = 101.5 # Should trigger alert_high
    >>> state = check_alerts(state)
    >>> assert len(state['triggered_alerts_history']) == 1
    """
    current_price = state['current_price']
    current_time = state['simulation_params']['current_time']
    triggered_this_cycle = []

    for alert_id, alert_details in state['alerts'].items():
        target_price = alert_details['target_price']
        condition = alert_details['condition']
        last_triggered_time = alert_details['last_triggered_time']
        cooldown_period_seconds = alert_details['cooldown_period_seconds']

        can_trigger = True
        if last_triggered_time:
            time_since_last_trigger = (current_time - last_triggered_time).total_seconds()
            if time_since_last_trigger < cooldown_period_seconds:
                can_trigger = False
                logging.debug(f"Alert '{alert_id}' on cooldown. Remaining: {cooldown_period_seconds - time_since_last_trigger:.0f}s")

        is_triggered = False
        if can_trigger:
            if condition == 'above' and current_price > target_price:
                is_triggered = True
            elif condition == 'below' and current_price < target_price:
                is_triggered = True

        if is_triggered:
            state['alerts'][alert_id]['last_triggered_time'] = current_time
            event = {
                'timestamp': current_time,
                'alert_id': alert_id,
                'condition': condition,
                'target_price': target_price,
                'current_price': current_price,
                'asset': state['asset_name']
            }
            state['triggered_alerts_history'].append(event)
            triggered_this_cycle.append(alert_id)
            logging.info(f"Alert '{alert_id}' triggered! {state['asset_name']} price {current_price:.2f} {condition} {target_price:.2f}")

    return state

Function Name: send_notification

This function simulates sending a notification when an alert is triggered. In a real-world scenario, this would integrate with external services like email, SMS, or push notifications. For this demonstration, it simply prints a message to the console. It includes a random jitter for any timing/backoff mechanisms, though not directly used for backoff here, it's a good practice to include.

Parameters:

  • alert_event (Dict[str, Any]): A dictionary containing details of the triggered alert event.

Returns:

  • (bool): True if the notification was 'sent', False otherwise (simulated).

Algorithm:

  1. Extract relevant details from alert_event.
  2. Print a formatted notification message to the console.
  3. Introduce a small random delay to simulate network latency or processing time.
  4. Return True to indicate simulated success.
[64]
def send_notification(
    alert_event: Dict[str, Any]
) -> bool:
    """
    Simulates sending a notification for a triggered alert.

    In a real system, this would integrate with email, SMS, or other notification services.

    Parameters
    ----------
    alert_event : Dict[str, Any]
        A dictionary containing details of the triggered alert event.

    Returns
    -------
    bool
        True if notification was 'sent', False otherwise (simulated).

    Examples
    --------
    >>> event = {
    ...     'timestamp': datetime.datetime.now(),
    ...     'alert_id': 'test_alert_1',
    ...     'condition': 'above',
    ...     'target_price': 105.0,
    ...     'current_price': 105.5,
    ...     'asset': 'TEST'
    ... }
    >>> sent = send_notification(event)
    >>> assert sent is True # Assuming success
    """
    asset = alert_event.get('asset', 'Unknown Asset')
    alert_id = alert_event.get('alert_id', 'Unknown Alert')
    current_price = alert_event.get('current_price', 0.0)
    target_price = alert_event.get('target_price', 0.0)
    condition = alert_event.get('condition', 'N/A')
    timestamp = alert_event.get('timestamp', datetime.datetime.now()).strftime('%Y-%m-%d %H:%M:%S')

    notification_message = (
        f"ALERT! [{timestamp}] {asset} - '{alert_id}' triggered! "
        f"Price {current_price:.2f} is {condition} {target_price:.2f}."
    )
    print(notification_message)
    logging.info(f"Notification simulated for alert '{alert_id}'.")

    # Add random jitter to simulate potential network delays for real notifications
    time.sleep(random.uniform(0.01, 0.1))
    return True

Function Name: run_monitoring_cycle

This function orchestrates a single monitoring cycle. It fetches (simulates) the latest price, checks all active alerts, and sends notifications for any triggered alerts. It also includes basic error handling with retries using exponential backoff for price fetching, though for simulation, it's simplified.

Parameters:

  • state (Dict[str, Any]): The current alert system state.
  • max_retries (int, optional): Maximum number of retries for price fetching, defaults to 3.
  • base_delay (float, optional): Base delay in seconds for exponential backoff, defaults to 0.1.

Returns:

  • (Dict[str, Any]): The updated state dictionary after one monitoring cycle.

Algorithm:

  1. Fetch Price Data (Simulated with Retries): a. Use a try/except block to simulate price fetching errors. b. Implement exponential backoff for retries: if an error occurs, wait for base_delay * (2 ** attempt) seconds before retrying. c. Introduce random jitter to backoff delays. d. Update state['current_price'] with the new simulated price.
  2. Check Alerts: Call check_alerts(state) to evaluate all active alerts.
  3. Send Notifications: Iterate through state['triggered_alerts_history'] (specifically, any new triggers from this cycle) and call send_notification() for each.
  4. Return the updated state.
[65]
def run_monitoring_cycle(
    state: Dict[str, Any],
    max_retries: int = 3,
    base_delay: float = 0.1
) -> Dict[str, Any]:
    """
    Executes a single price monitoring cycle: fetches price, checks alerts, sends notifications.

    Includes simplified error handling with exponential backoff for price fetching (simulated).

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.
    max_retries : int, optional
        Maximum number of retries for price fetching, defaults to 3.
    base_delay : float, optional
        Base delay in seconds for exponential backoff, defaults to 0.1.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary after one monitoring cycle.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> state = add_alert(state, 'test_alert', 105.0, 'above')
    >>> # Running a cycle would involve price simulation and alert checks
    >>> state = run_monitoring_cycle(state)
    """
    logging.info("Starting new monitoring cycle...")

    # Simulate price data fetching with retries
    price_fetched = False
    for attempt in range(max_retries):
        try:
            # Removed: if random.random() < 0.05 and attempt == 0:
            # Removed:    raise ConnectionError("Simulated network error during price fetch.")

            state = simulate_price_data(state)
            price_fetched = True
            break
        except ConnectionError as e:
            delay = base_delay * (2 ** attempt) + random.uniform(0, 0.1) # Add jitter
            logging.warning(f"Attempt {attempt + 1}/{max_retries}: Failed to fetch price: {e}. Retrying in {delay:.2f}s...")
            time.sleep(delay)
        except Exception as e:
            logging.error(f"Unexpected error during price simulation: {e}")
            break

    if not price_fetched:
        logging.error("Failed to fetch price data after multiple retries. Skipping alert check for this cycle.")
        return state # Skip alert check if price couldn't be fetched

    # Check alerts
    initial_history_len = len(state['triggered_alerts_history'])
    state = check_alerts(state)
    # Convert deque to list before slicing to get newly triggered events
    newly_triggered_events = list(state['triggered_alerts_history'])[initial_history_len:]

    # Send notifications for newly triggered alerts
    for event in newly_triggered_events:
        send_notification(event)

    logging.info(f"Monitoring cycle completed. Current price: {state['current_price']:.2f}")
    return state

Function Name: summarize_alerts

This function provides a summary of all triggered alerts. It converts the triggered_alerts_history (a deque) into a pandas DataFrame for easy analysis and display. This allows users to quickly review alert activity, including timestamps, triggered prices, and conditions.

Parameters:

  • state (Dict[str, Any]): The current alert system state.

Returns:

  • (pd.DataFrame): A DataFrame containing the history of triggered alerts.

Algorithm:

  1. Convert the state['triggered_alerts_history'] deque to a list of dictionaries.
  2. Create a pandas DataFrame from this list.
  3. Ensure the 'timestamp' column is of datetime type and set it as the index.
  4. Return the DataFrame. If no alerts, return an empty DataFrame.
[66]
def summarize_alerts(
    state: Dict[str, Any]
) -> pd.DataFrame:
    """
    Provides a summary of all triggered alerts in a pandas DataFrame.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.

    Returns
    -------
    pd.DataFrame
        A DataFrame containing the history of triggered alerts.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> state = add_alert(state, 'test_alert', 100.5, 'above')
    >>> state['current_price'] = 101.0
    >>> state['simulation_params']['current_time'] = datetime.datetime.now()
    >>> state = check_alerts(state)
    >>> df_summary = summarize_alerts(state)
    >>> assert not df_summary.empty
    """
    if not state['triggered_alerts_history']:
        logging.info("No alerts have been triggered yet.")
        return pd.DataFrame(
            columns=['timestamp', 'alert_id', 'condition', 'target_price', 'current_price', 'asset']
        ).set_index('timestamp')

    df = pd.DataFrame(list(state['triggered_alerts_history']))
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df = df.set_index('timestamp').sort_index()
    logging.info("Generated triggered alerts summary DataFrame.")
    return df

Function Name: get_price_history_df

This function retrieves the recorded price history from the system's state and returns it as a pandas DataFrame. This is useful for visualizing price movements over time.

Parameters:

  • state (Dict[str, Any]): The current alert system state.

Returns:

  • (pd.DataFrame): A DataFrame with 'timestamp' and 'price' columns, representing the price history.

Algorithm:

  1. Convert the state['price_history'] deque to a list of tuples.
  2. Create a pandas DataFrame from this list, naming the columns 'timestamp' and 'price'.
  3. Ensure the 'timestamp' column is of datetime type and set it as the index.
  4. Return the DataFrame. If no price history, return an empty DataFrame.
[67]
def get_price_history_df(
    state: Dict[str, Any]
) -> pd.DataFrame:
    """
    Retrieves the recorded price history as a pandas DataFrame.

    Parameters
    ----------
    state : Dict[str, Any]
        The current alert system state.

    Returns
    -------
    pd.DataFrame
        A DataFrame with 'timestamp' and 'price' columns.

    Examples
    --------
    >>> state = create_alert_system_state('TEST', 100.0)
    >>> for _ in range(5): state = simulate_price_data(state)
    >>> df_history = get_price_history_df(state)
    >>> assert not df_history.empty
    """
    if not state['price_history']:
        logging.info("No price history recorded yet.")
        return pd.DataFrame(columns=['timestamp', 'price']).set_index('timestamp')

    df = pd.DataFrame(list(state['price_history']), columns=['timestamp', 'price'])
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df = df.set_index('timestamp').sort_index()
    logging.info("Generated price history DataFrame.")
    return df

Demonstration and Visualization

This section demonstrates the complete workflow of the price alert system. We will initialize the state, add several alerts, simulate price movements over a period, and then visualize the price history along with the triggered alerts.

Step 1: Initialize the Alert System State

We'll create an initial state for a hypothetical asset, 'XYZ/USD', with a starting price and some simulation parameters.

[68]
initial_asset_price = 96000.0
# Reset state for BTC/USDT with adjusted BTC price levels
alert_system_state = create_alert_system_state(
    asset_name='BTC/USDT',
    initial_price=initial_asset_price,
    volatility=0.005,
    drift=0.0
)

print(f"Initial state created for {alert_system_state['asset_name']} at ${alert_system_state['current_price']:,.2f}")
Initial state created for BTC/USDT at $96,000.00

Step 2: Add Multiple Alerts

We will add various alerts with different target prices and conditions to see how the system handles them.

[73]
# Remove obsolete alerts from the previous low-price simulation
old_alerts = ['Strong_Resistance', 'Major_Support', 'Minor_Sell', 'Minor_Buy', 'Test_Cooldown']
for alert_id in old_alerts:
    if alert_id in alert_system_state['alerts']:
        alert_system_state = remove_alert(alert_system_state, alert_id)

print(f"Cleaned up state. Current active alerts: {list(alert_system_state['alerts'].keys())}")
Cleaned up state. Current active alerts: ['BTC_Resistance', 'BTC_Support']

Step 3: Simulate Price Monitoring Over Time

We will run the run_monitoring_cycle function many times to simulate continuous price monitoring. This will generate price data and trigger alerts as conditions are met.

[74]
# Add a comprehensive set of BTC-specific alerts
alert_system_state = add_alert(alert_system_state, 'Strong_Resistance', 96500.0, 'above', cooldown_period_seconds=10)
alert_system_state = add_alert(alert_system_state, 'Minor_Sell', 96300.0, 'above', cooldown_period_seconds=10)
alert_system_state = add_alert(alert_system_state, 'Minor_Buy', 95700.0, 'below', cooldown_period_seconds=10)
alert_system_state = add_alert(alert_system_state, 'Major_Support', 95500.0, 'below', cooldown_period_seconds=10)
alert_system_state = add_alert(alert_system_state, 'BTC_Target', 96000.0, 'above', cooldown_period_seconds=30)

num_cycles = 300
print(f"Simulating {num_cycles} monitoring cycles for BTC/USDT with full alert set...")

for i in range(num_cycles):
    alert_system_state = run_monitoring_cycle(alert_system_state)

print("Simulation complete.")
Simulating 300 monitoring cycles for BTC/USDT with full alert set...
ALERT! [2026-06-10 05:43:13] BTC/USDT - 'BTC_Target' triggered! Price 96159.16 is above 96000.00.
ALERT! [2026-06-10 05:43:13] BTC/USDT - 'BTC_Resistance' triggered! Price 96204.85 is above 96200.00.
ALERT! [2026-06-10 05:43:13] BTC/USDT - 'Minor_Sell' triggered! Price 96303.32 is above 96300.00.
ALERT! [2026-06-10 05:43:13] BTC/USDT - 'Strong_Resistance' triggered! Price 96538.36 is above 96500.00.
Simulation complete.

Step 4: Summarize Triggered Alerts

After the simulation, we'll get a summary of all alerts that were triggered using the summarize_alerts function.

[71]
# Get summary of triggered alerts from the full 5000-cycle run
alert_summary_df = summarize_alerts(alert_system_state)

if not alert_summary_df.empty:
    print("\n--- Triggered Alerts Summary ---")
    display(alert_summary_df.head())
    print(f"Total unique alerts triggered: {len(alert_summary_df['alert_id'].unique())}")
    print(f"Total alert triggers: {len(alert_summary_df)}")
else:
    print("\nNo alerts were triggered during this full simulation.")

--- Triggered Alerts Summary ---
alert_id condition target_price current_price asset
timestamp
2026-06-10 05:41:26.228379 Strong_Resistance above 452.0 96022.631511 BTC/USDT
2026-06-10 05:41:26.228379 Minor_Sell above 451.0 96022.631511 BTC/USDT
2026-06-10 05:41:26.228379 Test_Cooldown above 453.0 96022.631511 BTC/USDT
2026-06-10 05:41:26.486067 BTC_Resistance above 96200.0 96211.576529 BTC/USDT
Total unique alerts triggered: 4
Total alert triggers: 4

Step 5: Visualize Price History and Alerts

Now we will plot the simulated price data and overlay the points where alerts were triggered. This provides a clear visual understanding of the system's performance.

[75]
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Prepare BTC Data
price_df = get_price_history_df(alert_system_state).reset_index()
price_df['cycle'] = price_df.index
alert_summary = summarize_alerts(alert_system_state)

plt.figure(figsize=(14, 8))

# Plot Price
sns.lineplot(x='cycle', y='price', data=price_df, label='BTC Price', color='orange', linewidth=2)

# Plot all active alert levels dynamically
colors = {'Strong_Resistance': 'red', 'Minor_Sell': 'salmon', 'Minor_Buy': 'lightgreen', 'Major_Support': 'darkgreen', 'BTC_Target': 'blue'}

for alert_id, details in alert_system_state['alerts'].items():
    plt.axhline(y=details['target_price'],
                color=colors.get(alert_id, 'gray'),
                linestyle='--', alpha=0.6,
                label=f"{alert_id} (${details['target_price']:,.0f})")

# Overlay Triggers
if not alert_summary.empty:
    # Filter to only show triggers belonging to the current BTC simulation (high price range)
    btc_triggers = alert_summary[alert_summary['current_price'] > 1000].reset_index()

    triggers_with_cycle = pd.merge_asof(
        btc_triggers.sort_values('timestamp'),
        price_df[['timestamp', 'cycle']].sort_values('timestamp'),
        on='timestamp'
    )

    sns.scatterplot(x='cycle', y='current_price', hue='alert_id', data=triggers_with_cycle,
                    s=150, marker='X', zorder=5, palette='bright')

plt.title('BTC/USDT Multi-Level Alert Simulation')
plt.xlabel('Cycle Index')
plt.ylabel('Price (USDT)')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
cell output

Production Considerations

Deploying a real-time alerting system requires careful consideration of various factors to ensure reliability, scalability, and maintainability. Below is a table outlining key best practices:

AspectBest Practice
Data SourcesUse reliable, low-latency data feeds (e.g., WebSocket APIs). Implement robust error handling and retry mechanisms with exponential backoff for API calls.
ScalabilityDesign for horizontal scaling. Use message queues (e.g., Kafka, RabbitMQ) for asynchronous processing of price updates and notifications. Consider microservices architecture.
Notification RedundancyImplement multiple notification channels (e.g., email, SMS, Slack, PagerDuty) with failovers. Ensure critical alerts reach the user even if one channel fails.
Monitoring & LoggingImplement comprehensive logging (structured logs). Monitor system health, latency, error rates, and alert trigger frequency. Use metrics dashboards (e.g., Prometheus, Grafana).
Alert Debouncing/ThrottlingCrucial for preventing alert fatigue. Implement intelligent cooldown periods per alert, per asset, or globally. Consider alert aggregation logic (e.g., send one summary email instead of 10 individual emails in a minute).
State PersistenceFor long-running systems, persist alert configurations and system state to a database (e.g., PostgreSQL, Redis) to survive restarts or failures.
SecuritySecure API keys, notification credentials, and sensitive data. Use encrypted communication (HTTPS/TLS). Implement access control for alert management.
TestingThoroughly test alert conditions, cooldowns, edge cases (e.g., price gaps), and error handling. Implement unit tests, integration tests, and end-to-end tests.
Configuration ManagementExternalize configurations (e.g., target prices, conditions, notification settings) using environment variables, configuration files, or a dedicated configuration service. Avoid hardcoding.
Performance OptimizationOptimize price processing logic. Avoid expensive computations in the critical path. Use efficient data structures (e.g., deque for rolling windows, efficient hash maps for alerts).
Regulatory ComplianceIf dealing with financial data, ensure compliance with relevant regulations (e.g., data privacy, data retention policies).
User InterfaceProvide a clear interface for users to define, modify, and view their alerts. This could be a web application, mobile app, or even a simple command-line tool.

Conclusion

This notebook has provided a foundational framework for building a price alert system. We've covered:

  • State Management: Using a dictionary to maintain the system's operational parameters, active alerts, and historical data.
  • Core Logic: Functions for creating and managing alerts, simulating price data, checking alert conditions with cooldowns, and simulating notifications.
  • Robustness: Incorporating logging, type hints, docstrings, and a basic error handling mechanism with exponential backoff for simulated data fetching.
  • Demonstration: A step-by-step simulation demonstrating how the system tracks price movements and triggers alerts.
  • Visualization: Plotting the price history alongside triggered alerts for clear analysis.
  • Production Considerations: Discussing best practices for deploying such a system in a real-world, high-stakes environment.

This system can be extended by integrating with live data APIs, external notification services, persistent storage, and more sophisticated alert conditions or strategies.