Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,21 +9,21 @@ import networkx as nx
|
|
| 9 |
# Streamlit app setup
|
| 10 |
st.set_page_config(layout="wide")
|
| 11 |
|
| 12 |
-
st.title("Herding Behaviour Analysis in
|
| 13 |
|
| 14 |
st.markdown(
|
| 15 |
"""
|
| 16 |
-
This app analyzes herding behavior in
|
| 17 |
-
You can specify the
|
| 18 |
The app includes various analyses such as price reindexing, Kalman Filter estimation of a common factor,
|
| 19 |
-
CSSD and CSAD calculations,
|
| 20 |
"""
|
| 21 |
)
|
| 22 |
|
| 23 |
st.sidebar.header("How to use")
|
| 24 |
st.sidebar.write(
|
| 25 |
"""
|
| 26 |
-
1. Select the
|
| 27 |
2. Choose the time period.
|
| 28 |
3. Set additional parameters for the analyses.
|
| 29 |
4. Click 'Run Analysis' to see the results.
|
|
@@ -35,6 +35,7 @@ tickers = st.sidebar.text_area("Asset Symbol (Crypto-Pair or Stock Ticker) (comm
|
|
| 35 |
start_date = st.sidebar.date_input("Start Date", value=pd.to_datetime("2020-01-01"))
|
| 36 |
end_date = st.sidebar.date_input("End Date", value=pd.to_datetime("2025-01-01"))
|
| 37 |
market_index = st.sidebar.text_input("Market Index Ticker", value="BTC-USD")
|
|
|
|
| 38 |
run_button = st.sidebar.button("Run Analysis")
|
| 39 |
|
| 40 |
if run_button:
|
|
@@ -42,7 +43,7 @@ if run_button:
|
|
| 42 |
if market_index not in tickers:
|
| 43 |
tickers.append(market_index)
|
| 44 |
|
| 45 |
-
# Fetching
|
| 46 |
data = yf.download(tickers, start=start_date, end=end_date)['Close']
|
| 47 |
|
| 48 |
# Clean the data by filling or dropping NaN and infinite values
|
|
@@ -50,8 +51,8 @@ if run_button:
|
|
| 50 |
data = data.replace([np.inf, -np.inf], np.nan).dropna()
|
| 51 |
|
| 52 |
# Reindexing prices to start at 0
|
| 53 |
-
st.markdown("####
|
| 54 |
-
st.markdown("This analysis reindexes
|
| 55 |
|
| 56 |
data_reindexed = data.apply(lambda x: x / x.iloc[0])
|
| 57 |
fig = go.Figure()
|
|
@@ -60,7 +61,7 @@ if run_button:
|
|
| 60 |
fig.add_trace(go.Scatter(x=data_reindexed.index, y=data_reindexed[ticker], mode='lines', name=ticker))
|
| 61 |
|
| 62 |
fig.update_layout(
|
| 63 |
-
title="
|
| 64 |
xaxis_title="Date",
|
| 65 |
yaxis_title="Reindexed Price",
|
| 66 |
template="plotly_dark"
|
|
@@ -74,8 +75,8 @@ if run_button:
|
|
| 74 |
returns = returns.replace([np.inf, -np.inf], np.nan).dropna()
|
| 75 |
|
| 76 |
# Kalman Filter: Estimating a common factor
|
| 77 |
-
st.markdown("#### Kalman Filter: Estimated Common Factor and
|
| 78 |
-
st.markdown("This analysis uses the Kalman Filter to estimate a common factor influencing all
|
| 79 |
|
| 80 |
st.markdown("The Kalman Filter operates based on the following state-space model:")
|
| 81 |
|
|
@@ -89,17 +90,15 @@ if run_button:
|
|
| 89 |
|
| 90 |
st.markdown("Where:")
|
| 91 |
st.markdown(r"""
|
| 92 |
-
\
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
\item \(\mathbf{v}_t\) is the observation noise (with covariance \(\mathbf{R}\)).
|
| 99 |
-
\end{itemize}
|
| 100 |
""")
|
| 101 |
|
| 102 |
-
st.markdown("The Kalman Filter recursively estimates the state vector \(\
|
| 103 |
|
| 104 |
st.markdown("""
|
| 105 |
The steps to derive the common factor are:
|
|
@@ -107,17 +106,15 @@ if run_button:
|
|
| 107 |
2. **Prediction:** Use the state equation to predict the state vector at the next time step.
|
| 108 |
3. **Update:** Use the observation equation and the actual observed returns to update the estimate of the state vector.
|
| 109 |
|
| 110 |
-
This process repeats for each time step, producing an estimated common factor that influences all
|
| 111 |
""")
|
| 112 |
|
| 113 |
st.markdown("""
|
| 114 |
**How to Interpret the Results:**
|
| 115 |
|
| 116 |
-
- **Estimated Common Factor:** This represents the underlying factor that influences all the
|
| 117 |
-
- **Individual
|
| 118 |
-
- **Deviation from the Common Factor:**
|
| 119 |
-
|
| 120 |
-
By observing these comparisons, you can gain insights into the behavior of individual cryptocurrencies relative to the market and identify potential outliers or trend-followers.
|
| 121 |
""")
|
| 122 |
|
| 123 |
observations = returns.values
|
|
@@ -138,7 +135,7 @@ if run_button:
|
|
| 138 |
|
| 139 |
fig.add_trace(go.Scatter(x=returns.index, y=state_means[:, 0], mode='lines', name='Estimated Common Factor', line=dict(color='red', width=4)))
|
| 140 |
fig.update_layout(
|
| 141 |
-
title='Kalman Filter: Estimated Common Factor and
|
| 142 |
xaxis_title='Date',
|
| 143 |
yaxis_title='Returns',
|
| 144 |
template='plotly_dark'
|
|
@@ -147,39 +144,29 @@ if run_button:
|
|
| 147 |
|
| 148 |
# CSSD and CSAD calculations
|
| 149 |
st.markdown("#### CSSD and CSAD Calculations")
|
| 150 |
-
st.markdown("This analysis calculates the Cross-Sectional Standard Deviation (CSSD) and Cross-Sectional Absolute Deviation (CSAD) of
|
| 151 |
|
| 152 |
st.markdown("The formulas for CSSD and CSAD are as follows:")
|
| 153 |
|
| 154 |
st.markdown("**CSSD (Cross-Sectional Standard Deviation):**")
|
| 155 |
st.latex(r"\text{CSSD}_t = \sqrt{\frac{\sum_{i=1}^{N} (R_{i,t} - \overline{R}_t)^2}{N - 1}}")
|
| 156 |
|
| 157 |
-
st.markdown("""
|
| 158 |
-
Where:
|
| 159 |
-
- \(R_{i,t}\) is the return of cryptocurrency \(i\) at time \(t\).
|
| 160 |
-
- \(\overline{R}_t\) is the average return of all cryptocurrencies at time \(t\).
|
| 161 |
-
- \(N\) is the number of cryptocurrencies.
|
| 162 |
-
""")
|
| 163 |
-
|
| 164 |
st.markdown("**CSAD (Cross-Sectional Absolute Deviation):**")
|
| 165 |
st.latex(r"\text{CSAD}_t = \frac{\sum_{i=1}^{N} |R_{i,t} - \overline{R}_t|}{N}")
|
| 166 |
|
| 167 |
st.markdown("""
|
| 168 |
Where:
|
| 169 |
-
-
|
| 170 |
-
-
|
| 171 |
-
-
|
| 172 |
""")
|
| 173 |
-
|
| 174 |
-
st.markdown("These metrics help to identify
|
| 175 |
|
| 176 |
st.markdown("""
|
| 177 |
**How to Interpret the Results:**
|
| 178 |
|
| 179 |
-
- **CSSD (Cross-Sectional Standard Deviation):**
|
| 180 |
-
- **CSAD (Cross-Sectional Absolute Deviation):** A higher CSAD also indicates greater dispersion of individual cryptocurrency returns around the market return, suggesting less herding behavior. A lower CSAD suggests more herding behavior as cryptocurrency returns are more closely clustered around the market return.
|
| 181 |
-
|
| 182 |
-
By observing the trends in CSSD and CSAD over time, you can identify periods of increased or decreased herding behavior in the market.
|
| 183 |
""")
|
| 184 |
|
| 185 |
market_return = returns[market_index]
|
|
@@ -235,31 +222,31 @@ if run_button:
|
|
| 235 |
)
|
| 236 |
st.plotly_chart(fig, use_container_width=True)
|
| 237 |
|
| 238 |
-
# Network visualization of
|
| 239 |
-
st.markdown("#### Network Visualization of
|
| 240 |
-
st.markdown("This analysis visualizes the correlations between
|
| 241 |
st.markdown("""
|
| 242 |
**How to Interpret the Results:**
|
| 243 |
|
| 244 |
-
- **Nodes:** Each node represents a
|
| 245 |
-
- **Edges:** An edge (or line) between two nodes indicates that the correlation between the two
|
| 246 |
- **Edge Thickness:** The thickness of the edge represents the strength of the correlation. Thicker edges indicate higher correlations.
|
| 247 |
-
- **Cluster Formation:** Groups of nodes that are densely connected to each other represent clusters of
|
| 248 |
-
- **Isolated Nodes:** Nodes that are not connected to others suggest that those
|
| 249 |
|
| 250 |
-
By examining this network, you can identify groups of
|
| 251 |
""")
|
| 252 |
|
| 253 |
years = data.index.year.unique()
|
| 254 |
|
| 255 |
-
def plot_network_for_year(data_for_year, year):
|
| 256 |
corr_matrix = data_for_year.corr()
|
| 257 |
G = nx.Graph()
|
| 258 |
|
| 259 |
for ticker in tickers:
|
| 260 |
G.add_node(ticker)
|
| 261 |
|
| 262 |
-
threshold = 0.5
|
| 263 |
for i in range(len(tickers)):
|
| 264 |
for j in range(i+1, len(tickers)):
|
| 265 |
if abs(corr_matrix.iloc[i, j]) > threshold:
|
|
@@ -294,7 +281,7 @@ if run_button:
|
|
| 294 |
)
|
| 295 |
|
| 296 |
layout = go.Layout(
|
| 297 |
-
title=f'
|
| 298 |
showlegend=False,
|
| 299 |
hovermode='closest',
|
| 300 |
margin=dict(b=20, l=5, r=5, t=40),
|
|
@@ -307,7 +294,7 @@ if run_button:
|
|
| 307 |
|
| 308 |
for year in years:
|
| 309 |
data_for_year = data[data.index.year == year]
|
| 310 |
-
plot_network_for_year(data_for_year, year)
|
| 311 |
|
| 312 |
hide_streamlit_style = """
|
| 313 |
<style>
|
|
@@ -315,4 +302,4 @@ hide_streamlit_style = """
|
|
| 315 |
footer {visibility: hidden;}
|
| 316 |
</style>
|
| 317 |
"""
|
| 318 |
-
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
|
|
|
| 9 |
# Streamlit app setup
|
| 10 |
st.set_page_config(layout="wide")
|
| 11 |
|
| 12 |
+
st.title("Herding Behaviour Analysis in Financials Markets")
|
| 13 |
|
| 14 |
st.markdown(
|
| 15 |
"""
|
| 16 |
+
This app analyzes herding behavior in financial markets by examining price movements and correlations.
|
| 17 |
+
You can specify the stock ticker or Asset pairs, time period, and other parameters for the analysis.
|
| 18 |
The app includes various analyses such as price reindexing, Kalman Filter estimation of a common factor,
|
| 19 |
+
CSSD and CSAD calculations, and network visualization of correlations over time.
|
| 20 |
"""
|
| 21 |
)
|
| 22 |
|
| 23 |
st.sidebar.header("How to use")
|
| 24 |
st.sidebar.write(
|
| 25 |
"""
|
| 26 |
+
1. Select the stock ticker Asset pairs.
|
| 27 |
2. Choose the time period.
|
| 28 |
3. Set additional parameters for the analyses.
|
| 29 |
4. Click 'Run Analysis' to see the results.
|
|
|
|
| 35 |
start_date = st.sidebar.date_input("Start Date", value=pd.to_datetime("2020-01-01"))
|
| 36 |
end_date = st.sidebar.date_input("End Date", value=pd.to_datetime("2025-01-01"))
|
| 37 |
market_index = st.sidebar.text_input("Market Index Ticker", value="BTC-USD")
|
| 38 |
+
correlation_threshold = st.sidebar.slider("Correlation Threshold (for Network Analysis)", min_value=0.0, max_value=1.0, value=0.75, step=0.05)
|
| 39 |
run_button = st.sidebar.button("Run Analysis")
|
| 40 |
|
| 41 |
if run_button:
|
|
|
|
| 43 |
if market_index not in tickers:
|
| 44 |
tickers.append(market_index)
|
| 45 |
|
| 46 |
+
# Fetching Asset data
|
| 47 |
data = yf.download(tickers, start=start_date, end=end_date)['Close']
|
| 48 |
|
| 49 |
# Clean the data by filling or dropping NaN and infinite values
|
|
|
|
| 51 |
data = data.replace([np.inf, -np.inf], np.nan).dropna()
|
| 52 |
|
| 53 |
# Reindexing prices to start at 0
|
| 54 |
+
st.markdown("#### Asset Prices Reindexed to Start at 0")
|
| 55 |
+
st.markdown("This analysis reindexes asset prices to start at 0, making it easier to compare their relative movements over time.")
|
| 56 |
|
| 57 |
data_reindexed = data.apply(lambda x: x / x.iloc[0])
|
| 58 |
fig = go.Figure()
|
|
|
|
| 61 |
fig.add_trace(go.Scatter(x=data_reindexed.index, y=data_reindexed[ticker], mode='lines', name=ticker))
|
| 62 |
|
| 63 |
fig.update_layout(
|
| 64 |
+
title="Asset Prices Reindexed to Start at 0",
|
| 65 |
xaxis_title="Date",
|
| 66 |
yaxis_title="Reindexed Price",
|
| 67 |
template="plotly_dark"
|
|
|
|
| 75 |
returns = returns.replace([np.inf, -np.inf], np.nan).dropna()
|
| 76 |
|
| 77 |
# Kalman Filter: Estimating a common factor
|
| 78 |
+
st.markdown("#### Kalman Filter: Estimated Common Factor and Asset Returns")
|
| 79 |
+
st.markdown("This analysis uses the Kalman Filter to estimate a common factor influencing all asset returns. It compares individual asset returns to the estimated common factor.")
|
| 80 |
|
| 81 |
st.markdown("The Kalman Filter operates based on the following state-space model:")
|
| 82 |
|
|
|
|
| 90 |
|
| 91 |
st.markdown("Where:")
|
| 92 |
st.markdown(r"""
|
| 93 |
+
- \(xt\) is the state vector (the common factor we are estimating).
|
| 94 |
+
- \(A\) is the state transition matrix (set to the identity matrix \(I\)).
|
| 95 |
+
- \(wt\) is the process noise (with covariance \(Q\)).
|
| 96 |
+
- \(yt\) is the observation vector (asset returns).
|
| 97 |
+
- \(H\) is the observation matrix (set to a vector of ones).
|
| 98 |
+
- \(vt\) is the observation noise (with covariance \(R\)).
|
|
|
|
|
|
|
| 99 |
""")
|
| 100 |
|
| 101 |
+
st.markdown("The Kalman Filter recursively estimates the state vector \(xt\) using the observed asset returns \( yt \). The estimated common factor is then compared to individual asset returns.")
|
| 102 |
|
| 103 |
st.markdown("""
|
| 104 |
The steps to derive the common factor are:
|
|
|
|
| 106 |
2. **Prediction:** Use the state equation to predict the state vector at the next time step.
|
| 107 |
3. **Update:** Use the observation equation and the actual observed returns to update the estimate of the state vector.
|
| 108 |
|
| 109 |
+
This process repeats for each time step, producing an estimated common factor that influences all asset returns.
|
| 110 |
""")
|
| 111 |
|
| 112 |
st.markdown("""
|
| 113 |
**How to Interpret the Results:**
|
| 114 |
|
| 115 |
+
- **Estimated Common Factor:** This represents the underlying factor that influences all the asset returns. If the common factor is high, it indicates that most assets are experiencing high returns. Conversely, if the common factor is low, it indicates that most asset are experiencing low returns.
|
| 116 |
+
- **Individual Asset Returns vs. Common Factor:** By comparing the individual asset returns to the estimated common factor, you can identify which asset are moving with the market trend and which are moving independently.
|
| 117 |
+
- **Deviation from the Common Factor:** Assets that deviate significantly from the common factor may be influenced by specific news or events, whereas assets that closely follow the common factor are more influenced by market-wide factors.
|
|
|
|
|
|
|
| 118 |
""")
|
| 119 |
|
| 120 |
observations = returns.values
|
|
|
|
| 135 |
|
| 136 |
fig.add_trace(go.Scatter(x=returns.index, y=state_means[:, 0], mode='lines', name='Estimated Common Factor', line=dict(color='red', width=4)))
|
| 137 |
fig.update_layout(
|
| 138 |
+
title='Kalman Filter: Estimated Common Factor and Asset Returns',
|
| 139 |
xaxis_title='Date',
|
| 140 |
yaxis_title='Returns',
|
| 141 |
template='plotly_dark'
|
|
|
|
| 144 |
|
| 145 |
# CSSD and CSAD calculations
|
| 146 |
st.markdown("#### CSSD and CSAD Calculations")
|
| 147 |
+
st.markdown("This analysis calculates the Cross-Sectional Standard Deviation (CSSD) and Cross-Sectional Absolute Deviation (CSAD) of Asset returns.")
|
| 148 |
|
| 149 |
st.markdown("The formulas for CSSD and CSAD are as follows:")
|
| 150 |
|
| 151 |
st.markdown("**CSSD (Cross-Sectional Standard Deviation):**")
|
| 152 |
st.latex(r"\text{CSSD}_t = \sqrt{\frac{\sum_{i=1}^{N} (R_{i,t} - \overline{R}_t)^2}{N - 1}}")
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
st.markdown("**CSAD (Cross-Sectional Absolute Deviation):**")
|
| 155 |
st.latex(r"\text{CSAD}_t = \frac{\sum_{i=1}^{N} |R_{i,t} - \overline{R}_t|}{N}")
|
| 156 |
|
| 157 |
st.markdown("""
|
| 158 |
Where:
|
| 159 |
+
- Rit is the return of Asset i at time t.
|
| 160 |
+
- Rt is the average return of all Assets at time t.
|
| 161 |
+
- N is the number of Assets.
|
| 162 |
""")
|
| 163 |
+
|
| 164 |
+
st.markdown("These metrics help to identify the dispersion of individual asset returns around the market return.")
|
| 165 |
|
| 166 |
st.markdown("""
|
| 167 |
**How to Interpret the Results:**
|
| 168 |
|
| 169 |
+
- **CSSD (Cross-Sectional Standard Deviation) and CSAD (Cross-Sectional Absolute Deviation):** Higher values indicate greater dispersion of asset returns around the market return, suggesting less herding behavior. Lower values indicate more clustering, suggesting more herding behavior.
|
|
|
|
|
|
|
|
|
|
| 170 |
""")
|
| 171 |
|
| 172 |
market_return = returns[market_index]
|
|
|
|
| 222 |
)
|
| 223 |
st.plotly_chart(fig, use_container_width=True)
|
| 224 |
|
| 225 |
+
# Network visualization of Asset correlations
|
| 226 |
+
st.markdown("#### Network Visualization of Asset Correlations")
|
| 227 |
+
st.markdown("This analysis visualizes the correlations between Assets as a network. Assets are connected by edges if their correlation exceeds a threshold.")
|
| 228 |
st.markdown("""
|
| 229 |
**How to Interpret the Results:**
|
| 230 |
|
| 231 |
+
- **Nodes:** Each node represents a Asset. The position of the nodes is determined by a spring layout algorithm, which places highly connected nodes closer together.
|
| 232 |
+
- **Edges:** An edge (or line) between two nodes indicates that the correlation between the two Assets exceeds the specified threshold (0.5 in this case).
|
| 233 |
- **Edge Thickness:** The thickness of the edge represents the strength of the correlation. Thicker edges indicate higher correlations.
|
| 234 |
+
- **Cluster Formation:** Groups of nodes that are densely connected to each other represent clusters of Assets that move together. This can indicate sector-specific movements or broader market trends.
|
| 235 |
+
- **Isolated Nodes:** Nodes that are not connected to others suggest that those Assets do not have strong correlations with the rest of the market within the given threshold.
|
| 236 |
|
| 237 |
+
By examining this network, you can identify groups of Assets that tend to move together, which may reflect sector-specific behavior or broader market dynamics. This can provide insights into the structure of the market and potential areas of risk or opportunity.
|
| 238 |
""")
|
| 239 |
|
| 240 |
years = data.index.year.unique()
|
| 241 |
|
| 242 |
+
def plot_network_for_year(data_for_year, year, threshold):
|
| 243 |
corr_matrix = data_for_year.corr()
|
| 244 |
G = nx.Graph()
|
| 245 |
|
| 246 |
for ticker in tickers:
|
| 247 |
G.add_node(ticker)
|
| 248 |
|
| 249 |
+
#threshold = 0.5
|
| 250 |
for i in range(len(tickers)):
|
| 251 |
for j in range(i+1, len(tickers)):
|
| 252 |
if abs(corr_matrix.iloc[i, j]) > threshold:
|
|
|
|
| 281 |
)
|
| 282 |
|
| 283 |
layout = go.Layout(
|
| 284 |
+
title=f'Asset Correlation Network for {year}',
|
| 285 |
showlegend=False,
|
| 286 |
hovermode='closest',
|
| 287 |
margin=dict(b=20, l=5, r=5, t=40),
|
|
|
|
| 294 |
|
| 295 |
for year in years:
|
| 296 |
data_for_year = data[data.index.year == year]
|
| 297 |
+
plot_network_for_year(data_for_year, year, correlation_threshold)
|
| 298 |
|
| 299 |
hide_streamlit_style = """
|
| 300 |
<style>
|
|
|
|
| 302 |
footer {visibility: hidden;}
|
| 303 |
</style>
|
| 304 |
"""
|
| 305 |
+
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|