import pandas as pd import numpy as np import yfinance as yf import streamlit as st import plotly.graph_objects as go from plotly.subplots import make_subplots # Helper function to fetch stock or crypto data def fetch_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame: """Fetch stock or crypto 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 # True Range Volatility Ratio def calculate_volatility_ratio(data: pd.DataFrame, n: int = 14, ratio: float = 1.75) -> go.Figure: data['High_Low'] = data['High'] - data['Low'] data['High_PrevClose'] = abs(data['High'] - data['Close'].shift()) data['Low_PrevClose'] = abs(data['Low'] - data['Close'].shift()) data['True_Range'] = data[['High_Low', 'High_PrevClose', 'Low_PrevClose']].max(axis=1) data['EMA_True_Range'] = data['True_Range'].ewm(span=n, adjust=False).mean() data['Volatility_Ratio'] = data['True_Range'] / data['EMA_True_Range'] fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, subplot_titles=('Close Price', 'Volatility Ratio')) fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price'), row=1, col=1) above_threshold = data['Volatility_Ratio'] > ratio fig.add_trace(go.Scatter(x=data.index[above_threshold], y=data['Close'][above_threshold], mode='markers', marker=dict(color='red', size=10), name=f'Volatility Ratio > {ratio}'), row=1, col=1) fig.add_trace(go.Scatter(x=data.index, y=data['Volatility_Ratio'], mode='lines', name='Volatility Ratio'), row=2, col=1) fig.add_hline(y=ratio, line=dict(color='red', dash='dash'), row=2, col=1) fig.update_layout(title='Volatility Ratio with Buy/Sell Signals', xaxis_title='Date', yaxis_title='Price', yaxis2_title='Volatility Ratio') return fig # Volume Ratio with Dynamic Percentile def calculate_volume_ratio(data: pd.DataFrame, percentile_threshold: float = 0.97) -> go.Figure: data['50_day_MA'] = data['Close'].rolling(window=50).mean() data['200_day_MA'] = data['Close'].rolling(window=200).mean() data['Volume_MA20'] = data['Volume'].rolling(window=20).mean() # Ensure Volume_Ratio is a single-column Series with proper index alignment volume_ratio = (data['Volume'] / data['Volume_MA20']).reindex(data.index, method='ffill') data['Volume_Ratio'] = pd.Series(volume_ratio, index=data.index) dynamic_percentile = data['Volume_Ratio'].quantile(percentile_threshold) mask = data['Volume_Ratio'] > dynamic_percentile fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1, subplot_titles=('Close Price and Volume', 'Volume Ratio', 'Histogram of Volume Ratio'), specs=[[{"secondary_y": True}], [{}], [{}]]) # Plotting close price on primary y-axis fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='blue')), row=1, col=1, secondary_y=False) fig.add_trace(go.Scatter(x=data.index[mask], y=data['Close'][mask], mode='markers', marker=dict(color='red', size=10), name='High Volume Ratio'), row=1, col=1, secondary_y=False) fig.add_trace(go.Bar(x=data.index, y=data['Volume'], name='Volume', marker_color='gray', opacity=0.3), row=1, col=1, secondary_y=True) fig.add_trace(go.Scatter(x=data.index, y=data['Volume_MA20'], mode='lines', name='20-day Volume MA', line=dict(color='purple')), row=1, col=1, secondary_y=True) fig.add_trace(go.Scatter(x=data.index, y=data['Volume_Ratio'], mode='lines', name='Volume Ratio', line=dict(color='green')), row=2, col=1) fig.add_hline(y=dynamic_percentile, line=dict(color='red', dash='dash'), row=2, col=1) fig.add_trace(go.Histogram(x=data['Volume_Ratio'], nbinsx=50, name='Volume Ratio Distribution', marker_color='green', opacity=0.7), row=3, col=1) fig.add_vline(x=dynamic_percentile, line=dict(color='red', dash='dash'), row=3, col=1) fig.update_layout(title='Volume Ratio with Dynamic Percentile Threshold', xaxis_title='Date', yaxis1_title='Price', yaxis2_title='Volume', yaxis3_title='Volume Ratio', yaxis4_title='Frequency') return fig # Streamlit app st.set_page_config(page_title="Unusual Volatility and Volume", layout="wide") st.title('Unusual Volatility and Volume') st.sidebar.title('Input Parameters') with st.sidebar.expander("Select Analysis", expanded=True): selected = st.radio("Select Analysis", ["Volatility Ratio", "Volume Ratio"]) # Sidebar for "How to Use" instructions specific to the selected method with st.sidebar.expander("How to Use", expanded=False): if selected == "Volatility Ratio": st.markdown(""" **How to Use Volatility Ratio:** 1. Enter the stock or crypto symbol (e.g., 'AAPL' for Apple or 'BTC-USD' for Bitcoin). 2. Choose the date range. 3. Set the number of days for the EMA calculation. 4. Set the volatility ratio threshold. 5. Click 'Fetch Data' to load the data. 6. The chart will display the True Range Volatility Ratio and highlight points where the ratio exceeds the threshold. """) elif selected == "Volume Ratio": st.markdown(""" **How to Use Volume Ratio:** 1. Enter the stock or crypto symbol. 2. Choose the date range. 3. Set the percentile threshold. 4. Click 'Fetch Data' to load the data. 5. The chart will display the Volume Ratio and highlight points where the ratio exceeds the percentile threshold. """) # Input parameters inside an expander with st.sidebar.expander("Input Parameters", expanded=True): ticker = st.text_input('Enter Stock or Crypto Symbol (e.g., AAPL or BTC-USD)', 'AAPL', help="Enter the ticker symbol for the stock or cryptocurrency you want to analyze.") start_date = st.date_input('Start Date', pd.to_datetime('2020-01-01'), help="Select the start date for the data range.") end_date = st.date_input('End Date', pd.to_datetime(pd.Timestamp.now().date() + pd.Timedelta(days=1)), help="Select the end date for the data range.") fetch_data = st.button('Fetch Data') # Capture the button state # Fetch data if 'data' not in st.session_state or 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 # Parameters for each method inside an expander with st.sidebar.expander(f"Parameters for {selected}", expanded=True): if selected == "Volatility Ratio": n = st.number_input('Number of Days for EMA', min_value=1, value=14, help="Set the number of days for the Exponential Moving Average (EMA) calculation.") ratio = st.slider('Volatility Ratio Threshold', min_value=1.0, max_value=3.0, value=1.75, step=0.1, help="Set the threshold for the Volatility Ratio to identify high volatility periods.") elif selected == "Volume Ratio": percentile_threshold = st.slider('Percentile Threshold', min_value=0.5, max_value=1.0, value=0.97, step=0.01, help="Set the percentile threshold for identifying high volume periods.") if 'data' in st.session_state and not st.session_state.data.empty: data = st.session_state.data # Display results based on the selected method if selected == "Volatility Ratio": st.markdown("### Volatility Ratio") st.markdown("This method calculates the True Range Volatility Ratio, which helps identify periods of high volatility. A ratio above a specified threshold indicates high volatility.") with st.expander("How it Works", expanded=False): st.markdown(""" **How Volatility Ratio Works:** 1. **Calculate True Range:** - True Range is the maximum of the following: """) st.latex(r''' \text{True Range} = \max(\text{High} - \text{Low}, \left| \text{High} - \text{Previous Close} \right|, \left| \text{Low} - \text{Previous Close} \right|) ''') st.markdown(""" 2. **Compute Exponential Moving Average (EMA) of True Range:** - Calculate the EMA of the True Range over a specified period (e.g., 14 days). 3. **Calculate Volatility Ratio:** - Use the formula: """) st.latex(r''' \text{Volatility Ratio} = \frac{\text{True Range}}{\text{EMA of True Range}} ''') st.markdown(""" 4. **Identify Signals:** - **High Volatility:** Occurs when the Volatility Ratio exceeds a specified threshold (e.g., 1.75). """) fig = calculate_volatility_ratio(data, n, ratio) st.plotly_chart(fig) elif selected == "Volume Ratio": st.markdown("### Volume Ratio with Dynamic Percentile") st.markdown("This method calculates the Volume Ratio and uses a dynamic percentile threshold to identify high volume periods. A volume ratio above the specified percentile indicates high trading activity.") with st.expander("How it Works", expanded=False): st.markdown(""" **How Volume Ratio Works:** 1. **Calculate Volume Moving Average:** - Compute the 20-day moving average of the volume. 2. **Calculate Volume Ratio:** - Use the formula: """) st.latex(r''' \text{Volume Ratio} = \frac{\text{Volume}}{\text{20-day Moving Average of Volume}} ''') st.markdown(""" 3. **Determine Dynamic Percentile Threshold:** - Calculate the specified percentile of the Volume Ratio distribution. 4. **Identify Signals:** - **High Volume:** Occurs when the Volume Ratio exceeds the dynamic percentile threshold. """) fig = calculate_volume_ratio(data, percentile_threshold) st.plotly_chart(fig) # Hide the default Streamlit menu and footer hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)