Spaces:
Sleeping
Sleeping
| import yfinance as yf | |
| import numpy as np | |
| import pandas as pd | |
| import streamlit as st | |
| from sklearn.linear_model import RANSACRegressor, LinearRegression | |
| from scipy.stats import linregress | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import datetime | |
| # Helper function to fetch stock data | |
| def fetch_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame: | |
| """Fetch stock data from Yahoo Finance.""" | |
| data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False) | |
| if isinstance(data.columns, pd.MultiIndex): | |
| data.columns = data.columns.get_level_values(0) | |
| return data | |
| # Helper function to plot rolling volatility and volatility of volatility | |
| def plot_rolling_volatility(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| data['Rolling_Volatility'] = data['Return'].rolling(window=window).std() * np.sqrt(252) | |
| data['Vol_of_Vol'] = data['Rolling_Volatility'].rolling(window=window).std() | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Volatility', 'Volatility of Volatility')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Rolling_Volatility'], mode='lines', name='Rolling Volatility', line=dict(color='red')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Vol_of_Vol'], mode='lines', name='Volatility of Volatility', line=dict(color='blue')), row=3, col=1) | |
| fig.update_layout(title='Rolling Volatility and Volatility of Volatility', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Sharpe ratio | |
| def plot_rolling_sharpe(data: pd.DataFrame, window: int, risk_free_rate: float) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| rolling_sharpe = np.sqrt(252) * (data['Return'].rolling(window).mean() - risk_free_rate) / data['Return'].rolling(window).std() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Sharpe Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=rolling_sharpe, mode='lines', name='Rolling Sharpe Ratio', line=dict(color='green')), row=2, col=1) | |
| fig.add_hline(y=1, line=dict(color='red', dash='dash'), row=2, col=1) | |
| fig.update_layout(title='Rolling Sharpe Ratio with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Treynor ratio | |
| def plot_rolling_treynor(data: pd.DataFrame, market_data: pd.DataFrame, window: int, risk_free_rate: float) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| market_data['Market_Return'] = market_data['Close'].pct_change() | |
| # Align indices | |
| market_data = market_data.reindex(data.index, method='ffill') | |
| aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna() | |
| covariance = aligned_data['Return'].rolling(window=window).cov(aligned_data['Market_Return']) | |
| variance = aligned_data['Market_Return'].rolling(window=window).var() | |
| rolling_beta = covariance / variance | |
| avg_rolling_returns = aligned_data['Return'].rolling(window=window).mean() | |
| data['Rolling_Treynor_Ratio'] = (avg_rolling_returns - risk_free_rate) / rolling_beta | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Benchmark Price', 'Rolling Treynor Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=market_data.index, y=market_data['Close'], mode='lines', name='Benchmark Price', line=dict(color='blue')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Rolling_Treynor_Ratio'], mode='lines', name='Rolling Treynor Ratio', line=dict(color='red')), row=3, col=1) | |
| fig.update_layout(title='Rolling Treynor Ratio with Stock Price and Benchmark', xaxis_title='Date') | |
| return fig | |
| # Helper function to calculate and plot rolling beta | |
| def plot_rolling_beta(data: pd.DataFrame, market_data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| market_data['Market_Return'] = market_data['Close'].pct_change() | |
| # Align dates and remove rows with missing data | |
| aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna() | |
| ransac = RANSACRegressor() | |
| ols = LinearRegression() | |
| rolling_beta_ransac = [] | |
| rolling_beta_ols = [] | |
| for i in range(len(aligned_data) - window + 1): | |
| X = aligned_data['Market_Return'].iloc[i:i+window].values.reshape(-1, 1) | |
| y = aligned_data['Return'].iloc[i:i+window].values | |
| ransac.fit(X, y) | |
| beta_ransac = ransac.estimator_.coef_[0] | |
| rolling_beta_ransac.append(beta_ransac) | |
| ols.fit(X, y) | |
| beta_ols = ols.coef_[0] | |
| rolling_beta_ols.append(beta_ols) | |
| # Convert lists to series with appropriate index | |
| rolling_beta_ransac = pd.Series(rolling_beta_ransac, index=aligned_data.index[window-1:]) | |
| rolling_beta_ols = pd.Series(rolling_beta_ols, index=aligned_data.index[window-1:]) | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Benchmark Price', 'Rolling Beta (RANSAC & OLS)')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=market_data.index, y=market_data['Close'], mode='lines', name='Benchmark Price', line=dict(color='blue')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_beta_ransac.index, y=rolling_beta_ransac, mode='lines', name='Rolling Beta (RANSAC)', line=dict(color='red')), row=3, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_beta_ols.index, y=rolling_beta_ols, mode='lines', name='Rolling Beta (OLS)', line=dict(color='green')), row=3, col=1) | |
| fig.update_layout(title='Rolling Beta with Stock Price and Benchmark', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Jensen's alpha | |
| def plot_rolling_alpha(data: pd.DataFrame, market_data: pd.DataFrame, window: int, risk_free_rate: float) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| market_data['Market_Return'] = market_data['Close'].pct_change() | |
| # Align indices | |
| market_data = market_data.reindex(data.index, method='ffill') | |
| aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna() | |
| rolling_alpha = [] | |
| for i in range(len(aligned_data) - window + 1): | |
| window_stock = aligned_data['Return'].iloc[i:i + window] | |
| window_market = aligned_data['Market_Return'].iloc[i:i + window] | |
| if len(window_stock) > 1 and window_market.var() != 0: # Ensure valid data | |
| beta = window_stock.cov(window_market) / window_market.var() | |
| expected_return = risk_free_rate + beta * (window_market.mean() - risk_free_rate) | |
| alpha = window_stock.mean() - expected_return | |
| rolling_alpha.append(alpha) | |
| else: | |
| rolling_alpha.append(np.nan) | |
| rolling_alpha = pd.Series(rolling_alpha, index=aligned_data.index[window-1:]) | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Jensen\'s Alpha')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_alpha.index, y=rolling_alpha, mode='lines', name='Rolling Jensen\'s Alpha', line=dict(color='green')), row=2, col=1) | |
| fig.update_layout(title='Rolling Jensen\'s Alpha with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Value at Risk (VaR) | |
| def plot_rolling_var(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| rolling_var_90 = data['Return'].rolling(window).quantile(0.10).dropna() | |
| rolling_var_95 = data['Return'].rolling(window).quantile(0.05).dropna() | |
| rolling_var_97 = data['Return'].rolling(window).quantile(0.03).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling VaR')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_var_90.index, y=rolling_var_90, mode='lines', name='Rolling VaR 90%', line=dict(color='cyan')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_var_95.index, y=rolling_var_95, mode='lines', name='Rolling VaR 95%', line=dict(color='blue')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_var_97.index, y=rolling_var_97, mode='lines', name='Rolling VaR 97%', line=dict(color='purple')), row=2, col=1) | |
| fig.update_layout(title='Rolling VaR with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Conditional VaR (CVaR) | |
| def plot_rolling_cvar(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| def conditional_var(x, alpha=0.05): | |
| var = np.nanpercentile(x, alpha * 100) | |
| return np.mean(x[x < var]) if len(x[x < var]) > 0 else np.nan | |
| rolling_cvar_95 = data['Return'].rolling(window).apply(conditional_var, raw=True).dropna() | |
| rolling_cvar_90 = data['Return'].rolling(window).apply(lambda x: conditional_var(x, alpha=0.1), raw=True).dropna() | |
| rolling_cvar_97 = data['Return'].rolling(window).apply(lambda x: conditional_var(x, alpha=0.03), raw=True).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling CVaR')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_cvar_95.index, y=rolling_cvar_95, mode='lines', name='Rolling CVaR 95%', line=dict(color='blue')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_cvar_90.index, y=rolling_cvar_90, mode='lines', name='Rolling CVaR 90%', line=dict(color='green')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_cvar_97.index, y=rolling_cvar_97, mode='lines', name='Rolling CVaR 97%', line=dict(color='orange')), row=2, col=1) | |
| fig.update_layout(title='Rolling CVaR with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Tail Ratio | |
| def plot_rolling_tail_ratio(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| tail_ratio = data['Return'].rolling(window).apply(lambda x: np.abs(np.percentile(x.dropna(), 95)) / np.abs(np.percentile(x.dropna(), 5)) if len(x.dropna()) > 0 else np.nan).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Tail Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=tail_ratio.index, y=tail_ratio, mode='lines', name='Tail Ratio', line=dict(color='purple')), row=2, col=1) | |
| fig.add_hline(y=1, line=dict(color='red', dash='dash'), row=2, col=1) | |
| fig.update_layout(title='Rolling Tail Ratio with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Omega Ratio | |
| def plot_rolling_omega(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| MAR = 0 # Minimum Acceptable Return | |
| omega_ratio = data['Return'].rolling(window).apply(lambda x: np.sum(x[x > MAR] - MAR) / np.sum(MAR - x[x < MAR]) if np.sum(x[x < MAR]) != 0 else np.inf).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Omega Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=omega_ratio.index, y=omega_ratio, mode='lines', name='Omega Ratio', line=dict(color='green')), row=2, col=1) | |
| fig.add_hline(y=1, line=dict(color='red', dash='dash'), row=2, col=1) | |
| fig.update_layout(title='Rolling Omega Ratio with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Sortino Ratio | |
| def plot_rolling_sortino(data: pd.DataFrame, window: int, MAR: float) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| sortino_ratio = data['Return'].rolling(window).apply(lambda x: np.sqrt(252) * np.mean(x - MAR) / np.sqrt(np.mean(np.minimum(0, x - MAR) ** 2)) if np.mean(np.minimum(0, x - MAR) ** 2) > 0 else np.inf).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Sortino Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=sortino_ratio.index, y=sortino_ratio, mode='lines', name='Sortino Ratio', line=dict(color='green')), row=2, col=1) | |
| fig.update_layout(title='Rolling Sortino Ratio with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Calmar Ratio | |
| def plot_rolling_calmar(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| calmar_ratio = data['Return'].rolling(window).apply(lambda x: (1 + x).prod() ** (252.0 / len(x)) / np.abs(np.min((1 + x).cumprod() / (1 + x).cumprod().cummax()) - 1) if np.min((1 + x).cumprod() / (1 + x).cumprod().cummax()) < 1 else np.inf).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Calmar Ratio')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=calmar_ratio.index, y=calmar_ratio, mode='lines', name='Calmar Ratio', line=dict(color='green')), row=2, col=1) | |
| fig.update_layout(title='Rolling Calmar Ratio with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling stability | |
| def plot_rolling_stability(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| stability = data['Return'].rolling(window).apply(lambda x: np.std(np.log1p(x.dropna()).cumsum() - linregress(np.arange(len(x.dropna())), np.log1p(x.dropna()).cumsum()).intercept - linregress(np.arange(len(x.dropna())), np.log1p(x.dropna()).cumsum()).slope * np.arange(len(x.dropna()))) if len(x.dropna()) > 1 else np.nan).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Stability')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=stability.index, y=stability, mode='lines', name='Stability', line=dict(color='purple')), row=2, col=1) | |
| fig.update_layout(title='Rolling Stability of Returns with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling maximum drawdown | |
| def plot_rolling_drawdown(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| rolling_cumulative = (1 + data['Return']).cumprod() | |
| rolling_max = rolling_cumulative.rolling(window, min_periods=1).max() | |
| rolling_drawdown = (rolling_cumulative - rolling_max) / rolling_max | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Maximum Drawdown')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=rolling_drawdown.index, y=rolling_drawdown, mode='lines', name='Maximum Drawdown', line=dict(color='red')), row=2, col=1) | |
| fig.update_layout(title='Rolling Maximum Drawdown with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to calculate and plot rolling capture ratios | |
| def plot_rolling_capture(data: pd.DataFrame, market_data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| market_data['Market_Return'] = market_data['Close'].pct_change() | |
| # Align indices | |
| market_data = market_data.reindex(data.index, method='ffill') | |
| aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna() | |
| def calculate_capture(stock_returns, market_returns, is_upside=True): | |
| if is_upside: | |
| relevant_returns = stock_returns[market_returns > 0] | |
| relevant_market_returns = market_returns[market_returns > 0] | |
| else: | |
| relevant_returns = stock_returns[market_returns < 0] | |
| relevant_market_returns = market_returns[market_returns < 0] | |
| return relevant_returns.sum() / relevant_market_returns.sum() if len(relevant_market_returns) > 0 else np.nan | |
| def compute_rolling_captures(stock_returns, market_returns, window): | |
| upside_captures = [] | |
| downside_captures = [] | |
| for idx in range(len(stock_returns) - window + 1): | |
| current_window_stock = stock_returns.iloc[idx: idx + window] | |
| current_window_market = market_returns.iloc[idx: idx + window] | |
| if len(current_window_market[current_window_market > 0]) == 0 or len(current_window_market[current_window_market < 0]) == 0: | |
| upside = np.nan | |
| downside = np.nan | |
| else: | |
| upside = calculate_capture(current_window_stock, current_window_market, is_upside=True) | |
| downside = calculate_capture(current_window_stock, current_window_market, is_upside=False) | |
| upside_captures.append(upside) | |
| downside_captures.append(downside) | |
| # Padding the initial values with NaNs to match index length | |
| nan_padding = [np.nan] * (window - 1) | |
| upside_captures = pd.Series(nan_padding + upside_captures, index=stock_returns.index) | |
| downside_captures = pd.Series(nan_padding + downside_captures, index=stock_returns.index) | |
| return upside_captures, downside_captures | |
| data['rolling_upside_capture'], data['rolling_downside_capture'] = compute_rolling_captures( | |
| aligned_data['Return'], aligned_data['Market_Return'], window | |
| ) | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Capture Ratios')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['rolling_upside_capture'], mode='lines', name='Rolling Upside Capture', line=dict(color='green')), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['rolling_downside_capture'], mode='lines', name='Rolling Downside Capture', line=dict(color='red')), row=2, col=1) | |
| fig.update_layout(title='Rolling Capture Ratios with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Helper function to plot rolling Pain Index | |
| def plot_rolling_pain_index(data: pd.DataFrame, window: int) -> go.Figure: | |
| data['Return'] = data['Close'].pct_change() | |
| cumulative_return = (1 + data['Return']).cumprod() | |
| running_max = cumulative_return.cummax() | |
| drawdown = (cumulative_return - running_max) / running_max | |
| pain_index = drawdown.rolling(window).apply(lambda x: np.mean(x[x < 0]) if len(x[x < 0]) > 0 else 0).dropna() | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, | |
| subplot_titles=('Close Price', 'Rolling Pain Index')) | |
| fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='grey')), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=pain_index.index, y=pain_index, mode='lines', name='Pain Index', line=dict(color='red')), row=2, col=1) | |
| fig.update_layout(title='Rolling Pain Index with Stock Price', xaxis_title='Date') | |
| return fig | |
| # Streamlit app | |
| st.set_page_config(page_title="Dynamic Risk Management Indicators", layout="wide") | |
| st.title('Dynamic Risk Management Indicators') | |
| st.sidebar.title("Input Parameters") | |
| # Setting today's date plus one day | |
| today_plus_one = pd.to_datetime(datetime.datetime.now().date() + pd.Timedelta(days=1)) | |
| # Sidebar for ticker and dates inside an expander | |
| with st.sidebar.expander("Ticker and Dates", expanded=True): | |
| ticker = st.text_input('Enter Stock Ticker', 'VOW.DE', help="Enter the stock or crypto ticker symbol (e.g., AAPL).") | |
| start_date = st.date_input('Start Date', pd.to_datetime('2010-01-01'), help="Select the start date for the data analysis.") | |
| end_date = st.date_input('End Date', today_plus_one, help="Select the end date for the data analysis.") | |
| # Sidebar for method selection inside an expander | |
| with st.sidebar.expander("Select Method", expanded=True): | |
| selected = st.radio("Select Indicator", | |
| ["Rolling Volatility", "Rolling Sharpe Ratio", "Rolling Treynor Ratio", "Rolling Beta", "Rolling Jensen's Alpha", | |
| "Rolling Value at Risk", "Rolling Conditional VaR", "Rolling Tail Ratio", "Rolling Omega Ratio", | |
| "Rolling Sortino Ratio", "Rolling Calmar Ratio", "Rolling Stability", "Rolling Maximum Drawdown", | |
| "Rolling Capture Ratios", "Rolling Pain Index"], | |
| help="Choose the financial risk indicator you want to calculate and analyze.") | |
| # Sidebar for input parameters | |
| window_size = st.sidebar.number_input('Rolling Window Size (Days)', min_value=1, value=252, | |
| help="Enter the number of days to use for the rolling window in the selected risk indicator calculation.") | |
| # Fetch data | |
| if 'data' not in st.session_state or st.sidebar.button('Fetch Data'): | |
| data = fetch_stock_data(ticker, start_date, end_date) | |
| if data.empty: | |
| st.error(f"No data returned for {ticker} from {start_date} to {end_date}") | |
| else: | |
| st.session_state.data = data | |
| if 'data' in st.session_state and not st.session_state.data.empty: | |
| data = st.session_state.data | |
| # Additional input for methods requiring benchmark or risk-free rate | |
| if selected in ["Rolling Treynor Ratio", "Rolling Beta", "Rolling Jensen's Alpha", "Rolling Capture Ratios"]: | |
| benchmark_ticker = st.sidebar.text_input('Enter Benchmark Ticker', '^GSPC', | |
| help="Enter the ticker symbol for the benchmark index (e.g., ^GSPC for S&P 500).") | |
| if 'market_data' not in st.session_state or st.sidebar.button('Fetch Market Data'): | |
| market_data = fetch_stock_data(benchmark_ticker, start_date, end_date) | |
| if market_data.empty: | |
| st.error(f"No data returned for {benchmark_ticker} from {start_date} to {end_date}") | |
| else: | |
| st.session_state.market_data = market_data | |
| if 'market_data' in st.session_state and not st.session_state.market_data.empty: | |
| market_data = st.session_state.market_data | |
| if selected in ["Rolling Sharpe Ratio", "Rolling Treynor Ratio", "Rolling Jensen's Alpha", "Rolling Sortino Ratio"]: | |
| risk_free_rate = st.sidebar.number_input('Risk-Free Rate (as a decimal)', min_value=0.0, value=0.0, | |
| help="Enter the risk-free rate as a decimal (e.g., 0.01 for 1%).") | |
| if selected == "Rolling Sortino Ratio": | |
| MAR = st.sidebar.number_input('Minimum Acceptable Return (MAR, as a decimal)', min_value=0.0, value=0.0, | |
| help="Enter the Minimum Acceptable Return (MAR) as a decimal (e.g., 0.02 for 2%).") | |
| # Display results based on the selected method | |
| if selected == "Rolling Volatility": | |
| st.markdown(""" | |
| ### Rolling Volatility | |
| This method calculates the rolling volatility and the volatility of volatility. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| where `(P_t)` is the closing price at time `(t)`. | |
| 2. **Calculate Rolling Volatility:** | |
| - Compute the rolling standard deviation of the returns over a specified window and annualize it: | |
| """) | |
| st.latex(r''' | |
| \text{Rolling Volatility}_t = \sqrt{252} \times \text{std}(\text{Return}_{t-n:t}) | |
| ''') | |
| st.markdown(""" | |
| where `(n)` is the window size. | |
| 3. **Calculate Volatility of Volatility:** | |
| - Compute the rolling standard deviation of the rolling volatility over the specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Volatility of Volatility}_t = \text{std}(\text{Rolling Volatility}_{t-n:t}) | |
| ''') | |
| fig = plot_rolling_volatility(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Sharpe Ratio": | |
| st.markdown(""" | |
| ### Rolling Sharpe Ratio | |
| This method calculates the rolling Sharpe Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| where `(P_t)` is the closing price at time `(t)`. | |
| 2. **Calculate Rolling Average Return:** | |
| - Compute the rolling mean of the returns over the specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Rolling Average Return}_t = \text{mean}(\text{Return}_{t-n:t}) | |
| ''') | |
| st.markdown(""" | |
| where `(n)` is the window size. | |
| 3. **Calculate Rolling Standard Deviation:** | |
| - Compute the rolling standard deviation of the returns over the specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Rolling Std Dev}_t = \text{std}(\text{Return}_{t-n:t}) | |
| ''') | |
| st.markdown(""" | |
| 4. **Calculate Rolling Sharpe Ratio:** | |
| - Annualize the Sharpe Ratio: | |
| """) | |
| st.latex(r''' | |
| \text{Rolling Sharpe Ratio}_t = \frac{\text{Rolling Average Return}_t - R_f}{\text{Rolling Std Dev}_t} \times \sqrt{252} | |
| ''') | |
| st.markdown(""" | |
| where `(R_f)` is the risk-free rate. | |
| """) | |
| fig = plot_rolling_sharpe(data, window_size, risk_free_rate) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Treynor Ratio" and 'market_data' in st.session_state: | |
| st.markdown(""" | |
| ### Rolling Treynor Ratio | |
| This method calculates the rolling Treynor Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock and the benchmark: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Beta:** | |
| - Compute the rolling covariance between the stock and benchmark returns, and divide by the rolling variance of the benchmark returns to get the rolling beta: | |
| """) | |
| st.latex(r''' | |
| \beta_t = \frac{\text{Cov}(\text{Return}_{\text{stock}, t}, \text{Return}_{\text{benchmark}, t})}{\text{Var}(\text{Return}_{\text{benchmark}, t})} | |
| ''') | |
| st.markdown(""" | |
| 3. **Calculate Average Rolling Returns:** | |
| - Compute the rolling mean of the stock returns over the same window: | |
| """) | |
| st.latex(r''' | |
| \text{Average Rolling Return}_t = \frac{1}{n} \sum_{i=t-n+1}^{t} \text{Return}_{\text{stock}, i} | |
| ''') | |
| st.markdown(""" | |
| 4. **Calculate Treynor Ratio:** | |
| - Compute the Treynor Ratio using the risk-free rate `(R_f)`: | |
| """) | |
| st.latex(r''' | |
| \text{Treynor Ratio}_t = \frac{\text{Average Rolling Return}_t - R_f}{\beta_t} | |
| ''') | |
| fig = plot_rolling_treynor(data, market_data, window_size, risk_free_rate) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Beta" and 'market_data' in st.session_state: | |
| st.markdown(""" | |
| ### Rolling Beta | |
| This method calculates the rolling beta of a stock's returns against a benchmark using RANSAC and OLS methods. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock and the benchmark: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling Beta using OLS:** | |
| - Perform a linear regression of the stock returns against the benchmark returns over a specified window: | |
| """) | |
| st.latex(r''' | |
| \beta_{OLS} = \frac{\text{Cov}(\text{Return}_{\text{stock}}, \text{Return}_{\text{benchmark}})}{\text{Var}(\text{Return}_{\text{benchmark}})} | |
| ''') | |
| st.markdown(""" | |
| 3. **Calculate Rolling Beta using RANSAC:** | |
| - Use the RANSAC algorithm to robustly estimate the beta, reducing the influence of outliers: | |
| """) | |
| st.latex(r''' | |
| \beta_{RANSAC} = \text{RANSAC}(\text{Return}_{\text{benchmark}}, \text{Return}_{\text{stock}}) | |
| ''') | |
| fig = plot_rolling_beta(data, market_data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Jensen's Alpha" and 'market_data' in st.session_state: | |
| st.markdown(""" | |
| ### Rolling Jensen's Alpha | |
| This method calculates the rolling Jensen's Alpha. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock and the benchmark: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Beta:** | |
| - Compute the rolling beta of the stock returns against the benchmark returns over a specified window: | |
| """) | |
| st.latex(r''' | |
| \beta_t = \frac{\text{Cov}(\text{Return}_{\text{stock}, t}, \text{Return}_{\text{benchmark}, t})}{\text{Var}(\text{Return}_{\text{benchmark}, t})} | |
| ''') | |
| st.markdown(""" | |
| 3. **Calculate Expected Return:** | |
| - Compute the expected return of the stock based on the CAPM model: | |
| """) | |
| st.latex(r''' | |
| \text{Expected Return}_t = R_f + \beta_t (\text{Return}_{\text{benchmark}} - R_f) | |
| ''') | |
| st.markdown(""" | |
| 4. **Calculate Jensen's Alpha:** | |
| - Compute the Jensen's Alpha as the difference between the actual return and the expected return: | |
| """) | |
| st.latex(r''' | |
| \alpha_t = \text{Return}_{\text{stock}, t} - \text{Expected Return}_t | |
| ''') | |
| fig = plot_rolling_alpha(data, market_data, window_size, risk_free_rate) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Value at Risk": | |
| st.markdown(""" | |
| ### Rolling Value at Risk (VaR) | |
| This method calculates the rolling Value at Risk (VaR) at different confidence levels. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling VaR:** | |
| - Compute the rolling quantile of the returns over a specified window for different confidence levels: | |
| """) | |
| st.latex(r''' | |
| \text{VaR}_{\alpha, t} = \text{Quantile}_{\alpha}(\text{Return}_{t-n:t}) | |
| ''') | |
| fig = plot_rolling_var(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Conditional VaR": | |
| st.markdown(""" | |
| ### Rolling Conditional Value at Risk (CVaR) | |
| This method calculates the rolling Conditional Value at Risk (CVaR) at different confidence levels. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling CVaR:** | |
| - Compute the average of the returns that are below the VaR threshold over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{CVaR}_{\alpha, t} = \frac{1}{n} \sum_{i=1}^{n} \text{Return}_{i} \text{ where } \text{Return}_{i} < \text{VaR}_{\alpha, t} | |
| ''') | |
| fig = plot_rolling_cvar(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Tail Ratio": | |
| st.markdown(""" | |
| ### Rolling Tail Ratio | |
| This method calculates the rolling Tail Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Tail Ratio:** | |
| - Compute the rolling Tail Ratio over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Tail Ratio}_t = \frac{|\text{Quantile}_{95}(\text{Return}_{t-n:t})|}{|\text{Quantile}_{5}(\text{Return}_{t-n:t})|} | |
| ''') | |
| fig = plot_rolling_tail_ratio(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Omega Ratio": | |
| st.markdown(""" | |
| ### Rolling Omega Ratio | |
| This method calculates the rolling Omega Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Omega Ratio:** | |
| - Compute the rolling Omega Ratio over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Omega Ratio}_t = \frac{\sum (\text{Return}_{i} > MAR) (\text{Return}_{i} - MAR)}{\sum (\text{Return}_{i} < MAR) (MAR - \text{Return}_{i})} | |
| ''') | |
| fig = plot_rolling_omega(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Sortino Ratio": | |
| st.markdown(""" | |
| ### Rolling Sortino Ratio | |
| This method calculates the rolling Sortino Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling Sortino Ratio:** | |
| - Compute the rolling Sortino Ratio over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Sortino Ratio}_t = \frac{\sqrt{252} \cdot \text{Mean}(\text{Return}_{t-n:t} - MAR)}{\sqrt{\text{Mean}(\min(0, \text{Return}_{t-n:t} - MAR)^2)}} | |
| ''') | |
| fig = plot_rolling_sortino(data, window_size, MAR) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Calmar Ratio": | |
| st.markdown(""" | |
| ### Rolling Calmar Ratio | |
| This method calculates the rolling Calmar Ratio. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling Calmar Ratio:** | |
| - Compute the rolling Calmar Ratio over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Calmar Ratio}_t = \frac{\text{CAGR}_{t-n:t}}{\text{Max Drawdown}_{t-n:t}} | |
| ''') | |
| fig = plot_rolling_calmar(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Stability": | |
| st.markdown(""" | |
| ### Rolling Stability | |
| This method calculates the rolling stability of returns. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling Stability:** | |
| - Compute the rolling stability over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Stability}_t = \sqrt{\frac{1}{n-1} \sum_{i=1}^{n} (\log(1 + \text{Return}_{t-i}) - \overline{\log(1 + \text{Return})})^2} | |
| ''') | |
| fig = plot_rolling_stability(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Maximum Drawdown": | |
| st.markdown(""" | |
| ### Rolling Maximum Drawdown | |
| This method calculates the rolling maximum drawdown. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Rolling Maximum Drawdown:** | |
| - Compute the cumulative returns and the maximum drawdown over a specified window: | |
| """) | |
| st.latex(r''' | |
| \text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i) | |
| ''') | |
| st.latex(r''' | |
| \text{Max Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Rolling Max Cumulative Return}_t}{\text{Rolling Max Cumulative Return}_t} | |
| ''') | |
| fig = plot_rolling_drawdown(data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Capture Ratios" and 'market_data' in st.session_state: | |
| st.markdown(""" | |
| ### Rolling Capture Ratios | |
| This method calculates the rolling upside and downside capture ratios. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock and the benchmark: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Upside Capture Ratio:** | |
| - Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are positive: | |
| """) | |
| st.latex(r''' | |
| \text{Upside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} > 0 | |
| ''') | |
| st.markdown(""" | |
| 3. **Calculate Downside Capture Ratio:** | |
| - Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are negative: | |
| """) | |
| st.latex(r''' | |
| \text{Downside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} < 0 | |
| ''') | |
| fig = plot_rolling_capture(data, market_data, window_size) | |
| st.plotly_chart(fig) | |
| elif selected == "Rolling Pain Index": | |
| st.markdown(""" | |
| ### Rolling Pain Index | |
| This method calculates the rolling pain index. | |
| """) | |
| with st.expander("Methodology", expanded=False): | |
| st.markdown(""" | |
| 1. **Calculate Returns:** | |
| - Compute the daily returns of the stock: | |
| """) | |
| st.latex(r''' | |
| \text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}} | |
| ''') | |
| st.markdown(""" | |
| 2. **Calculate Cumulative Returns:** | |
| - Compute the cumulative returns over time: | |
| """) | |
| st.latex(r''' | |
| \text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i) | |
| ''') | |
| st.markdown(""" | |
| 3. **Calculate Drawdowns:** | |
| - Determine the drawdowns by comparing the cumulative returns to their running maximum: | |
| """) | |
| st.latex(r''' | |
| \text{Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Running Max Cumulative Return}_t}{\text{Running Max Cumulative Return}_t} | |
| ''') | |
| st.markdown(""" | |
| 4. **Calculate Rolling Pain Index:** | |
| - Compute the average drawdown over a specified window where the drawdown is negative: | |
| """) | |
| st.latex(r''' | |
| \text{Pain Index} = \frac{1}{n} \sum_{i=1}^{n} \text{Drawdown}_i \quad \text{for } \text{Drawdown}_i < 0 | |
| ''') | |
| fig = plot_rolling_pain_index(data, window_size) | |
| st.plotly_chart(fig) | |
| hide_streamlit_style = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) |