top of page

Get auto trading tips and tricks from our experts. Join our newsletter now

Thanks for submitting!

Building an AI Trading Dashboard with Python for Futures Markets

The Ultimate Guide: Building an AI Trading Dashboard with Python for Futures Markets

 

In the fast-paced world of algorithmic trading, the gap between a simple trading idea and a robust, data-driven strategy is vast. Many traders and developers hit a wall, constrained by off-the-shelf software or overwhelmed by the complexity of building professional-grade tools. What if you could bridge that gap? What if you could create a personalized, intelligent command center that not only visualizes market data but also leverages Artificial Intelligence to test, validate, and analyze your trading strategies?


Building an AI Trading Dashboard with Python

 

This is not a far-fetched dream. This is the power you unlock when you start building an AI trading dashboard with Python.

 

Welcome to the definitive guide for aspiring quants, data-savvy traders, and Python developers looking to conquer the financial markets. Over the next 6000 words, we will embark on a detailed, end-to-end journey. We won't just scratch the surface with simplistic stock examples. We will dive deep into the world of futures trading, using professional-grade data concepts and advanced validation techniques.



Bookings Paid Product
Plan only
15min
Book Now

 

You will learn how to:

 

  • Assemble a powerful tech stack using Python, Streamlit, and essential data science libraries.

  • Source and process professional-grade trading data, understanding the critical difference between free APIs and reliable market feeds.

  • Engineer meaningful features that transform raw price data into valuable signals for your AI.

  • Build, train, and evaluate a machine learning model to predict market movements.

  • Implement advanced backtesting and validation techniques, including the industry-gold-standard Walk-Forward Analysis.

  • Tie it all together in a stunning, interactive dashboard using Streamlit, allowing you to explore your strategy's performance from every angle.

  •  

This is more than a tutorial; it's a project-based masterclass. By the end, you will have the blueprint and the code to build a tool that can give you a genuine analytical edge.

 

Part 1: The Foundation - Philosophy and Tech Stack

 

Before we write a single line of code, we must establish our philosophy and choose our tools. The "why" is just as important as the "how."

 

Why Build a Custom Trading Dashboard?

 

Commercial trading platforms like NinjaTrader or TradeStation are powerful, but they come with inherent limitations. They often operate as "black boxes," restricting you to their proprietary languages, indicators, and backtesting modules. You are confined to their ecosystem.

 

Building your own dashboard offers three transformative advantages:

 

  1. Infinite Customization: You are not limited to standard indicators. You can invent your own, integrate alternative datasets (like sentiment analysis or economic data), and implement unique risk management logic that fits your exact trading style. Your only limit is your imagination.

  2. Deep Understanding and Trust: When you build the engine, you understand every gear. You know precisely how your data is processed, how your signals are generated, and how your performance is calculated. This eliminates the "black box" problem and builds unshakable confidence in your strategy's results. There are no hidden commissions, slippage assumptions, or calculation quirks.

  3. Ownership and a Competitive Edge: Every successful quantitative fund has its own proprietary software. By building your own tools, you are taking the first step on that same path. You are creating an asset that is uniquely yours, one that can evolve with your strategies and the changing market, giving you a sustainable edge over those relying on generic, off-the-shelf solutions.

  4.  

Assembling Your High-Performance Tech Stack

 

Our philosophy is one of openness, power, and speed. The Python ecosystem provides the perfect tools to bring this philosophy to life.

 

Python: The Lingua Franca of Quantitative Finance

 

Python has become the undisputed king of data science and quantitative analysis for several key reasons:

 

  • Vast Library Ecosystem: Libraries like Pandas, NumPy, Scikit-learn, and Statsmodels provide pre-built, highly optimized tools for data manipulation, statistical analysis, and machine learning. We don't need to reinvent the wheel.

  • Ease of Use and Readability: Python's clean syntax makes it relatively easy to learn and allows you to translate complex financial ideas into code quickly. This accelerates the research and development cycle.

  • Massive Community: If you encounter a problem, chances are someone else has already solved it. The vast community support available through forums like Stack Overflow and dedicated communities is an invaluable resource.

 

Streamlit: For Rapid Interactive Dashboard Development

 

Traditionally, building a web-based dashboard required knowledge of complex frameworks like Flask or Django, along with HTML, CSS, and JavaScript. This created a massive barrier for quants and data scientists who simply wanted to visualize their results.

 

Streamlit changes everything. It's a pure Python library that allows you to create beautiful, interactive web applications with just a few lines of code.

 

  • Pure Python: No web development experience is needed. If you can write a Python script, you can build a web app.

  • Instant Interactivity: Widgets like sliders, buttons, and dropdowns are simple function calls. Streamlit handles the entire frontend and backend communication automatically.

  • Data-First Focus: Streamlit is designed for data scientists. It excels at displaying data frames, charts, and metrics, making it the perfect choice for our AI trading dashboard.

 

