Skip to content

Latest commit

 

History

History
849 lines (669 loc) · 29.9 KB

7 Essential Python Packages for Finance.md

File metadata and controls

849 lines (669 loc) · 29.9 KB

7 Essential Python Packages for Finance

Slide 1: The Power of QuantLib in Python

QuantLib is a comprehensive quantitative finance library providing tools for derivatives pricing, fixed income analysis, and risk management. Its Python wrapper, QuantLib-Python, enables sophisticated financial modeling with object-oriented design patterns and industry-standard algorithms.

import QuantLib as ql

# Create a European call option
maturity_date = ql.Date().todaysDate() + ql.Period('1Y')
spot_price = 100.0
strike_price = 100.0
dividend_rate = 0.0
risk_free_rate = 0.05
volatility = 0.20

# Set up the pricing environment
day_count = ql.Actual365Fixed()
calendar = ql.UnitedStates()
calculation_date = ql.Date().todaysDate()
ql.Settings.instance().evaluationDate = calculation_date

# Create option parameters
payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)
exercise = ql.EuropeanExercise(maturity_date)
european_option = ql.VanillaOption(payoff, exercise)

# Set up the Black-Scholes process
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
riskfree_handle = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, risk_free_rate, day_count))
dividend_handle = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, dividend_rate, day_count))
volatility_handle = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(calculation_date, calendar, volatility, day_count))

# Calculate option price
black_scholes_process = ql.BlackScholesProcess(spot_handle, dividend_handle, riskfree_handle, volatility_handle)
european_option.setPricingEngine(ql.AnalyticEuropeanEngine(black_scholes_process))

print(f"Option NPV: {european_option.NPV():.4f}")
print(f"Delta: {european_option.delta():.4f}")
print(f"Gamma: {european_option.gamma():.4f}")
print(f"Vega: {european_option.vega():.4f}")

Slide 2: Pandas-ta for Technical Analysis

Pandas-ta extends pandas functionality with over 130 technical analysis indicators. It provides a pythonic interface for calculating moving averages, momentum indicators, volume studies, and volatility measures essential for algorithmic trading.

import pandas as pd
import pandas_ta as ta
import yfinance as yf

# Download historical data
symbol = "AAPL"
df = yf.download(symbol, start="2023-01-01", end="2024-01-01")

# Calculate multiple technical indicators
df.ta.strategy(
    name="technical_analysis",
    ta=[
        {"kind": "sma", "length": 20},
        {"kind": "sma", "length": 50},
        {"kind": "rsi"},
        {"kind": "macd", "fast": 12, "slow": 26, "signal": 9},
        {"kind": "bbands", "length": 20}
    ]
)

# Generate trading signals
df['SMA_Signal'] = np.where(df['SMA_20'] > df['SMA_50'], 1, -1)
df['RSI_Signal'] = np.where(df['RSI_14'] < 30, 1, np.where(df['RSI_14'] > 70, -1, 0))

# Calculate returns based on signals
df['Returns'] = df['Close'].pct_change()
df['Strategy_Returns'] = df['Returns'] * df['SMA_Signal'].shift(1)

print(f"Strategy Sharpe Ratio: {(df['Strategy_Returns'].mean() / df['Strategy_Returns'].std()) * np.sqrt(252):.2f}")

Slide 3: Algorithmic Trading with Zipline

Zipline, originally developed by Quantopian, is a powerful Python library for backtesting trading strategies. It provides a robust event-driven system that simulates market conditions and handles order management with realistic transaction costs and slippage models.

from zipline.api import order_target, record, symbol
from zipline.finance import commission, slippage
import pandas as pd
import numpy as np

def initialize(context):
    context.asset = symbol('AAPL')
    context.set_commission(commission.PerShare(cost=0.001, min_trade_cost=1.0))
    context.set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))
    context.sma_short = 50
    context.sma_long = 200
    
