Space18 / app.py
QuantumLearner's picture
Update app.py
def0095 verified
import streamlit as st
import numpy as np
import pandas as pd
import yfinance as yf
import plotly.graph_objects as go
from plotly.subplots import make_subplots
st.set_page_config(page_title="Hurst Exponent Analysis", layout="wide")
st.title("Long-Term Memory in Asset Prices")
st.markdown("""This tool analyzes the long-term memory in asset prices by calculating and visualizing the Hurst exponent to assess whether the price series is mean-reverting, trending, or a random walk.""")
with st.expander("Hurst Exponent Methodology", expanded=False):
st.markdown("""
## Understanding the Hurst Exponent
The Hurst exponent (H) is a measure of the long-term memory of a time series. It helps to determine the nature of the time series:
- **Mean Reverting**: If \( H < 0.5 \), the time series tends to revert to its mean.
- **Random Walk**: If \( H = 0.5 \), the time series behaves like a random walk, with no correlation between values.
- **Persistent Trend**: If \( H > 0.5 \), the time series shows a persistent trend, meaning that high values are likely to be followed by high values and low values by low values.
The Hurst exponent is calculated using the following steps:
1. Calculate the log-log plot of the range and the number of lags.
2. Fit a linear regression model to the log-log plot.
3. The slope of the line is the Hurst exponent.
### Formula:
""")
st.latex(r'''
H = \frac{\log \left( \frac{Range(t+k) - Range(t)}{\sigma(t)} \right)}{\log(k)}
''')
st.markdown("""
Where:
- H is the Hurst exponent.
- t is the time.
- k is the lag.
- σ(t) is the standard deviation of the time series.
""")
# Sidebar: How to Use (closed by default)
with st.sidebar.expander("How to Use", expanded=False):
st.markdown("""
1. Enter the stock ticker symbol or crypto pair (e.g., AAPL for Apple Inc. or BTC-USD for Bitcoin).
2. Select the start and end dates for the analysis.
3. Choose the rolling window size for calculating the Hurst exponent.
4. Click "Run Analysis" to see the results.
The tool will calculate the Hurst exponent for the selected asset and display:
- The asset price and its simple moving average (SMA).
- The rolling Hurst exponent over time.
- A histogram of the Hurst exponent values.
""")
# Sidebar: Asset Symbol and Dates (open by default)
with st.sidebar.expander("Asset Symbol and Dates", expanded=True):
symbol = st.text_input("Asset Symbol (Stock Ticker or Crypto Pair)", value="ASML.AS",
help="Enter the ticker symbol for the stock or crypto pair you want to analyze, e.g., 'AAPL' or 'BTC-USD'.")
start_date = st.date_input("Start Date", value=pd.to_datetime("2018-01-01"),
help="Select the start date for the analysis period.")
end_date = st.date_input("End Date", value=pd.to_datetime(pd.Timestamp.now().date() + pd.Timedelta(days=1)),
help="Select the end date for the analysis period.")
# Sidebar: Parameters (open by default)
with st.sidebar.expander("Parameters", expanded=True):
rolling_window = st.slider("Rolling Window Size", min_value=10, max_value=180, value=90,
help="Choose the size of the rolling window for the Hurst exponent calculation.")
sma_window = st.slider("SMA Window Size", min_value=10, max_value=180, value=30,
help="Choose the size of the window for the Simple Moving Average (SMA).")
quantile_high = st.slider("High Quantile Threshold (0-1)", min_value=0.0, max_value=1.0, value=0.95, step=0.01,
help="Set the high quantile threshold for significant Hurst exponent values.")
quantile_low = st.slider("Low Quantile Threshold (0-1)", min_value=0.0, max_value=1.0, value=0.05, step=0.01,
help="Set the low quantile threshold for significant Hurst exponent values.")
def hurst_exponent(time_series, max_lag=20):
lags = range(2, max_lag)
tau = [np.std(np.subtract(time_series[lag:], time_series[:-lag])) for lag in lags]
log_lags = np.log(lags)
log_tau = np.log(tau)
poly = np.polyfit(log_lags, log_tau, 1)
hurst = poly[0] * 2.0
return hurst
def rolling_hurst(price_series, window, max_lag=20):
return price_series.rolling(window=window).apply(lambda x: hurst_exponent(x, max_lag), raw=True)
def interpret_hurst(hurst):
if hurst < 0.5:
return (f"Hurst exponent of {hurst:.2f} suggests the time series is mean reverting. "
"Future prices are likely to decrease or oscillate around the mean.")
elif hurst > 0.5:
return (f"Hurst exponent of {hurst:.2f} suggests the time series is trending. "
"Future prices are likely to continue increasing.")
else:
return (f"Hurst exponent of {hurst:.2f} suggests the time series is a random walk. "
"Future prices are unpredictable and could move in either direction.")
def interpret_rolling_window(window):
if window < 60:
return "A shorter rolling window captures more recent trends but may be more volatile."
elif window > 90:
return "A longer rolling window smooths out short-term fluctuations but may lag in reflecting recent changes."
else:
return "A moderate rolling window balances recent trends with some degree of smoothing."
if st.sidebar.button("Run Analysis"):
try:
df = yf.download(symbol, start=start_date, end=end_date, auto_adjust=False)
if isinstance(df.columns, pd.MultiIndex): # Flatten multi-index
df.columns = df.columns.get_level_values(0)
if df.empty:
raise ValueError(f"No data found for {symbol} from {start_date} to {end_date}")
if len(df) < rolling_window:
raise ValueError(f"Insufficient data points for {symbol}. Need at least {rolling_window} points.")
df['hurst_exponent'] = rolling_hurst(df['Close'], rolling_window)
df.dropna(inplace=True)
df['sma'] = df['Close'].rolling(window=sma_window).mean()
hurst_95 = df['hurst_exponent'].quantile(quantile_high)
hurst_05 = df['hurst_exponent'].quantile(quantile_low)
current_hurst = df['hurst_exponent'].iloc[-1]
significant_dates_95 = df[df['hurst_exponent'] >= hurst_95].index
significant_dates_05 = df[df['hurst_exponent'] <= hurst_05].index
fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
subplot_titles=(f"{symbol} Price and SMA", "Rolling Hurst Exponent", "Hurst Exponent Histogram"))
# Plot price and SMA
fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name=f'{symbol} Price', line=dict(color='blue')), row=1, col=1)
fig.add_trace(go.Scatter(x=df.index, y=df['sma'], name=f'{sma_window}-day SMA', line=dict(color='purple')), row=1, col=1)
# Add shaded areas for significant dates
for date in significant_dates_95:
fig.add_vrect(
x0=date,
x1=date + pd.Timedelta(days=1),
fillcolor="red",
opacity=0.7,
layer="below",
line_width=0
)
for date in significant_dates_05:
fig.add_vrect(
x0=date,
x1=date + pd.Timedelta(days=1),
fillcolor="blue",
opacity=0.7,
layer="below",
line_width=0
)
# Plot Hurst Exponent
fig.add_trace(go.Scatter(x=df.index, y=df['hurst_exponent'], name='Rolling Hurst Exponent', line=dict(color='white', width=5)), row=2, col=1)
fig.add_hline(y=hurst_95, line=dict(color='red', dash='dash', width=1), annotation_text=f'95th Percentile: {hurst_95:.2f}', row=2, col=1)
fig.add_hline(y=hurst_05, line=dict(color='blue', dash='dash', width=1), annotation_text=f'5th Percentile: {hurst_05:.2f}', row=2, col=1)
fig.add_hline(y=current_hurst, line=dict(color='orange', width=1), annotation_text=f'Current Hurst: {current_hurst:.2f}', row=2, col=1)
# Hurst interpretation ranges
fig.add_hline(y=0.5, line=dict(color='black', width=1), annotation_text='0.5: Random Walk', row=2, col=1)
fig.add_trace(go.Scatter(x=df.index, y=[0.5] * len(df.index), name='0.5: Random Walk', mode='lines', line=dict(color='black', width=3)), row=2, col=1)
fig.add_trace(go.Scatter(x=df.index, y=[0.0] * len(df.index), name='<0.5: Mean Reverting', fill='tonexty', mode='lines', line=dict(color='blue', width=0), opacity=0.01), row=2, col=1)
fig.add_trace(go.Scatter(x=df.index, y=[1.0] * len(df.index), name='>0.5: Persistent Trend', fill='tonexty', mode='lines', line=dict(color='red', width=0), opacity=0.01), row=2, col=1)
# Plot histogram of Hurst Exponent
fig.add_trace(go.Histogram(x=df['hurst_exponent'], nbinsx=30, marker=dict(color='grey', line=dict(color='black', width=1)), name='Hurst Exponent Histogram'), row=3, col=1)
fig.add_vline(x=current_hurst, line=dict(color='orange', width=1), annotation_text=f'Current Hurst: {current_hurst:.2f}', row=3, col=1)
fig.update_layout(height=900, width=1200, title_text=f"Hurst Exponent Analysis for {symbol}")
st.plotly_chart(fig)
hurst_interpretation = interpret_hurst(current_hurst)
window_interpretation = interpret_rolling_window(rolling_window)
st.markdown(f"**Rolling window interpretation:** {window_interpretation}")
st.markdown(f"**Hurst exponent interpretation:** {hurst_interpretation}")
except Exception as e:
st.error(f"Error: {str(e)}. Check ticker symbol, date range, or window size.")
hide_streamlit_style = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)