Pandas, NumPy, and Scikit-learn: The Data Science Trio

 

  • Pandas: This is the cornerstone of our data handling. Pandas provides the DataFrame, a powerful, Excel-like data structure for storing and manipulating time-series data. We will use it for everything from loading data to engineering features.

  • NumPy: The fundamental package for numerical computation in Python. Pandas is built on top of it. We'll use NumPy for fast mathematical operations on our data arrays.

  • Scikit-learn: The go-to library for machine learning in Python. It offers a simple, consistent API for accessing a vast range of models (from Logistic Regression to Random Forests) and evaluation tools.

 

The Importance of a Professional Data Feed

 

For this project, we are graduating from free, often unreliable data sources like yfinance. While excellent for quick analyses, they lack the precision required for serious backtesting, especially in futures markets.

 

Professional data, often sourced through a direct market data provider like Rhythmic or CQG and accessed via a broker like EdgeClear, offers:

 

  • Tick-Level Data: You see every single trade, not just the open, high, low, and close of a one-minute bar. This is critical for accurately simulating slippage and fills.

  • Reliability and Cleanliness: The data is clean, timestamped accurately, and free from the frequent errors and gaps found in free sources.

  • Continuous Contracts: Futures contracts expire. Professional feeds provide stitched, continuous back-adjusted contracts, saving you a massive data-cleaning headache and allowing for long-term backtests.

 

For our guide, we will work with a sample CSV file representing this high-quality bar data, but understanding its origin is crucial for anyone looking to eventually trade live.

 

Part 2: Sourcing and Preparing Your Trading Data

 

Garbage in, garbage out. This maxim is nowhere more true than in quantitative trading. The quality of your data and the intelligence of your features will determine the success or failure of your AI model.

 

The Lifeblood of Your AI: Structuring Your Data Pipeline

 

Let's assume you have acquired a CSV file with historical data for a futures contract, like the E-mini S&P 500 (ES). A typical file might look like this:

 

datetime,open,high,low,close,volume2023-01-03 09:30:00,4000.50,4001.25,4000.00,4001.00,15002023-01-03 09:31:00,4001.00,4002.00,4000.75,4001.75,1200

 

...

Our first step is to load this data into a Pandas DataFrame, the workhorse of our entire project.

 

Code Block 1: Loading and Preparing the Data

 

import pandas as pd

 

def load_data(filepath):

    """

    Loads trading data from a CSV file and prepares it for analysis.

   

    Args:

        filepath (str): The path to the CSV file.

       

    Returns:

        pd.DataFrame: A prepared DataFrame with a DatetimeIndex.

    """

    try:

        # Load the data

        df = pd.read_csv(filepath)

       

        # --- Data Cleaning and Preparation ---

        # Ensure column names are clean (lowercase, no spaces)

        df.columns = [col.lower().strip() for col in df.columns]

       

        # Convert the 'datetime' column to a proper datetime object

        # The format string may need to be adjusted based on your CSV

        df['datetime'] = pd.to_datetime(df['datetime'], format='%Y-%m-%d %H:%M:%S')

       

        # Set the datetime column as the index, which is best practice for time-series data

        df.set_index('datetime', inplace=True)

       

        # Check for and handle any missing values (e.g., fill forward)

        df.ffill(inplace=True)

       

        # Ensure data is sorted by time

        df.sort_index(inplace=True)

       

        print("Data loaded and prepared successfully.")

        print(f"Data shape: {df.shape}")

        print("First 5 rows:")

        print(df.head())

       

        return df

       

    except FileNotFoundError:

        print(f"Error: The file at {filepath} was not found.")

        return None

 

# --- Usage ---

# Replace 'your_data.csv' with the path to your data file

data_filepath = 'your_data.csv'

main_df = load_data(data_filepath)

 

 

 

Explanation:

 

  1. Import Pandas: We start by importing the pandas library.

  2. Robust Function: We wrap our logic in a function load_data for reusability. It includes error handling (try...except) for a missing file.

  3. Clean Column Names: A crucial but often overlooked step. Inconsistent column names (' Close' vs 'close') cause errors. We standardize them to lowercase.

  4. pd.to_datetime: This is the most important step. We convert the date strings from the CSV into true datetime objects. This unlocks all of Pandas' powerful time-series capabilities.

  5. set_index: We set the datetime column as the DataFrame's index. This makes slicing, resampling, and plotting by date incredibly efficient.

  6. ffill: This stands for "forward fill." It handles any missing data points by carrying the last known value forward. This is a simple but effective way to deal with gaps in financial data.

  7. sort_index: We ensure the data is chronologically sorted, a prerequisite for almost all time-series analysis.

 

Data Wrangling and Feature Engineering with Pandas

 

Our AI model cannot learn from raw prices alone. It needs context. We provide this context through feature engineering—the art and science of creating new, informative variables from our existing data.

 