def handle_data(context, data):
    # Calculate moving averages
    prices = data.history(context.asset, 'price', context.sma_long + 1, '1d')
    sma_short = prices[-context.sma_short:].mean()
    sma_long = prices[-context.sma_long:].mean()
    
    # Trading logic
    if sma_short > sma_long:
        order_target(context.asset, 100)
    elif sma_short < sma_long:
        order_target(context.asset, -100)
        
    record(short_mavg=sma_short, long_mavg=sma_long, price=prices[-1])

def analyze(context, perf):
    fig = plt.figure(figsize=(12, 8))
    ax1 = fig.add_subplot(211)
    perf.portfolio_value.plot(ax=ax1)
    ax1.set_ylabel('Portfolio Value')
    ax2 = fig.add_subplot(212)
    perf[['short_mavg', 'long_mavg']].plot(ax=ax2)
    perf['price'].plot(ax=ax2)
    ax2.set_ylabel('Price')

Slide 4: Financial Analysis with PyFolio

PyFolio is a sophisticated portfolio and risk analytics library that provides comprehensive performance and risk metrics. It integrates seamlessly with Zipline and enables detailed analysis of trading strategies through tear sheets and risk decomposition.

import pyfolio as pf
import pandas as pd
import numpy as np

# Prepare returns data
returns = pd.Series(
    index=pd.date_range('2023-01-01', '2024-01-01'),
    data=np.random.normal(0.001, 0.02, 252)  # Simulated daily returns
)

# Create full tear sheet
pf.create_full_tear_sheet(
    returns,
    benchmark_rets=market_data['spy_returns'],
    positions=positions_df,
    transactions=transactions_df,
    round_trips=True
)

# Calculate performance metrics
stats = pd.Series({
    'Annual Return': pf.annual_return(returns),
    'Cumulative Returns': pf.cum_returns_final(returns),
    'Annual Volatility': pf.annual_volatility(returns),
    'Sharpe Ratio': pf.sharpe_ratio(returns),
    'Sortino Ratio': pf.sortino_ratio(returns),
    'Max Drawdown': pf.max_drawdown(returns),
    'Calmar Ratio': pf.calmar_ratio(returns)
})

print(stats.round(4))

Slide 5: Advanced Risk Modeling with PyRisk

PyRisk provides sophisticated tools for calculating Value at Risk (VaR), Expected Shortfall, and other risk metrics using various methodologies including Historical Simulation, Monte Carlo, and Parametric approaches.

import numpy as np
from scipy import stats
import pandas as pd

class RiskMetrics:
    def __init__(self, returns, confidence_level=0.95):
        self.returns = returns
        self.confidence_level = confidence_level
        
    def historical_var(self):
        """Calculate Historical VaR"""
        return np.percentile(self.returns, (1 - self.confidence_level) * 100)
    
    def parametric_var(self):
        """Calculate Parametric VaR"""
        mu = np.mean(self.returns)
        sigma = np.std(self.returns)
        return stats.norm.ppf(1 - self.confidence_level, mu, sigma)
    
    def expected_shortfall(self):
        """Calculate Expected Shortfall (CVaR)"""
        var = self.historical_var()
        return np.mean(self.returns[self.returns <= var])
    
    def monte_carlo_var(self, n_simulations=10000):
        """Calculate Monte Carlo VaR"""
        mu = np.mean(self.returns)
        sigma = np.std(self.returns)
        simulated_returns = np.random.normal(mu, sigma, n_simulations)
        return np.percentile(simulated_returns, (1 - self.confidence_level) * 100)

# Example usage
returns = np.random.normal(0.001, 0.02, 1000)
risk = RiskMetrics(returns)

print(f"Historical VaR: {risk.historical_var():.4f}")
print(f"Parametric VaR: {risk.parametric_var():.4f}")
print(f"Expected Shortfall: {risk.expected_shortfall():.4f}")
print(f"Monte Carlo VaR: {risk.monte_carlo_var():.4f}")

Slide 6: Options Analysis with mibian

Mibian is a specialized library for options pricing and analysis that implements various options pricing models including Black-Scholes, Black-76, and their variations for both European and American options.

import mibian

