1. LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  • Design and build interactive financial dashboards using Plotly and Dash.

  • Create professional financial visualizations (candlestick charts, line charts, heatmaps).

  • Build a complete backtesting engine for trading strategies.

  • Implement performance metrics and risk statistics for strategy evaluation.

  • Create a real-time portfolio tracking dashboard.

  • Build a stock screener application with custom filters.

  • Deploy financial applications as web services.

  • Understand the architecture of production-grade financial applications.


2. DATA VISUALIZATION FOR FINANCE

2.1 Matplotlib – The Foundation

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Basic line chart
prices = [100, 102, 98, 105, 110, 112, 108, 115, 120, 118]
dates = pd.date_range('2024-01-01', periods=10, freq='D')

plt.figure(figsize=(12, 6))
plt.plot(dates, prices, 'b-', linewidth=2, label='Price')
plt.title('Stock Price Chart')
plt.xlabel('Date')
plt.ylabel('Price ($)')
plt.grid(True, alpha=0.3)
plt.legend()
plt.show()

# Multiple series
returns = np.random.normal(0.001, 0.02, 252)
cumulative = (1 + returns).cumprod()

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

ax1.plot(cumulative)
ax1.set_title('Cumulative Returns')
ax1.set_ylabel('Cumulative Return')
ax1.grid(True, alpha=0.3)

ax2.hist(returns, bins=50, alpha=0.7, edgecolor='black')
ax2.set_title('Return Distribution')
ax2.set_xlabel('Daily Return')
ax2.set_ylabel('Frequency')
ax2.axvline(x=0, color='red', linestyle='--')

plt.tight_layout()
plt.show()

2.2 Candlestick Charts

python
import plotly.graph_objects as go
from plotly.subplots import make_subplots

def create_candlestick_chart(df, title='Candlestick Chart'):
    """
    Create an interactive candlestick chart using Plotly.
    df must have columns: Date, Open, High, Low, Close
    """
    fig = go.Figure(data=[go.Candlestick(
        x=df['Date'],
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        name='Price'
    )])

    fig.update_layout(
        title=title,
        yaxis_title='Price ($)',
        xaxis_title='Date',
        template='plotly_dark',
        height=600
    )

    return fig

# Example usage
# df = pd.read_csv('stock_data.csv', parse_dates=['Date'])
# fig = create_candlestick_chart(df)
# fig.show()

2.3 Interactive Dashboards with Plotly and Dash

python
import dash
from dash import dcc, html, Input, Output
import plotly.express as px
import pandas as pd
import yfinance as yf

# Initialize the app
app = dash.Dash(__name__)

# Sample data
tickers = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META']

# Layout
app.layout = html.Div([
    html.H1('Financial Dashboard', style={'textAlign': 'center'}),
    
    html.Div([
        html.Label('Select Ticker:'),
        dcc.Dropdown(
            id='ticker-dropdown',
            options=[{'label': t, 'value': t} for t in tickers],
            value='AAPL'
        ),
        
        html.Label('Select Date Range:'),
        dcc.DatePickerRange(
            id='date-range',
            start_date='2024-01-01',
            end_date='2024-12-31'
        ),
        
        html.Button('Update', id='update-button', n_clicks=0)
    ], style={'padding': '20px'}),
    
    dcc.Graph(id='price-chart'),
    dcc.Graph(id='volume-chart'),
    html.Div(id='stats-output')
])

@app.callback(
    [Output('price-chart', 'figure'),
     Output('volume-chart', 'figure'),
     Output('stats-output', 'children')],
    [Input('update-button', 'n_clicks')],
    [dash.dependencies.State('ticker-dropdown', 'value'),
     dash.dependencies.State('date-range', 'start_date'),
     dash.dependencies.State('date-range', 'end_date')]
)
def update_dashboard(n_clicks, ticker, start_date, end_date):
    # Fetch data
    df = yf.download(ticker, start=start_date, end=end_date)
    df.reset_index(inplace=True)
    
    # Price chart
    price_fig = px.line(
        df, x='Date', y='Close',
        title=f'{ticker} Price',
        template='plotly_dark'
    )
    
    # Volume chart
    volume_fig = px.bar(
        df, x='Date', y='Volume',
        title=f'{ticker} Volume',
        template='plotly_dark'
    )
    
    # Statistics
    returns = df['Close'].pct_change().dropna()
    stats = html.Div([
        html.H4(f'{ticker} Statistics'),
        html.P(f'Average Return: {returns.mean():.4%}'),
        html.P(f'Volatility: {returns.std():.4%}'),
        html.P(f'Sharpe Ratio: {returns.mean() / returns.std():.4f}'),
        html.P(f'Current Price: ${df["Close"].iloc[-1]:.2f}')
    ])
    
    return price_fig, volume_fig, stats

