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
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
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
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
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
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
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
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
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 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
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
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")