Our goal is to create features that might have predictive power. Let's create a few classic examples:

 

  • Moving Averages (Trend): Simple Moving Averages (SMAs) help identify the underlying trend by smoothing out price volatility. A crossover between a fast and a slow SMA is a classic trend-following signal.

  • Relative Strength Index (RSI) (Momentum): RSI is a momentum oscillator that measures the speed and change of price movements. It oscillates between 0 and 100 and is typically used to identify overbought (>70) or oversold (<30) conditions.

  • Average True Range (ATR) (Volatility): ATR measures market volatility. High ATR indicates a volatile market, while low ATR suggests a quiet one. This can be crucial for setting stop-losses or profit targets.

  • Time-Based Features: Does the market behave differently on Mondays? Or in the first hour of trading? We can create features for the day of the week, hour of the day, etc., to let the AI discover these patterns.

  •  

Code Block 2: Engineering Features

 

import numpy as np

 

def engineer_features(df, short_window=20, long_window=100, rsi_period=14, atr_period=14):

    """

    Engineers a set of features for the AI model.

   

    Args:

        df (pd.DataFrame): The input DataFrame with OHLC data.

        short_window (int): The lookback period for the short SMA.

        long_window (int): The lookback period for the long SMA.

        rsi_period (int): The lookback period for the RSI.

        atr_period (int): The lookback period for the ATR.

       

    Returns:

        pd.DataFrame: The DataFrame with new feature columns.

    """

    if df is None:

        return None

       

    # --- 1. Trend Features: Moving Averages ---

    df['sma_short'] = df['close'].rolling(window=short_window).mean()

    df['sma_long'] = df['close'].rolling(window=long_window).mean()

   

    # --- 2. Momentum Feature: RSI ---

    delta = df['close'].diff()

    gain = (delta.where(delta > 0, 0)).rolling(window=rsi_period).mean()

    loss = (-delta.where(delta < 0, 0)).rolling(window=rsi_period).mean()

    rs = gain / loss

    df['rsi'] = 100 - (100 / (1 + rs))

 

    # --- 3. Volatility Feature: ATR ---

    high_low = df['high'] - df['low']

    high_close = np.abs(df['high'] - df['close'].shift())

    low_close = np.abs(df['low'] - df['close'].shift())

    ranges = pd.concat([high_low, high_close, low_close], axis=1)

    true_range = np.max(ranges, axis=1)

    df['atr'] = true_range.rolling(window=atr_period).mean()

 

    # --- 4. Time-Based Features ---

    df['day_of_week'] = df.index.dayofweek # Monday=0, Sunday=6

    df['hour_of_day'] = df.index.hour

   

    # --- 5. Price Change Features ---

    # We can create features based on returns over different periods

    df['return_1p'] = df['close'].pct_change(1) # 1-period return

    df['return_5p'] = df['close'].pct_change(5) # 5-period return

   

    # Drop rows with NaN values created by rolling windows

    df.dropna(inplace=True)

   

    print("Feature engineering complete.")

    print(f"Data shape after feature engineering: {df.shape}")

    print("Columns added:", ['sma_short', 'sma_long', 'rsi', 'atr', 'day_of_week', 'hour_of_day', 'return_1p', 'return_5p'])

   

    return df

 

# --- Usage ---

if main_df is not None:

    featured_df = engineer_features(main_df.copy()) # Use a copy to avoid modifying the original

 

 

Explanation:

 

  1. .rolling(): This is a powerful Pandas method that creates a rolling window over the data. We then chain an aggregation function like .mean() to calculate the SMA.

  2. RSI Calculation: The RSI logic is more involved but follows the standard formula. We calculate the price change (delta), separate gains and losses, and then compute the Relative Strength (rs) to get the final RSI value.

  3. ATR Calculation: Similarly, we calculate the True Range as the maximum of three values and then take a rolling mean to get the ATR.

  4. Time Features: Accessing time-based components is trivial once the index is a datetime object. We simply use .index.dayofweek and .index.hour.

  5. dropna(): Our rolling calculations (rolling, diff, pct_change) will produce NaN (Not a Number) values at the beginning of the dataset where there isn't enough lookback data. It's crucial to drop these rows before feeding the data to a machine learning model.

 

Part 3: The "AI" in AI Trading - Building the Predictive Model

 

With our features ready, it's time to build the brain of our operation. We will frame our trading problem as a classification task: given the current market data and our engineered features, can we predict if the price will go up or down in the near future?

 

Framing the Trading Problem for Machine Learning

 

First, we must define our target variable. This is the "answer" we want the AI to learn. A common approach is to define the target based on future price movement.

 

Let's define our target y:

 

  • 1 (Up): If the price n periods in the future is higher than the current price.

  • 0 (Down): If the price n periods in the future is lower than or equal to the current price.

 

This transforms our complex trading problem into a simple binary classification problem that machine learning models excel at solving.

 

Code Block 3: Defining the Target and Splitting Data

 

from sklearn.model_selection import train_test_split

 