class OptionsAnalyzer:
    def __init__(self, underlying, strike, rate, time, volatility):
        self.underlying = underlying
        self.strike = strike
        self.rate = rate
        self.time = time
        self.volatility = volatility
    
    def black_scholes_european(self):
        """Calculate European option prices and Greeks"""
        bs = mibian.BS([self.underlying, self.strike, self.rate, self.time], 
                      volatility=self.volatility)
        
        return {
            'Call Price': bs.callPrice,
            'Put Price': bs.putPrice,
            'Call Delta': bs.callDelta,
            'Put Delta': bs.putDelta,
            'Gamma': bs.gamma,
            'Vega': bs.vega,
            'Call Theta': bs.callTheta,
            'Put Theta': bs.putTheta,
            'Call Rho': bs.callRho,
            'Put Rho': bs.putRho
        }
    
    def implied_volatility(self, option_price, option_type='call'):
        """Calculate implied volatility"""
        if option_type.lower() == 'call':
            return mibian.BS([self.underlying, self.strike, self.rate, self.time], 
                           callPrice=option_price).impliedVolatility
        else:
            return mibian.BS([self.underlying, self.strike, self.rate, self.time], 
                           putPrice=option_price).impliedVolatility

# Example usage
analyzer = OptionsAnalyzer(
    underlying=100,  # Current price
    strike=100,      # Strike price
    rate=0.05,       # Risk-free rate
    time=1,          # Time to expiration (years)
    volatility=0.20  # Volatility
)

results = analyzer.black_scholes_european()
for metric, value in results.items():
    print(f"{metric}: {value:.4f}")

# Calculate implied volatility for a call option priced at 10
impl_vol = analyzer.implied_volatility(10, 'call')
print(f"\nImplied Volatility: {impl_vol:.4f}")

Slide 7: Backtesting with Backtrader

Backtrader provides a comprehensive framework for developing, testing, and analyzing trading strategies. It offers event-driven operations, multiple data feeds support, and advanced position sizing capabilities with realistic broker simulation.

import backtrader as bt
import datetime

class SmaCrossStrategy(bt.Strategy):
    params = (
        ('fast_period', 10),
        ('slow_period', 30),
        ('risk_percent', 0.02),
    )

    def __init__(self):
        self.fast_sma = bt.indicators.SMA(self.data.close, 
                                         period=self.params.fast_period)
        self.slow_sma = bt.indicators.SMA(self.data.close, 
                                         period=self.params.slow_period)
        self.crossover = bt.indicators.CrossOver(self.fast_sma, self.slow_sma)
        
    def next(self):
        if not self.position:
            if self.crossover > 0:
                price = self.data.close[0]
                cash = self.broker.get_cash()
                size = (cash * self.params.risk_percent) / price
                self.buy(size=size)
        
        elif self.crossover < 0:
            self.close()

# Initialize Cerebro engine
cerebro = bt.Cerebro()

# Add data feed
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate=datetime.datetime(2023, 1, 1),
    todate=datetime.datetime(2024, 1, 1)
)
cerebro.adddata(data)

# Add strategy
cerebro.addstrategy(SmaCrossStrategy)

# Set initial capital
cerebro.broker.setcash(100000.0)

# Add analyzer
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe_ratio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')

# Run backtest
results = cerebro.run()
strategy = results[0]

# Print results
print(f"Sharpe Ratio: {strategy.analyzers.sharpe_ratio.get_analysis()['sharperatio']:.2f}")
print(f"Max Drawdown: {strategy.analyzers.drawdown.get_analysis()['max']['drawdown']:.2%}")
print(f"Total Return: {strategy.analyzers.returns.get_analysis()['rtot']:.2%}")

Slide 8: Financial Data Analysis with yfinance

The yfinance library provides a reliable and efficient way to download historical market data from Yahoo Finance. It supports multiple assets, various timeframes, and includes fundamental data like financial statements and company information.

import yfinance as yf
import pandas as pd
import numpy as np

