Spaces:
Sleeping
Sleeping
| 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 = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) |