def prepare_ml_data(df, forecast_horizon=5):

    """

    Prepares the data for machine learning by defining the target variable

    and splitting the data into features (X) and target (y).

   

    Args:

        df (pd.DataFrame): The DataFrame with features.

        forecast_horizon (int): The number of periods into the future to predict.

       

    Returns:

        tuple: A tuple containing X (features) and y (target).

    """

    if df is None:

        return None, None

       

    # --- Define the Target Variable (y) ---

    # We want to predict if the price will be higher 'forecast_horizon' periods from now.

    df['future_price'] = df['close'].shift(-forecast_horizon)

   

    # The target is 1 if future price is higher, 0 otherwise.

    df['target'] = (df['future_price'] > df['close']).astype(int)

   

    # Drop the last 'forecast_horizon' rows as they will have NaN for the target

    df.dropna(inplace=True)

   

    # --- Define Features (X) ---

    # X is our dataframe minus the target and any columns we don't want the model to see.

    # We must drop 'future_price' to prevent data leakage!

    features_to_drop = ['open', 'high', 'low', 'close', 'volume', 'future_price', 'target']

    X = df.drop(columns=features_to_drop)

   

    y = df['target']

   

    print("ML data preparation complete.")

    print(f"Shape of features (X): {X.shape}")

    print(f"Shape of target (y): {y.shape}")

    print("Class distribution in target (y):")

    print(y.value_counts(normalize=True))

   

    return X, y

 

# --- Splitting the Data into Training and Testing Sets ---

# !! CRITICAL !! For time-series data, we must NOT shuffle the data.

# The test set must come chronologically after the training set to simulate real-world prediction.

 

def split_data(X, y, test_size=0.2):

    """

    Splits data into training and testing sets for time-series.

   

    Args:

        X (pd.DataFrame): Features.

        y (pd.Series): Target.

        test_size (float): The proportion of the dataset to allocate to the test split.

       

    Returns:

        tuple: X_train, X_test, y_train, y_test

    """

    if X is None or y is None:

        return None, None, None, None

 

    # We use train_test_split but with shuffle=False to respect the time order.

    X_train, X_test, y_train, y_test = train_test_split(

        X, y, test_size=test_size, shuffle=False

    )

   

    print("Data split into training and testing sets.")

    print(f"X_train shape: {X_train.shape}")

    print(f"X_test shape: {X_test.shape}")

   

    return X_train, X_test, y_train, y_test

 

# --- Usage ---

if featured_df is not None:

    X, y = prepare_ml_data(featured_df.copy())

    X_train, X_test, y_train, y_test = split_data(X, y)

 

 

Explanation:

 

  1. shift(-forecast_horizon): This is the key to creating our target. shift(-5) pulls the closing price from 5 periods in the future into the current row under the future_price column.

  2. Data Leakage Prevention: We explicitly drop the future_price column from our features X. If we left it in, the model would learn a perfect but useless rule: if future_price > close, predict 1. This is cheating, as we wouldn't know the future price in a live scenario.

  3. train_test_split(shuffle=False): This is one of the most critical steps in time-series modeling. We split our data into a training set (e.g., the first 80% of the data) and a testing set (the most recent 20%). By setting shuffle=False, we ensure that we train on the past and test on the "future," mimicking how a real trading system would operate. Shuffling the data would destroy all temporal patterns and lead to a wildly over-optimistic and completely invalid model.

 

Choosing, Training, and Evaluating Your First AI Model

 

We will start with a Random Forest Classifier. It's an excellent choice for this problem because it's powerful, robust to overfitting (compared to a single decision tree), and can provide insights into which features are most important.

 

Code Block 4: Training the Model and Evaluating Performance

 

from sklearn.ensemble import RandomForestClassifier

from sklearn.metrics import classification_report, confusion_matrix

import seaborn as sns

import matplotlib.pyplot as plt

 

def train_and_evaluate(X_train, y_train, X_test, y_test):

    """

    Trains a RandomForestClassifier and evaluates its performance.

   

    Args:

        X_train, y_train: Training data.

        X_test, y_test: Testing data.

       

    Returns:

        The trained model.

    """

    if X_train is None:

        print("Training data not available. Aborting.")

        return None

       

    # --- Model Training ---

    # We can tune these hyperparameters, but we'll start with sensible defaults.

    # n_estimators: number of trees in the forest.

    # random_state: for reproducibility.

    model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)

   

    print("Training the RandomForestClassifier...")

    model.fit(X_train, y_train)

    print("Training complete.")

   

    # --- Model Evaluation ---

    print("\n--- Model Evaluation on Test Data ---")

    predictions = model.predict(X_test)

   

    # Print the classification report

    print("\nClassification Report:")

    # target_names=['Down', 'Up'] helps make the report readable

    print(classification_report(y_test, predictions, target_names=['Down (0)', 'Up (1)']))

   

    # Display the Confusion Matrix

    print("\nConfusion Matrix:")

    cm = confusion_matrix(y_test, predictions)

    print(cm)

   

    # Visualize the Confusion Matrix

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

    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',

                xticklabels=['Predicted Down', 'Predicted Up'],

                yticklabels=['Actual Down', 'Actual Up'])

    plt.title('Confusion Matrix')

    plt.ylabel('Actual Label')

    plt.xlabel('Predicted Label')

    plt.show()

   

    return model

 