class FinancialAnalyzer:
    def __init__(self, ticker):
        self.ticker = yf.Ticker(ticker)
        self.df = self.ticker.history(period="1y")
        
    def calculate_technical_metrics(self):
        # Calculate technical indicators
        self.df['Returns'] = self.df['Close'].pct_change()
        self.df['Volatility'] = self.df['Returns'].rolling(window=20).std() * np.sqrt(252)
        self.df['SMA_20'] = self.df['Close'].rolling(window=20).mean()
        self.df['SMA_50'] = self.df['Close'].rolling(window=50).mean()
        self.df['RSI'] = self._calculate_rsi()
        
        return self.df
    
    def get_fundamental_metrics(self):
        # Get fundamental data
        info = self.ticker.info
        financials = self.ticker.financials
        balance_sheet = self.ticker.balance_sheet
        
        metrics = {
            'Market Cap': info.get('marketCap'),
            'PE Ratio': info.get('trailingPE'),
            'EPS': info.get('trailingEps'),
            'ROE': info.get('returnOnEquity'),
            'Revenue Growth': info.get('revenueGrowth'),
            'Debt to Equity': info.get('debtToEquity')
        }
        
        return metrics
    
    def _calculate_rsi(self, periods=14):
        delta = self.df['Close'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=periods).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=periods).mean()
        rs = gain / loss
        return 100 - (100 / (1 + rs))

# Example usage
analyzer = FinancialAnalyzer('AAPL')

# Get technical analysis
tech_data = analyzer.calculate_technical_metrics()
print("\nTechnical Metrics:")
print(tech_data.tail())

# Get fundamental analysis
fund_data = analyzer.get_fundamental_metrics()
print("\nFundamental Metrics:")
for metric, value in fund_data.items():
    print(f"{metric}: {value}")

# Download financial statements
statements = analyzer.ticker.financials
print("\nFinancial Statements:")
print(statements.head())

Slide 9: Machine Learning for Finance with scikit-learn

Implementing machine learning models for financial predictions requires careful feature engineering and model validation. This implementation demonstrates a comprehensive pipeline for predicting market movements using various technical indicators.

import pandas as pd
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
import yfinance as yf

class MLFinanceModel:
    def __init__(self, symbol, start_date, end_date):
        self.df = yf.download(symbol, start=start_date, end=end_date)
        self.features = None
        self.model = RandomForestClassifier(n_estimators=100, random_state=42)
        
    def create_features(self):
        df = self.df.copy()
        
        # Technical indicators
        df['Returns'] = df['Close'].pct_change()
        df['MA10'] = df['Close'].rolling(window=10).mean()
        df['MA30'] = df['Close'].rolling(window=30).mean()
        df['Volatility'] = df['Returns'].rolling(window=20).std()
        
        # Create target variable (1 if price goes up, 0 if down)
        df['Target'] = np.where(df['Returns'].shift(-1) > 0, 1, 0)
        
        # Feature columns
        feature_columns = ['Returns', 'MA10', 'MA30', 'Volatility']
        
        # Remove missing values
        df = df.dropna()
        
        self.features = df[feature_columns]
        self.target = df['Target']
        
        return df
    
    def train_model(self):
        # Create time series split
        tscv = TimeSeriesSplit(n_splits=5)
        
        # Scale features
        scaler = StandardScaler()
        scaled_features = scaler.fit_transform(self.features)
        
        # Training
        scores = []
        for train_idx, test_idx in tscv.split(scaled_features):
            X_train = scaled_features[train_idx]
            X_test = scaled_features[test_idx]
            y_train = self.target.iloc[train_idx]
            y_test = self.target.iloc[test_idx]
            
            self.model.fit(X_train, y_train)
            score = self.model.score(X_test, y_test)
            scores.append(score)
            
            print(f"\nFold Results:")
            print(classification_report(y_test, 
                  self.model.predict(X_test)))
        
        print(f"\nAverage Score: {np.mean(scores):.4f}")
        return np.mean(scores)
    
    def predict(self, X_new):
        return self.model.predict_proba(X_new)

# Example usage
model = MLFinanceModel('AAPL', '2022-01-01', '2024-01-01')
data = model.create_features()
accuracy = model.train_model()