if __name__ == '__main__':
    app.run_server(debug=True)

2.4 Heatmaps for Correlation Analysis

python
def create_correlation_heatmap(returns_df):
    """
    Create a correlation heatmap for multiple assets.
    """
    corr_matrix = returns_df.corr()
    
    fig = px.imshow(
        corr_matrix,
        text_auto=True,
        color_continuous_scale='RdBu_r',
        title='Asset Correlation Matrix',
        template='plotly_dark'
    )
    
    return fig

# Example
# returns_df = pd.DataFrame({
#     'AAPL': aapl_returns,
#     'GOOGL': googl_returns,
#     'MSFT': msft_returns
# })
# fig = create_correlation_heatmap(returns_df)
# fig.show()

3. BUILDING A BACKTESTING ENGINE

3.1 Complete Backtesting Framework

python
import pandas as pd
import numpy as np
from typing import Dict, List, Callable, Optional
from dataclasses import dataclass

@dataclass
class Trade:
    """Represents a single trade."""
    entry_date: pd.Timestamp
    exit_date: pd.Timestamp
    entry_price: float
    exit_price: float
    quantity: float
    direction: str  # 'long' or 'short'
    
    @property
    def return_pct(self) -> float:
        if self.direction == 'long':
            return (self.exit_price - self.entry_price) / self.entry_price
        else:
            return (self.entry_price - self.exit_price) / self.entry_price
    
    @property
    def pnl(self) -> float:
        return self.return_pct * self.quantity * self.entry_price

class BacktestEngine:
    """
    A flexible backtesting engine for trading strategies.
    """
    
    def __init__(self, initial_capital: float = 100000):
        self.initial_capital = initial_capital
        self.capital = initial_capital
        self.trades = []
        self.equity_curve = []
        self.position = 0
        self.entry_price = None
    
    def run(self, data: pd.DataFrame, signal_column: str, 
            price_column: str = 'Close', stop_loss: Optional[float] = None,
            take_profit: Optional[float] = None) -> Dict:
        """
        Run the backtest on historical data.
        """
        self.capital = self.initial_capital
        self.trades = []
        self.equity_curve = []
        self.position = 0
        self.entry_price = None
        
        for i in range(1, len(data)):
            current_price = data[price_column].iloc[i]
            signal = data[signal_column].iloc[i]
            
            # Check stop loss and take profit
            if self.position != 0 and self.entry_price is not None:
                if stop_loss is not None:
                    loss_threshold = self.entry_price * (1 - stop_loss)
                    if current_price <= loss_threshold:
                        self._exit_trade(data.index[i], current_price, 'stop_loss')
                        continue
                
                if take_profit is not None:
                    profit_threshold = self.entry_price * (1 + take_profit)
                    if current_price >= profit_threshold:
                        self._exit_trade(data.index[i], current_price, 'take_profit')
                        continue
            
            # Entry signals
            if self.position == 0 and signal == 1:
                self._enter_long(data.index[i], current_price)
            
            elif self.position == 0 and signal == -1:
                self._enter_short(data.index[i], current_price)
            
            elif self.position > 0 and signal == -1:
                self._exit_trade(data.index[i], current_price, 'signal')
                self._enter_short(data.index[i], current_price)
            
            elif self.position < 0 and signal == 1:
                self._exit_trade(data.index[i], current_price, 'signal')
                self._enter_long(data.index[i], current_price)
            
            # Track equity
            self._track_equity(data.index[i], current_price)
        
        # Close any open position
        if self.position != 0:
            self._exit_trade(data.index[-1], data[price_column].iloc[-1], 'end_of_period')
        
        return self._calculate_performance()
    
    def _enter_long(self, date: pd.Timestamp, price: float):
        """Enter a long position."""
        self.position = self.capital / price
        self.entry_price = price
        self.trades.append(Trade(
            entry_date=date,
            exit_date=None,
            entry_price=price,
            exit_price=None,
            quantity=self.position,
            direction='long'
        ))
    
    def _enter_short(self, date: pd.Timestamp, price: float):
        """Enter a short position."""
        self.position = -self.capital / price
        self.entry_price = price
        self.trades.append(Trade(
            entry_date=date,
            exit_date=None,
            entry_price=price,
            exit_price=None,
            quantity=-self.position,
            direction='short'
        ))
    
    def _exit_trade(self, date: pd.Timestamp, price: float, reason: str):
        """Exit the current position."""
        if not self.trades:
            return
        
        trade = self.trades[-1]
        if trade.exit_date is None:
            trade.exit_date = date
            trade.exit_price = price
            
            # Update capital
            self.capital += trade.pnl
            self.position = 0
            self.entry_price = None
    
    def _track_equity(self, date: pd.Timestamp, current_price: float):
        """Track the current equity value."""
        if self.position == 0:
            equity = self.capital
        else:
            if self.position > 0:
                equity = self.capital + self.position * (current_price - self.entry_price)
            else:
                equity = self.capital + abs(self.position) * (self.entry_price - current_price)
        
        self.equity_curve.append({
            'date': date,
            'equity': equity,
            'return': (equity / self.initial_capital) - 1
        })
    
    def _calculate_performance(self) -> Dict:
        """Calculate performance metrics."""
        equity_df = pd.DataFrame(self.equity_curve)
        returns = equity_df['return'].pct_change().dropna()
        
        # Basic metrics
        total_return = equity_df['equity'].iloc[-1] / self.initial_capital - 1
        annualized_return = (1 + total_return) ** (252 / len(equity_df)) - 1
        
        # Risk metrics
        volatility = returns.std() * np.sqrt(252)
        sharpe = annualized_return / volatility if volatility > 0 else 0
        
        # Maximum drawdown
        running_max = equity_df['equity'].expanding().max()
        drawdown = (equity_df['equity'] / running_max) - 1
        max_drawdown = drawdown.min()
        
        # Win rate
        completed_trades = [t for t in self.trades if t.exit_date is not None]
        winning_trades = [t for t in completed_trades if t.pnl > 0]
        win_rate = len(winning_trades) / len(completed_trades) if completed_trades else 0
        
        return {
            'total_return': total_return,
            'annualized_return': annualized_return,
            'volatility': volatility,
            'sharpe_ratio': sharpe,
            'max_drawdown': max_drawdown,
            'win_rate': win_rate,
            'total_trades': len(completed_trades),
            'final_equity': equity_df['equity'].iloc[-1],
            'equity_curve': equity_df
        }