# --- Usage ---

trained_model = train_and_evaluate(X_train, y_train, X_test, y_test)

 

 

Explanation of the Evaluation:

 

A raw accuracy score (e.g., 55% accurate) is misleading in trading. We need to look deeper.

 

  • Classification Report:

    • Precision: Of all the times the model predicted "Up," what percentage of them were actually "Up"? High precision for the "Up" class is crucial; it means we avoid false signals (false positives).

    • Recall: Of all the actual "Up" moves that happened, what percentage did our model correctly identify?

    • F1-Score: The harmonic mean of precision and recall, providing a single score that balances both.

  • Confusion Matrix: This is the most intuitive tool.

    • Top-Left (True Negative): Model correctly predicted "Down."

    • Bottom-Right (True Positive): Model correctly predicted "Up." These are our winning trades.

    • Top-Right (False Positive): Model predicted "Up," but the price went "Down." These are our losing trades.

    • Bottom-Left (False Negative): Model predicted "Down," but the price went "Up." These are missed opportunities.

 

For a trading strategy, we are often most concerned with maximizing True Positives while minimizing False Positives. A model with high precision for the "Up" signal is often preferable, even if it means lower recall (missing some opportunities).

 

 

Part 4: From Model to Strategy - Backtesting and Validation

 

A model that is 55% accurate at predicting the market's direction is not a profitable strategy... yet. We need to translate these predictions into trading rules and simulate their performance over time. This process is called backtesting.

 

Automated Backtesting with Python: Simulating Your Strategy

 

We will perform a simple vectorized backtest. The logic is as follows:

 

  1. Use our trained model to generate predictions (signals) for the entire dataset.

  2. Calculate the periodic returns of the asset (e.g., daily or minute-by-minute returns).

  3. Create a "strategy returns" series: when our signal is 1 (go long), our strategy return for that period is the asset's return. When our signal is 0 (do nothing/stay flat), our strategy return is 0.

  4. Calculate the cumulative returns of the strategy over time to generate an equity curve.

 

Code Block 5: Vectorized Backtesting

 

def run_backtest(model, X, y, initial_capital=100000):

    """

    Runs a simple vectorized backtest on the model's predictions.

   

    Args:

        model: The trained machine learning model.

        X (pd.DataFrame): The feature data for the backtest period.

        y (pd.Series): The true target data for the backtest period.

        initial_capital (float): The starting capital for the backtest.

       

    Returns:

        pd.DataFrame: A DataFrame containing the backtest results.

    """

    if model is None or X is None:

        return None

 

    # 1. Generate signals from the model

    signals = pd.Series(model.predict(X), index=X.index)

   

    # Combine signals with true values for analysis

    backtest_df = pd.DataFrame({'signal': signals, 'true_target': y})

   

    # 2. Calculate asset returns

    # We need the original 'close' price for this, so let's merge it back

    # This assumes 'featured_df' is available in the global scope or passed in

    backtest_df = backtest_df.join(featured_df[['close']], how='inner')

    backtest_df['asset_return'] = backtest_df['close'].pct_change()

   

    # 3. Calculate strategy returns

    # Our strategy: be in the market when signal is 1, be out when signal is 0

    # We shift the signal by 1 because we make a decision at time T based on data at T,

    # and the return is realized at T+1. This prevents look-ahead bias.

    backtest_df['strategy_return'] = backtest_df['asset_return'] * backtest_df['signal'].shift(1)

   

    # 4. Calculate cumulative returns (equity curve)

    backtest_df.fillna(0, inplace=True)

    backtest_df['cumulative_asset_return'] = (1 + backtest_df['asset_return']).cumprod()

    backtest_df['cumulative_strategy_return'] = (1 + backtest_df['strategy_return']).cumprod()

   

    print("Backtest complete.")

   

    # Plot the equity curve

    plt.figure(figsize=(12, 7))

    backtest_df['cumulative_strategy_return'].plot(label='AI Strategy Equity Curve', color='blue')

    backtest_df['cumulative_asset_return'].plot(label='Buy and Hold Equity Curve', color='grey', linestyle='--')

    plt.title('Strategy Performance vs. Buy and Hold')

    plt.xlabel('Date')

    plt.ylabel('Cumulative Returns (Growth of $1)')

    plt.legend()

    plt.grid()

    plt.show()

   

    return backtest_df

 

# --- Usage ---

# We run the backtest on the test set to see out-of-sample performance

backtest_results = run_backtest(trained_model, X_test, y_test)

Explanation:

 

 

  • signal.shift(1): This is another subtle but critically important step to prevent look-ahead bias. We make our trading decision based on the signal at the close of bar T. The profit or loss from that decision is realized on the next bar, T+1. By shifting the signals forward by one period, we ensure our simulation correctly reflects this reality.

  • Equity Curve: The resulting plot is the most important visual from a backtest. It shows the growth of your capital over time. A good strategy should have a consistently upward-sloping curve with minimal large dips (drawdowns). Comparing it to a "Buy and Hold" strategy provides a crucial benchmark.

 