# Make predictions for latest data
latest_features = model.features.tail(1)
prediction = model.predict(StandardScaler().fit_transform(latest_features))
print(f"\nProbability of price increase: {prediction[0][1]:.4f}")

Slide 10: Time Series Analysis with statsmodels

Statsmodels provides powerful tools for time series analysis, including ARIMA models, decomposition methods, and statistical tests essential for financial forecasting and analysis.

import statsmodels.api as sm
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
import pandas as pd
import numpy as np

class TimeSeriesAnalyzer:
    def __init__(self, data):
        self.data = data
        
    def decompose_series(self):
        """Decompose time series into trend, seasonal, and residual components"""
        decomposition = seasonal_decompose(self.data, period=20)
        return decomposition
    
    def check_stationarity(self):
        """Perform Augmented Dickey-Fuller test"""
        result = adfuller(self.data.dropna())
        return {
            'Test Statistic': result[0],
            'p-value': result[1],
            'Critical Values': result[4]
        }
    
    def fit_arima(self, order=(1,1,1)):
        """Fit ARIMA model and make predictions"""
        model = sm.tsa.ARIMA(self.data, order=order)
        results = model.fit()
        
        forecast = results.forecast(steps=30)
        conf_int = results.get_forecast(steps=30).conf_int()
        
        return {
            'Model': results,
            'Forecast': forecast,
            'Confidence Intervals': conf_int,
            'AIC': results.aic,
            'BIC': results.bic
        }
    
    def best_arima_order(self, max_p=3, max_d=2, max_q=3):
        """Find best ARIMA parameters using AIC"""
        best_aic = float('inf')
        best_order = None
        
        for p in range(max_p + 1):
            for d in range(max_d + 1):
                for q in range(max_q + 1):
                    try:
                        model = sm.tsa.ARIMA(self.data, order=(p,d,q))
                        results = model.fit()
                        if results.aic < best_aic:
                            best_aic = results.aic
                            best_order = (p,d,q)
                    except:
                        continue
        
        return best_order, best_aic

# Example usage
data = yf.download('AAPL')['Close']
analyzer = TimeSeriesAnalyzer(data)

# Check stationarity
stationarity = analyzer.check_stationarity()
print("\nStationarity Test Results:")
for key, value in stationarity.items():
    print(f"{key}: {value}")

# Find best ARIMA order
best_order, best_aic = analyzer.best_arima_order()
print(f"\nBest ARIMA Order: {best_order}")
print(f"Best AIC: {best_aic:.2f}")

# Fit model and forecast
results = analyzer.fit_arima(order=best_order)
print("\nForecast next 30 days:")
print(results['Forecast'])

Slide 11: Portfolio Optimization with PyPortfolioOpt

PyPortfolioOpt implements modern portfolio theory for optimal asset allocation. It provides multiple optimization methods including Mean-Variance, Black-Litterman, and Hierarchical Risk Parity approaches.

from pypfopt import EfficientFrontier, risk_models, expected_returns
from pypfopt.discrete_allocation import DiscreteAllocation
import pandas as pd
import numpy as np

class PortfolioOptimizer:
    def __init__(self, prices_df):
        self.prices = prices_df
        self.returns = self.prices.pct_change()
        
    def optimize_portfolio(self, risk_free_rate=0.02):
        # Calculate expected returns and sample covariance matrix
        mu = expected_returns.mean_historical_return(self.prices)
        S = risk_models.sample_cov(self.prices)
        
        # Optimize for maximum Sharpe Ratio
        ef = EfficientFrontier(mu, S)
        weights = ef.maximum_sharpe(risk_free_rate=risk_free_rate)
        cleaned_weights = ef.clean_weights()
        
        # Calculate performance metrics
        performance = {
            'Expected Annual Return': ef.portfolio_performance(risk_free_rate=risk_free_rate)[0],
            'Annual Volatility': ef.portfolio_performance(risk_free_rate=risk_free_rate)[1],
            'Sharpe Ratio': ef.portfolio_performance(risk_free_rate=risk_free_rate)[2]
        }
        
        return cleaned_weights, performance
    
    def get_discrete_allocation(self, weights, total_portfolio_value=100000):
        latest_prices = self.prices.iloc[-1]
        da = DiscreteAllocation(weights, latest_prices, 
                              total_portfolio_value=total_portfolio_value)
        allocation, leftover = da.greedy_portfolio()
        
        return allocation, leftover
    
    def efficient_frontier_points(self, points=100):
        mu = expected_returns.mean_historical_return(self.prices)
        S = risk_models.sample_cov(self.prices)
        
        ef = EfficientFrontier(mu, S)
        
        frontier_returns = []
        frontier_volatilities = []
        
        for target_return in np.linspace(0.05, 0.30, points):
            try:
                ef.efficient_return(target_return)
                ret, vol, _ = ef.portfolio_performance()
                frontier_returns.append(ret)
                frontier_volatilities.append(vol)
                ef = EfficientFrontier(mu, S)  # Reset optimization
            except:
                continue
                
        return frontier_returns, frontier_volatilities