3.2 Building a Simple Moving Average Crossover Strategy

python
def sma_crossover_strategy(df, short_window=20, long_window=50):
    """
    Simple moving average crossover strategy.
    """
    df = df.copy()
    df['SMA_Short'] = df['Close'].rolling(short_window).mean()
    df['SMA_Long'] = df['Close'].rolling(long_window).mean()
    
    # Generate signals: 1 = buy, -1 = sell, 0 = hold
    df['Signal'] = 0
    df.loc[df['SMA_Short'] > df['SMA_Long'], 'Signal'] = 1
    df.loc[df['SMA_Short'] < df['SMA_Long'], 'Signal'] = -1
    
    # Remove NaN values
    df = df.dropna()
    
    return df

# Run backtest
def run_backtest_strategy(data, strategy_func, **kwargs):
    """
    Run a backtest with the given strategy.
    """
    # Generate signals
    data_with_signals = strategy_func(data, **kwargs)
    
    # Run backtest
    engine = BacktestEngine(initial_capital=100000)
    results = engine.run(data_with_signals, signal_column='Signal')
    
    return results, engine

# Example usage
# data = yf.download('AAPL', start='2020-01-01', end='2024-12-31')
# results, engine = run_backtest_strategy(data, sma_crossover_strategy)
# print(results)

4. PERFORMANCE REPORTING

4.1 Performance Metrics Visualization

