Source code for awt_quant.risk.tearsheet

import yfinance as yf
import numpy as np
from scipy.stats import norm
import pandas as pd
# Need to get real interest rate .

[docs] def compute_beta(portfolio_value_series, ticker='^GSPC'): """ Calculate the beta of the portfolio against a benchmark index. Args: portfolio_value_series (pandas.Series): Time series data of portfolio values. ticker (str): Ticker symbol of the benchmark index. Default is S&P 500 ('^GSPC'). Returns: float: Beta value of the portfolio. """ # Ensure the portfolio series is a Pandas Series if not isinstance(portfolio_value_series, pd.Series): raise ValueError("portfolio_value_series must be a pandas Series") # Calculate percent change for the portfolio portfolio_returns = portfolio_value_series.pct_change().dropna() # Check if the portfolio returns series is empty if portfolio_returns.empty: raise ValueError("The portfolio returns series is empty after percent change calculation.") # Download S&P500 data for the same date range as the portfolio start_date = portfolio_returns.index.min().strftime('%Y-%m-%d') end_date = portfolio_returns.index.max().strftime('%Y-%m-%d') sp500_data = yf.download(ticker, start=start_date, end=end_date)['Close'].pct_change().dropna() # Ensure that both series have a common index common_dates = portfolio_returns.index.intersection(sp500_data.index) portfolio_returns = portfolio_returns[common_dates] sp500_returns = sp500_data[common_dates] # Calculate the beta covariance = np.cov(portfolio_returns, sp500_returns)[0, 1] variance = np.var(sp500_returns) beta = covariance / variance return beta
[docs] def common_index(series1, series2): """ Returns the common index values of two series. Args: series1 (pandas.Series): The first series. series2 (pandas.Series): The second series. Returns: pandas.Index: The common index values of the two series. """ return series1.index.intersection(series2.index)
[docs] def risk_tearSheet(data, time_input='2y', risk_free_rate=0.02, confidence_level=0.95, benchmark_ticker='^GSPC'): # Determine the period for the analysis start_date, end_date = None, None if '-' in time_input: start_date, end_date = time_input.split('-') # Handle ticker input for data if isinstance(data, str): data = yf.download(data, start=start_date, end=end_date, period=(None if start_date else time_input))['Close'] # Download benchmark data market_data = yf.download(benchmark_ticker, start=start_date, end=end_date, period=(None if start_date else time_input)) market_returns = market_data['Close'].pct_change().dropna() # Calculate various metrics daily_returns = data.pct_change().dropna() annual_return = (data[-1] / data[0]) ** (252 / len(data)) - 1 cumulative_returns = data[-1] / data[0] - 1 annual_volatility = np.std(daily_returns) * np.sqrt(252) sharpe_ratio = (daily_returns.mean() - risk_free_rate / 252) / daily_returns.std() * np.sqrt(252) rolling_max = data.cummax() drawdown = (data - rolling_max) / rolling_max max_drawdown = drawdown.min() calmar_ratio = annual_return / abs(max_drawdown) # stability = np.exp(np.mean(np.log(1 + daily_returns))) - 1 omega_ratio = np.sum(daily_returns[daily_returns > 0]) / np.abs(np.sum(daily_returns[daily_returns < 0])) downside_returns = daily_returns[daily_returns < 0] sortino_ratio = (annual_return - risk_free_rate / 252) / np.sqrt(np.mean(downside_returns ** 2) * 252) skewness = daily_returns.skew() kurtosis = daily_returns.kurtosis() tail_ratio = np.abs(daily_returns[daily_returns < 0].mean()) / daily_returns[daily_returns > 0].mean() common_sense_ratio = annual_return / abs(max_drawdown) gross_leverage = (1 + daily_returns.abs()).mean() daily_turnover = np.mean(daily_returns.abs() / gross_leverage) semi_std_dev = np.sqrt(np.mean(np.square(daily_returns[daily_returns < daily_returns.mean()]))) standard_error = np.std(daily_returns) / np.sqrt(len(daily_returns)) var_99 = norm.ppf(1 - 0.01, daily_returns.mean(), daily_returns.std()) var_95 = norm.ppf(1 - 0.05, daily_returns.mean(), daily_returns.std()) ivar = np.mean(daily_returns[daily_returns <= var_95]) cvar = daily_returns[daily_returns <= var_95].mean() var_gaussian = -norm.ppf(1 - confidence_level, daily_returns.mean(), daily_returns.std()) # Calculate Beta and Alpha beta = compute_beta(data, ticker=benchmark_ticker) benchmark_annual_return = (market_returns[-1] / market_returns[0]) ** (252 / len(market_returns)) - 1 alpha = annual_return - (risk_free_rate + beta * (benchmark_annual_return - risk_free_rate)) # Historical Volatility yearly_volatility = np.std(daily_returns) * np.sqrt(252) monthly_volatility = np.std(daily_returns) * np.sqrt(21) # Pain Gain Ratio gains = daily_returns[daily_returns > 0].sum() pains = -daily_returns[daily_returns < 0].sum() pain_gain_ratio = gains / pains # Downside Deviation downside_returns = daily_returns[daily_returns < 0] downside_deviation = np.sqrt(np.mean(np.square(downside_returns))) # Upside Potential Ratio upside_returns = daily_returns[daily_returns > 0] upside_potential_ratio = np.mean(upside_returns) / downside_deviation # Treynor Ratio treynor_ratio = (annual_return - risk_free_rate) / beta # Tracking Error tracking_error = np.sqrt(np.mean(np.square(daily_returns - market_returns))) # Information Ratio information_ratio = (annual_return - benchmark_annual_return) / tracking_error # R-Squared correlation_matrix = np.corrcoef(daily_returns, market_returns) r_squared = correlation_matrix[0, 1] ** 2 # Maximum Drawdown Duration drawdown_end = np.argmin(drawdown) # End of the max drawdown period drawdown_start = np.argmax(data[:drawdown_end]) # Start of the max drawdown period max_drawdown_duration = drawdown_end - drawdown_start # Compile metrics metrics = { 'Annual Return': annual_return, 'Cumulative Returns': cumulative_returns, 'Annual Volatility': annual_volatility, 'Sharpe Ratio': sharpe_ratio, 'Calmar Ratio': calmar_ratio, # 'Stability': stability, 'Omega Ratio': omega_ratio, 'Sortino Ratio': sortino_ratio, 'Skewness': skewness, 'Kurtosis': kurtosis, 'Tail Ratio': tail_ratio, 'Common Sense Ratio': common_sense_ratio, 'Gross Leverage': gross_leverage, 'Daily Turnover': daily_turnover, 'Semi Standard Deviation': semi_std_dev, 'Standard Error': standard_error, 'VaR 99%': var_99, 'VaR 95%': var_95, 'IVaR': ivar, 'CVaR': cvar, f'Gaussian VaR({confidence_level})': var_gaussian, 'Max Drawdown': max_drawdown, 'Beta': beta, 'Alpha': alpha } # Adding new metrics to the dictionary metrics['Yearly Volatility'] = yearly_volatility metrics['Monthly Volatility'] = monthly_volatility metrics['Pain Gain Ratio'] = pain_gain_ratio metrics['Downside Deviation'] = downside_deviation metrics['Upside Potential Ratio'] = upside_potential_ratio # Adding new metrics to the dictionary metrics.update({ 'Treynor Ratio': treynor_ratio, 'Tracking Error': tracking_error, 'Information Ratio': information_ratio, 'R-Squared': r_squared, 'Max Drawdown Duration': max_drawdown_duration, # 'Active Share': active_share, # Uncomment when holdings data is available }) return metrics
# Example usage if __name__ == "__main__":
[docs] data = yf.download('AAPL', start='2020-01-01', end='2023-01-01')['Close']
metrics = risk_tearSheet(data) print(metrics)