# Example usage
# Download historical data for multiple assets
symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'FB']
prices_df = pd.DataFrame()

for symbol in symbols:
    prices_df[symbol] = yf.download(symbol, start='2023-01-01')['Close']

optimizer = PortfolioOptimizer(prices_df)

# Optimize portfolio
weights, performance = optimizer.optimize_portfolio()

print("\nOptimal Portfolio Weights:")
for asset, weight in weights.items():
    print(f"{asset}: {weight:.4f}")

print("\nPortfolio Performance:")
for metric, value in performance.items():
    print(f"{metric}: {value:.4f}")

# Get discrete allocation
allocation, leftover = optimizer.get_discrete_allocation(weights)
print("\nDiscrete Allocation:")
for asset, shares in allocation.items():
    print(f"{asset}: {shares} shares")
print(f"Funds remaining: ${leftover:.2f}")

Slide 12: Risk-Adjusted Returns Analysis with empyrical

Empyrical provides comprehensive metrics for analyzing trading strategies and portfolio performance, including advanced risk-adjusted return calculations and drawdown analysis.

import empyrical as ep
import pandas as pd
import numpy as np

class PerformanceAnalyzer:
    def __init__(self, returns, benchmark_returns=None):
        self.returns = returns
        self.benchmark_returns = benchmark_returns
        
    def calculate_metrics(self, risk_free=0.0):
        metrics = {
            'Cumulative Returns': ep.cum_returns_final(self.returns),
            'Annual Return': ep.annual_return(self.returns),
            'Annual Volatility': ep.annual_volatility(self.returns),
            'Sharpe Ratio': ep.sharpe_ratio(self.returns, risk_free),
            'Sortino Ratio': ep.sortino_ratio(self.returns, risk_free),
            'Calmar Ratio': ep.calmar_ratio(self.returns),
            'Omega Ratio': ep.omega_ratio(self.returns),
            'Max Drawdown': ep.max_drawdown(self.returns),
            'Value at Risk': self._calculate_var(),
            'Conditional VaR': self._calculate_cvar()
        }
        
        if self.benchmark_returns is not None:
            metrics.update({
                'Alpha': ep.alpha(self.returns, self.benchmark_returns, risk_free),
                'Beta': ep.beta(self.returns, self.benchmark_returns),
                'Information Ratio': ep.information_ratio(self.returns, self.benchmark_returns)
            })
            
        return metrics
    
    def _calculate_var(self, confidence_level=0.95):
        """Calculate Value at Risk"""
        return np.percentile(self.returns, (1 - confidence_level) * 100)
    
    def _calculate_cvar(self, confidence_level=0.95):
        """Calculate Conditional Value at Risk (Expected Shortfall)"""
        var = self._calculate_var(confidence_level)
        return self.returns[self.returns <= var].mean()
    
    def rolling_metrics(self, window=252):
        """Calculate rolling performance metrics"""
        rolling_sharpe = ep.roll_sharpe_ratio(self.returns, window=window)
        rolling_sortino = ep.roll_sortino_ratio(self.returns, window=window)
        rolling_beta = ep.roll_beta(self.returns, self.benchmark_returns, 
                                  window=window) if self.benchmark_returns is not None else None
        
        return pd.DataFrame({
            'Rolling Sharpe': rolling_sharpe,
            'Rolling Sortino': rolling_sortino,
            'Rolling Beta': rolling_beta
        })