Key Performance Metrics: Measuring What Matters

 

A pretty equity curve is nice, but professional quants use a suite of metrics to dissect a strategy's performance and risk.

 

The Sharpe Ratio and Sortino Ratio in Python

 

  • Sharpe Ratio: The most famous performance metric. It measures your risk-adjusted return. A higher Sharpe Ratio is better. The formula is (Average Return - Risk-Free Rate) / Standard Deviation of Returns. It tells you how much return you are getting for the amount of volatility (risk) you are taking on.

  • Sortino Ratio: A popular variation of the Sharpe Ratio. The key difference is that it only penalizes for downside volatility. It doesn't consider upside volatility (good volatility) as risk. This often provides a more realistic measure of risk for trading strategies. The formula is (Average Return - Risk-Free Rate) / Standard Deviation of Negative Returns.

 

Code Block 6: Calculating Performance Metrics

 

def calculate_performance_metrics(returns_series, risk_free_rate=0.0):

    """

    Calculates key performance metrics for a series of returns.

   

    Args:

        returns_series (pd.Series): A series of periodic returns.

        risk_free_rate (float): The annual risk-free rate.

       

    Returns:

        dict: A dictionary of performance metrics.

    """

    # Assuming daily returns, so we need to annualize

    # This factor will change based on data frequency (e.g., ~252 for daily, more for intraday)

    annualization_factor = 252

   

    # --- Sharpe Ratio ---

    mean_return = returns_series.mean()

    std_dev = returns_series.std()

    sharpe_ratio = (mean_return / std_dev) * np.sqrt(annualization_factor) if std_dev != 0 else 0

   

    # --- Sortino Ratio ---

    negative_returns = returns_series[returns_series < 0]

    downside_std_dev = negative_returns.std()

    sortino_ratio = (mean_return / downside_std_dev) * np.sqrt(annualization_factor) if downside_std_dev != 0 else 0

   

    # --- Max Drawdown ---

    cumulative_returns = (1 + returns_series).cumprod()

    peak = cumulative_returns.expanding(min_periods=1).max()

    drawdown = (cumulative_returns - peak) / peak

    max_drawdown = drawdown.min()

   

    metrics = {

        'Annualized Return': mean_return * annualization_factor,

        'Annualized Volatility': std_dev * np.sqrt(annualization_factor),

        'Sharpe Ratio': sharpe_ratio,

        'Sortino Ratio': sortino_ratio,

        'Max Drawdown': max_drawdown

    }

   

    print("\n--- Performance Metrics ---")

    for key, value in metrics.items():

        print(f"{key}: {value:.4f}")

       

    return metrics

 

# --- Usage ---

if backtest_results is not None:

    strategy_metrics = calculate_performance_metrics(backtest_results['strategy_return'])

 

 

 

The Gold Standard: Walk-Forward Analysis for Robust Strategies

 

A single train-test split is good, but it has a weakness. The model is static; it's trained once on a large chunk of historical data and never updated. Markets, however, are not static. They evolve, and market "regimes" change. A strategy that worked well from 2018-2020 might fail completely in 2022.

 

Walk-Forward Analysis (WFA) is the solution. It is a more realistic and robust method of backtesting that simulates how a trader would periodically re-train their model.

 

The process works in a series of rolling windows:

 

  1. Window 1: Train the model on data from years 1-4 (the "in-sample" period).

  2. Test 1: Test (trade) the model on data from year 5 (the "out-of-sample" period).

  3. Window 2: Slide the window forward. Now, train a new model on years 2-5.

  4. Test 2: Test this new model on year 6.

  5. ...and so on, until you reach the end of your data.

 

 

Finally, you stitch together all the out-of-sample periods (years 5, 6, etc.) to form one continuous, more reliable equity curve. This process ensures your strategy is tested on data it has never seen, across multiple market regimes, and simulates the real-world process of model retraining. While the full code for WFA is complex, understanding the concept is a massive step towards professional-grade quantitative analysis.

 

Part 5: Bringing It All Together: The Interactive Streamlit Dashboard

 

Now for the final, most rewarding part. We will take all our components—data loading, feature engineering, model training, and backtesting—and wrap them in a user-friendly Streamlit dashboard.

 

This dashboard will allow us to:

 

  • Upload our own data file.

  • Adjust model parameters like the forecast horizon on the fly.

  • Trigger the backtest with the click of a button.

  • View the equity curve, key performance metrics, and the raw data all in one place.

  •  

Code Block 7: The Complete Streamlit Dashboard (dashboard.py)

 

import streamlit as st

import pandas as pd

import numpy as np

from sklearn.ensemble import RandomForestClassifier

from sklearn.model_selection import train_test_split

