File size: 10,527 Bytes
eec67e7
 
 
 
 
 
 
78bf65f
eec67e7
78bf65f
f35cc67
a5fac15
 
 
eec67e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5fac15
 
 
eec67e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380a39d
 
eec67e7
6856892
 
6125812
faf3f39
eec67e7
78bf65f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53ff493
78bf65f
 
 
 
15fbd06
 
 
a5fac15
 
 
 
 
 
15fbd06
78bf65f
53ff493
78bf65f
 
 
 
 
eec67e7
a5fac15
 
eec67e7
a5fac15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
980572e
a5fac15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30a97ff
78bf65f
30a97ff
 
 
 
 
 
a5fac15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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)