CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-ffn

Financial functions for Python providing performance analysis, risk metrics, portfolio optimization, and data retrieval for quantitative finance

Pending
Overview
Eval results
Files

risk-metrics.mddocs/

Risk Metrics

Calculate comprehensive risk measures including drawdowns, volatility-based ratios, and downside risk metrics for portfolio evaluation. Provides essential risk assessment tools for quantitative finance.

Capabilities

Drawdown Analysis

Measure and analyze portfolio drawdowns from peak values.

def to_drawdown_series(prices):
    """
    Calculate drawdown series showing percentage decline from peak values.

    Parameters:
    - prices (pd.Series or pd.DataFrame): Price series

    Returns:
    pd.Series or pd.DataFrame: Drawdown series (negative values)
    """

def calc_max_drawdown(prices):
    """
    Calculate maximum drawdown over the entire period.

    Parameters:
    - prices (pd.Series): Price series

    Returns:
    float: Maximum drawdown as positive decimal (e.g., 0.15 for 15% drawdown)
    """

def drawdown_details(drawdown, index_type=pd.DatetimeIndex):
    """
    Detailed drawdown analysis including duration and recovery periods.

    Parameters:
    - drawdown (pd.Series): Drawdown series
    - index_type: Index type for date handling

    Returns:
    pd.DataFrame: Detailed drawdown statistics
    """

Volatility-Based Risk Ratios

Calculate risk-adjusted return metrics using volatility measures.

def calc_sharpe(returns, rf=0.0, nperiods=None, annualize=True):
    """
    Calculate Sharpe ratio (excess return per unit of volatility).

    Parameters:
    - returns (pd.Series): Return series
    - rf (float): Risk-free rate (default: 0.0)
    - nperiods (int): Periods for annualization (inferred if None)
    - annualize (bool): Whether to annualize the ratio

    Returns:
    float: Sharpe ratio
    """

def calc_information_ratio(returns, benchmark_returns):
    """
    Calculate Information ratio (active return per unit of tracking error).

    Parameters:
    - returns (pd.Series): Portfolio returns
    - benchmark_returns (pd.Series): Benchmark returns

    Returns:
    float: Information ratio
    """

def calc_risk_return_ratio(returns):
    """
    Calculate risk-return ratio (mean return divided by volatility).

    Parameters:
    - returns (pd.Series): Return series

    Returns:
    float: Risk-return ratio
    """

Downside Risk Metrics

Risk measures focused on negative returns and downside volatility.

def calc_sortino_ratio(returns, rf=0.0, nperiods=None, annualize=True):
    """
    Calculate Sortino ratio (excess return per unit of downside deviation).

    Parameters:
    - returns (pd.Series): Return series
    - rf (float): Risk-free rate (default: 0.0)
    - nperiods (int): Periods for annualization (inferred if None)
    - annualize (bool): Whether to annualize the ratio

    Returns:
    float: Sortino ratio
    """

def calc_calmar_ratio(prices):
    """
    Calculate Calmar ratio (CAGR divided by maximum drawdown).

    Parameters:
    - prices (pd.Series): Price series

    Returns:
    float: Calmar ratio
    """

Ulcer Index Metrics

Alternative risk measures based on drawdown depth and duration.

def to_ulcer_index(prices):
    """
    Calculate Ulcer Index (root-mean-square of drawdowns).

    Parameters:
    - prices (pd.Series): Price series

    Returns:
    float: Ulcer Index
    """

def to_ulcer_performance_index(prices, rf=0.0, nperiods=None):
    """
    Calculate Ulcer Performance Index (excess return per unit of Ulcer Index).

    Parameters:
    - prices (pd.Series): Price series
    - rf (float): Risk-free rate (default: 0.0)
    - nperiods (int): Periods for annualization (inferred if None)

    Returns:
    float: Ulcer Performance Index
    """

Statistical Risk Utilities

Supporting functions for excess returns and risk calculations.

def to_excess_returns(returns, rf, nperiods=None):
    """
    Calculate excess returns over risk-free rate.

    Parameters:
    - returns (pd.Series): Return series
    - rf (float): Risk-free rate
    - nperiods (int): Periods for annualization (inferred if None)

    Returns:
    pd.Series: Excess return series
    """

