Space7 / app.py
QuantumLearner's picture
Update app.py
f35cc67 verified
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)