Spaces:
Sleeping
Sleeping
| 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) |