Usage Examples

Basic Risk Metrics

import ffn

# Download price data
prices = ffn.get('AAPL', start='2020-01-01')['AAPL']
returns = ffn.to_returns(prices).dropna()

# Calculate key risk metrics
sharpe = ffn.calc_sharpe(returns, rf=0.02)
sortino = ffn.calc_sortino_ratio(returns, rf=0.02)
calmar = ffn.calc_calmar_ratio(prices)
max_dd = ffn.calc_max_drawdown(prices)

print(f"Sharpe Ratio: {sharpe:.2f}")
print(f"Sortino Ratio: {sortino:.2f}")
print(f"Calmar Ratio: {calmar:.2f}")
print(f"Max Drawdown: {max_dd:.2%}")

Drawdown Analysis

import ffn

# Analyze drawdowns
prices = ffn.get('AAPL', start='2020-01-01')['AAPL']
drawdowns = ffn.to_drawdown_series(prices)

# Get detailed drawdown statistics
dd_details = ffn.drawdown_details(drawdowns)
print("Drawdown Details:")
print(dd_details)

# Plot drawdown series
import matplotlib.pyplot as plt
drawdowns.plot(title='AAPL Drawdowns', figsize=(12, 6))
plt.ylabel('Drawdown %')
plt.show()

Advanced Risk Analysis

import ffn

# Multi-asset risk comparison
prices = ffn.get('AAPL,MSFT,GOOGL', start='2020-01-01')
returns = ffn.to_returns(prices).dropna()

# Calculate Ulcer Index for each asset
ulcer_indices = {}
for col in prices.columns:
    ulcer_indices[col] = ffn.to_ulcer_index(prices[col])

print("Ulcer Index by Asset:")
for asset, ui in ulcer_indices.items():
    print(f"{asset}: {ui:.2f}")

# Risk-adjusted performance comparison
risk_metrics = {}
for col in returns.columns:
    risk_metrics[col] = {
        'Sharpe': ffn.calc_sharpe(returns[col], rf=0.02),
        'Sortino': ffn.calc_sortino_ratio(returns[col], rf=0.02),
        'Calmar': ffn.calc_calmar_ratio(prices[col]),
        'Max DD': ffn.calc_max_drawdown(prices[col])
    }

import pandas as pd
risk_df = pd.DataFrame(risk_metrics).T
print("\nRisk Metrics Comparison:")
print(risk_df.round(3))

Portfolio Risk Assessment

import ffn
import pandas as pd

# Create equal-weight portfolio
prices = ffn.get('AAPL,MSFT,GOOGL', start='2020-01-01')
returns = ffn.to_returns(prices).dropna()

# Portfolio returns
weights = pd.Series([1/3, 1/3, 1/3], index=returns.columns)
portfolio_returns = (returns * weights).sum(axis=1)
portfolio_prices = ffn.to_price_index(portfolio_returns, start=100)

# Portfolio risk metrics
portfolio_sharpe = ffn.calc_sharpe(portfolio_returns, rf=0.02)
portfolio_sortino = ffn.calc_sortino_ratio(portfolio_returns, rf=0.02)
portfolio_max_dd = ffn.calc_max_drawdown(portfolio_prices)

print(f"Portfolio Sharpe: {portfolio_sharpe:.2f}")
print(f"Portfolio Sortino: {portfolio_sortino:.2f}")
print(f"Portfolio Max DD: {portfolio_max_dd:.2%}")

# Compare to individual assets
individual_sharpe = [ffn.calc_sharpe(returns[col], rf=0.02) for col in returns.columns]
avg_individual_sharpe = sum(individual_sharpe) / len(individual_sharpe)

print(f"Average Individual Sharpe: {avg_individual_sharpe:.2f}")
print(f"Portfolio Benefit: {portfolio_sharpe - avg_individual_sharpe:.2f}")

Install with Tessl CLI

npx tessl i tessl/pypi-ffn

docs

data-retrieval.md

data-utilities.md

index.md

pandas-extensions.md

performance-analysis.md

portfolio-optimization.md

return-calculations.md

risk-metrics.md

statistical-analysis.md

tile.json