# Example usage
# Get returns data
returns = yf.download('SPY', start='2023-01-01')['Close'].pct_change().dropna()
benchmark_returns = yf.download('^GSPC', start='2023-01-01')['Close'].pct_change().dropna()

analyzer = PerformanceAnalyzer(returns, benchmark_returns)

# Calculate performance metrics
metrics = analyzer.calculate_metrics()
print("\nPerformance Metrics:")
for metric, value in metrics.items():
    print(f"{metric}: {value:.4f}")

# Calculate rolling metrics
rolling_metrics = analyzer.rolling_metrics()
print("\nRolling Metrics (last 5 periods):")
print(rolling_metrics.tail())

Slide 13: Deep Learning for Financial Time Series with PyTorch

PyTorch enables the implementation of sophisticated deep learning models for financial time series prediction. This implementation showcases an LSTM model for multi-step forecasting with attention mechanism.

import torch
import torch.nn as nn
import numpy as np
from sklearn.preprocessing import MinMaxScaler
import pandas as pd

class AttentionLSTM(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_layers, output_dim):
        super(AttentionLSTM, self).__init__()
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        
        self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
        self.attention = nn.MultiheadAttention(hidden_dim, num_heads=1)
        self.fc = nn.Linear(hidden_dim, output_dim)
    
    def forward(self, x):
        # LSTM forward pass
        lstm_out, _ = self.lstm(x)
        
        # Self-attention mechanism
        attn_output, _ = self.attention(lstm_out, lstm_out, lstm_out)
        
        # Take the last output for prediction
        out = self.fc(attn_output[:, -1, :])
        return out

class FinancialPredictor:
    def __init__(self, sequence_length=20):
        self.sequence_length = sequence_length
        self.scaler = MinMaxScaler()
        self.model = None
        
    def prepare_data(self, data):
        scaled_data = self.scaler.fit_transform(data.reshape(-1, 1))
        
        sequences = []
        targets = []
        
        for i in range(len(scaled_data) - self.sequence_length):
            seq = scaled_data[i:i + self.sequence_length]
            target = scaled_data[i + self.sequence_length]
            sequences.append(seq)
            targets.append(target)
            
        return torch.FloatTensor(sequences), torch.FloatTensor(targets)
    
    def train_model(self, train_data, epochs=100, learning_rate=0.01):
        X_train, y_train = self.prepare_data(train_data)
        
        self.model = AttentionLSTM(
            input_dim=1,
            hidden_dim=32,
            num_layers=2,
            output_dim=1
        )
        
        criterion = nn.MSELoss()
        optimizer = torch.optim.Adam(self.model.parameters(), lr=learning_rate)
        
        for epoch in range(epochs):
            self.model.train()
            optimizer.zero_grad()
            
            outputs = self.model(X_train)
            loss = criterion(outputs, y_train)
            
            loss.backward()
            optimizer.step()
            
            if (epoch + 1) % 10 == 0:
                print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
    
    def predict(self, test_sequence):
        self.model.eval()
        with torch.no_grad():
            scaled_sequence = self.scaler.transform(test_sequence.reshape(-1, 1))
            X_test = torch.FloatTensor(scaled_sequence).unsqueeze(0)
            prediction = self.model(X_test)
            return self.scaler.inverse_transform(prediction.numpy())

# Example usage
# Get financial data
data = yf.download('AAPL', start='2023-01-01')['Close'].values

# Split data
train_size = int(len(data) * 0.8)
train_data = data[:train_size]
test_data = data[train_size:]

# Initialize and train model
predictor = FinancialPredictor()
predictor.train_model(train_data)

# Make predictions
test_sequence = data[-20:]  # Last 20 days
prediction = predictor.predict(test_sequence)
print(f"\nNext day prediction: ${prediction[0][0]:.2f}")

Slide 14: Additional Resources