python
def plot_backtest_results(results, engine):
    """
    Visualize backtest results.
    """
    fig = make_subplots(
        rows=3, cols=1,
        subplot_titles=('Equity Curve', 'Drawdown', 'Trade Distribution'),
        vertical_spacing=0.1
    )
    
    # Equity curve
    equity_df = results['equity_curve']
    fig.add_trace(
        go.Scatter(
            x=equity_df['date'],
            y=equity_df['equity'],
            mode='lines',
            name='Equity',
            line=dict(color='blue')
        ),
        row=1, col=1
    )
    
    # Drawdown
    running_max = equity_df['equity'].expanding().max()
    drawdown = (equity_df['equity'] / running_max - 1) * 100
    fig.add_trace(
        go.Scatter(
            x=equity_df['date'],
            y=drawdown,
            mode='lines',
            name='Drawdown',
            line=dict(color='red'),
            fill='tozeroy'
        ),
        row=2, col=1
    )
    
    # Trade distribution
    trades = [t for t in engine.trades if t.exit_date is not None]
    returns = [t.return_pct * 100 for t in trades]
    fig.add_trace(
        go.Histogram(
            x=returns,
            name='Trade Returns',
            nbinsx=30
        ),
        row=3, col=1
    )
    
    fig.update_layout(
        height=900,
        template='plotly_dark',
        showlegend=False
    )
    
    return fig

# Generate a performance report
def generate_performance_report(results):
    """
    Generate a text-based performance report.
    """
    report = f"""
    ========================================
    PERFORMANCE SUMMARY
    ========================================
    
    Total Return:          {results['total_return']:.2%}
    Annualized Return:     {results['annualized_return']:.2%}
    Volatility:            {results['volatility']:.2%}
    Sharpe Ratio:          {results['sharpe_ratio']:.4f}
    Maximum Drawdown:      {results['max_drawdown']:.2%}
    Win Rate:              {results['win_rate']:.2%}
    Total Trades:          {results['total_trades']}
    Final Equity:          ${results['final_equity']:,.2f}
    ========================================
    """
    return report

5. STOCK SCREENER APPLICATION

python
class StockScreener:
    """
    A stock screener that filters stocks based on financial criteria.
    """
    
    def __init__(self, data):
        self.data = data
        self.filters = []
    
    def add_filter(self, column, operator, value):
        """
        Add a filter condition.
        operator: '>', '<', '>=', '<=', '==', '!='
        """
        self.filters.append({
            'column': column,
            'operator': operator,
            'value': value
        })
    
    def run(self):
        """
        Apply all filters and return matching stocks.
        """
        result = self.data.copy()
        
        for filter_cond in self.filters:
            column = filter_cond['column']
            operator = filter_cond['operator']
            value = filter_cond['value']
            
            if operator == '>':
                result = result[result[column] > value]
            elif operator == '<':
                result = result[result[column] < value]
            elif operator == '>=':
                result = result[result[column] >= value]
            elif operator == '<=':
                result = result[result[column] <= value]
            elif operator == '==':
                result = result[result[column] == value]
            elif operator == '!=':
                result = result[result[column] != value]
        
        return result

# Example usage
def build_stock_screener():
    """
    Build a stock screener with common financial filters.
    """
    # Create sample data
    stocks = pd.DataFrame({
        'Symbol': ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META', 'TSLA', 'NVDA'],
        'P/E': [25, 28, 32, 45, 22, 60, 55],
        'MarketCap': [2800, 1700, 2500, 1500, 800, 700, 1200],
        'Revenue_Growth': [0.08, 0.12, 0.15, 0.10, 0.20, 0.30, 0.25],
        'Profit_Margin': [0.25, 0.22, 0.28, 0.15, 0.30, 0.12, 0.20],
        'Debt_to_Equity': [1.2, 0.8, 1.0, 1.5, 0.5, 2.0, 1.0]
    })
    
    screener = StockScreener(stocks)
    
    # Add filters
    screener.add_filter('P/E', '<', 30)
    screener.add_filter('Revenue_Growth', '>', 0.10)
    screener.add_filter('Profit_Margin', '>', 0.20)
    screener.add_filter('Debt_to_Equity', '<', 1.5)
    
    results = screener.run()
    return results

# results = build_stock_screener()
# print(results)

6. DEPLOYING FINANCIAL APPLICATIONS

6.1 Dockerizing the Application

dockerfile
# Dockerfile
FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8050

CMD ["python", "app.py"]

6.2 Environment Variables

python
import os

# Load environment variables
API_KEY = os.environ.get('FINANCIAL_API_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'

# Configuration
config = {
    'api_key': API_KEY,
    'database_url': DATABASE_URL,
    'debug': DEBUG
}

6.3 Logging and Monitoring

python
import logging
import sys

def setup_logging():
    """Configure application logging."""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.StreamHandler(sys.stdout),
            logging.FileHandler('app.log')
        ]
    )
    return logging.getLogger(__name__)

logger = setup_logging()

# Usage
logger.info("Application started")
logger.warning("Low balance alert")
logger.error("API connection failed")