from sklearn.metrics import classification_report, confusion_matrix

import matplotlib.pyplot as plt

import seaborn as sns

 

# --- Set Page Configuration ---

st.set_page_config(layout="wide", page_title="AI Trading Dashboard")

 

# --- All Helper Functions from previous blocks go here ---

# (load_data, engineer_features, prepare_ml_data, split_data,

#  train_and_evaluate, run_backtest, calculate_performance_metrics)

# For brevity, we will assume they are defined above this point in the script.

# We will redefine them here for a self-contained script.

 

#<editor-fold desc="Helper Functions">

@st.cache_data

def load_data(uploaded_file):

    try:

        df = pd.read_csv(uploaded_file)

        df.columns = [col.lower().strip() for col in df.columns]

        df['datetime'] = pd.to_datetime(df['datetime'])

        df.set_index('datetime', inplace=True)

        df.ffill(inplace=True)

        df.sort_index(inplace=True)

        return df

    except Exception as e:

        st.error(f"Error loading data: {e}")

        return None

 

def engineer_features(df, short_window=20, long_window=100, rsi_period=14, atr_period=14):

    if df is None: return None

    df_feat = df.copy()

    df_feat['sma_short'] = df_feat['close'].rolling(window=short_window).mean()

    df_feat['sma_long'] = df_feat['close'].rolling(window=long_window).mean()

    delta = df_feat['close'].diff()

    gain = (delta.where(delta > 0, 0)).rolling(window=rsi_period).mean()

    loss = (-delta.where(delta < 0, 0)).rolling(window=rsi_period).mean()

    rs = gain / loss

    df_feat['rsi'] = 100 - (100 / (1 + rs))

    high_low = df_feat['high'] - df_feat['low']

    high_close = np.abs(df_feat['high'] - df_feat['close'].shift())

    low_close = np.abs(df_feat['low'] - df_feat['close'].shift())

    ranges = pd.concat([high_low, high_close, low_close], axis=1)

    true_range = np.max(ranges, axis=1)

    df_feat['atr'] = true_range.rolling(window=atr_period).mean()

    df_feat['day_of_week'] = df_feat.index.dayofweek

    df_feat['hour_of_day'] = df_feat.index.hour

    df_feat['return_1p'] = df_feat['close'].pct_change(1)

    df_feat['return_5p'] = df_feat['close'].pct_change(5)

    df_feat.dropna(inplace=True)

    return df_feat

 

def prepare_ml_data(df, forecast_horizon=5):

    if df is None: return None, None

    df_ml = df.copy()

    df_ml['future_price'] = df_ml['close'].shift(-forecast_horizon)

    df_ml['target'] = (df_ml['future_price'] > df_ml['close']).astype(int)

    df_ml.dropna(inplace=True)

    features_to_drop = ['open', 'high', 'low', 'close', 'volume', 'future_price', 'target']

    X = df_ml.drop(columns=features_to_drop)

    y = df_ml['target']

    return X, y

 

def run_full_pipeline(df, forecast_horizon, test_size):

    st.write("### 2. Feature Engineering")

    with st.spinner('Engineering features...'):

        featured_df = engineer_features(df)

        st.write("Features created successfully. Sample of data with features:")

        st.dataframe(featured_df.head())

 

    st.write("### 3. Machine Learning Model Training")

    with st.spinner('Preparing data and training model...'):

        X, y = prepare_ml_data(featured_df, forecast_horizon)

       

        if X is None or y.empty:

            st.error("Failed to create ML data. Check your forecast horizon and data.")

            return

 

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, shuffle=False)

       

        model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)

        model.fit(X_train, y_train)

        st.success("Model trained successfully!")

 

        predictions = model.predict(X_test)

       

        # Display Classification Report

        st.write("#### Classification Report (on Test Set)")

        report = classification_report(y_test, predictions, target_names=['Down (0)', 'Up (1)'], output_dict=True)

        st.dataframe(pd.DataFrame(report).transpose())

 

    st.write("### 4. Backtesting and Performance")

    with st.spinner('Running backtest...'):

        # Vectorized Backtest

        backtest_df = pd.DataFrame(index=X_test.index)

        backtest_df['signal'] = predictions

        backtest_df = backtest_df.join(featured_df[['close']], how='inner')

        backtest_df['asset_return'] = backtest_df['close'].pct_change()

        backtest_df['strategy_return'] = backtest_df['asset_return'] * backtest_df['signal'].shift(1)

        backtest_df.fillna(0, inplace=True)

       

        backtest_df['cumulative_asset_return'] = (1 + backtest_df['asset_return']).cumprod()

        backtest_df['cumulative_strategy_return'] = (1 + backtest_df['strategy_return']).cumprod()

 

        # Equity Curve Plot

        st.write("#### Equity Curve")

        fig, ax = plt.subplots(figsize=(12, 6))

        backtest_df['cumulative_strategy_return'].plot(ax=ax, label='AI Strategy', color='blue')

        backtest_df['cumulative_asset_return'].plot(ax=ax, label='Buy and Hold', color='grey', linestyle='--')

        ax.set_title('Strategy Performance vs. Buy and Hold')

        ax.set_ylabel('Cumulative Returns')

        ax.legend()

        ax.grid(True)

        st.pyplot(fig)

 

        # Performance Metrics

        st.write("#### Performance Metrics")

        metrics = calculate_performance_metrics(backtest_df['strategy_return'])

        col1, col2, col3 = st.columns(3)

        col1.metric("Sharpe Ratio", f"{metrics['Sharpe Ratio']:.2f}")

        col2.metric("Sortino Ratio", f"{metrics['Sortino Ratio']:.2f}")

        col3.metric("Max Drawdown", f"{metrics['Max Drawdown']*100:.2f}%")

 

