Financial functions for Python providing performance analysis, risk metrics, portfolio optimization, and data retrieval for quantitative finance
—
Calculate comprehensive risk measures including drawdowns, volatility-based ratios, and downside risk metrics for portfolio evaluation. Provides essential risk assessment tools for quantitative finance.
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
"""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
"""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
"""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
"""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
"""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%}")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()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))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