File size: 9,953 Bytes
783fdc5
 
 
 
 
 
 
 
2cef11f
d51521a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783fdc5
600c235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783fdc5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
def0095
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783fdc5
 
 
 
 
 
 
def0095
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
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)