def calculate_performance_metrics(returns_series, risk_free_rate=0.0):

    annualization_factor = 252

    mean_return = returns_series.mean()

    std_dev = returns_series.std()

    sharpe_ratio = (mean_return / std_dev) * np.sqrt(annualization_factor) if std_dev != 0 else 0

    negative_returns = returns_series[returns_series < 0]

    downside_std_dev = negative_returns.std()

    sortino_ratio = (mean_return / downside_std_dev) * np.sqrt(annualization_factor) if downside_std_dev != 0 else 0

    cumulative_returns = (1 + returns_series).cumprod()

    peak = cumulative_returns.expanding(min_periods=1).max()

    drawdown = (cumulative_returns - peak) / peak

    max_drawdown = drawdown.min()

    return {

        'Sharpe Ratio': sharpe_ratio,

        'Sortino Ratio': sortino_ratio,

        'Max Drawdown': max_drawdown

    }

#</editor-fold>

 

# --- Main App ---

st.title("📈 Building an AI Trading Dashboard with Python")

 

st.markdown("""

This dashboard allows you to upload your own financial data, train a machine learning model,

and backtest a simple AI-driven trading strategy.

""")

 

# --- Sidebar for User Inputs ---

st.sidebar.header("⚙️ Configuration")

uploaded_file = st.sidebar.file_uploader("Upload your OHLCV CSV data", type="csv")

 

if uploaded_file is not None:

    st.sidebar.success("File uploaded successfully!")

   

    forecast_horizon = st.sidebar.slider(

        "Prediction Horizon (Periods)",

        min_value=1, max_value=50, value=5,

        help="How many periods into the future should the model try to predict?"

    )

   

    test_size = st.sidebar.slider(

        "Test Set Size",

        min_value=0.1, max_value=0.5, value=0.2, step=0.05,

        help="The proportion of recent data to use for testing the model."

    )

 

    if st.sidebar.button("🚀 Run Analysis", use_container_width=True):

        st.write("### 1. Data Loading and Preview")

        main_df = load_data(uploaded_file)

        if main_df is not None:

            st.write("Data successfully loaded. Here are the first 5 rows:")

            st.dataframe(main_df.head())

           

            # Run the entire pipeline

            run_full_pipeline(main_df, forecast_horizon, test_size)

else:

    st.info("Awaiting CSV file upload. Please ensure it has 'datetime,open,high,low,close,volume' columns.")

 

 

How to Run This Dashboard:

 

  1. Save the code above as a Python file (e.g., dashboard.py).

  2. Make sure you have all the libraries installed: pip install streamlit pandas numpy scikit-learn matplotlib seaborn.

  3. Open your terminal or command prompt.

  4. Navigate to the directory where you saved the file.

  5. Run the command: streamlit run dashboard.py

  6. Your web browser will open with your interactive dashboard!

 

Conclusion: Your Journey Starts Now

 

We have traveled a long and detailed road. We started with a core idea, assembled a professional-grade Python toolkit, and meticulously navigated the steps of data preparation, feature engineering, AI model training, and robust backtesting. The result is not just a collection of scripts, but a cohesive, powerful, and interactive tool. This is the essence of building an AI trading dashboard with Python.

 

You now possess the foundational knowledge to create tools that can provide a real analytical edge. You understand the importance of clean data, the art of feature engineering, the necessity of avoiding look-ahead bias, and the superiority of methods like walk-forward analysis over simple backtests.

 

But this is just the beginning. The framework you've learned is a launchpad for endless exploration. You can now:

 

  • Experiment with more advanced models like Gradient Boosting (XGBoost, LightGBM) or even neural networks (LSTMs).

  • Incorporate alternative data sources, such as news sentiment or economic indicators.

  • Build more complex strategies that include sophisticated risk management, position sizing, and stop-loss logic.

  • Connect your dashboard to a live data feed and paper trading account to see your AI operate in real-time.

 

The path of a quantitative trader is one of continuous learning, experimentation, and refinement. You have taken the most important step by building your own workshop instead of just renting tools. The markets are a complex, ever-evolving puzzle, but with the skills you've developed here, you are better equipped than ever to start solving it. Now, go build your edge.

 

 

Comments


bottom of page