QuantumLearner commited on
Commit
d49994e
·
verified ·
1 Parent(s): e6c62ba

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +547 -0
app.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+ import yfinance as yf
5
+ import warnings
6
+ import plotly.graph_objects as go
7
+ from plotly.subplots import make_subplots
8
+ import datetime
9
+
10
+ warnings.filterwarnings('ignore')
11
+
12
+ # Set wide page layout
13
+ st.set_page_config(page_title="Spread Estimation", layout="wide")
14
+
15
+ st.title("Spread Estimation")
16
+
17
+ st.write("This application estimates the rolling bid-ask spread using a rolling window estimator on OHLC prices. Each chart below shows volume, close price, rolling volatility, and rolling spread. Use the expanders to see additional analysis for each interval. For further details on the methodology, please see [this article](https://entreprenerdly.com/estimating-bid-ask-spreads-using-ohlc-prices/).")
18
+
19
+ with st.expander("Theory and Methodology ", expanded=False):
20
+ st.markdown(r"""
21
+ #### Methodology for Estimating Rolling Bid-Ask Spreads
22
+
23
+ This function allows for rolling estimation of bid-ask spreads. It is suitable for analyzing transaction costs over time.
24
+
25
+ ##### Bid-Ask Spread Definition
26
+
27
+ The **effective bid-ask spread** measures the deviation of observed transaction prices from the unobserved fundamental price. Formally, for a given trade:
28
+
29
+ $$
30
+ S = \frac{2D (P - P^*)}{P^*}
31
+ $$
32
+
33
+ where:
34
+ - \( P \) is the observed transaction price.
35
+ - \( P^* \) is the unobserved fundamental price.
36
+ - \( D \) is a trade direction indicator (+1 for buy, -1 for sell).
37
+
38
+ Since \( P^* \) is unobserved, various methods exist to estimate the spread using **low-frequency data (OHLC prices)**.
39
+
40
+ ##### Improving Estimation with OHLC Prices
41
+
42
+ The function implements an estimator that:
43
+ 1. Uses **open, high, low, and close** prices.
44
+ 2. Corrects for **discretely observed prices** to avoid biases.
45
+ 3. Uses the **Generalized Method of Moments (GMM)** to minimize estimation variance.
46
+
47
+ ##### Moment Conditions
48
+
49
+ We define multiple log-returns:
50
+
51
+ - **Midpoint log-prices**:
52
+
53
+ $$
54
+ \eta_t = \frac{\log(H_t) + \log(L_t)}{2}
55
+ $$
56
+
57
+ - **Returns used for estimation**:
58
+
59
+ $$
60
+ r_1 = \eta_t - o_t,\quad r_2 = o_t - \eta_{t-1},\quad r_3 = \eta_t - c_{t-1},\quad r_4 = c_{t-1} - \eta_{t-1},\quad r_5 = o_t - c_{t-1}
61
+ $$
62
+
63
+ where \( o_t \), \( c_t \), \( H_t \), and \( L_t \) are the log-transformed **open, close, high, and low** prices.
64
+
65
+ A key step is defining **indicators for price variation**:
66
+
67
+ $$
68
+ \tau_t =
69
+ \begin{cases}
70
+ 0, & \text{if } H_t = L_t = c_{t-1} \\\\
71
+ 1, & \text{otherwise}
72
+ \end{cases}
73
+ $$
74
+
75
+ This handles cases when prices remain unchanged and prevents overestimation.
76
+
77
+ #### Estimator Calculation
78
+
79
+ Using the moment conditions, the estimator is:
80
+
81
+ $$
82
+ S^2 = -8\,\frac{E[(\eta_t - o_t)(o_t - c_{t-1})]}{P[o_t \neq H_t,\, \tau_t = 1] + P[o_t \neq L_t,\, \tau_t = 1]}
83
+ $$
84
+
85
+ Multiple estimators are computed and combined using GMM weighting:
86
+
87
+ $$
88
+ S^2_{\text{EDGE}} = w_1\,E[x_1] + w_2\,E[x_2]
89
+ $$
90
+
91
+ where \( w_1 \) and \( w_2 \) are chosen to minimize variance:
92
+
93
+ $$
94
+ w_1 = \frac{\operatorname{Var}[x_2]}{\operatorname{Var}[x_1] + \operatorname{Var}[x_2]},\quad
95
+ w_2 = \frac{\operatorname{Var}[x_1]}{\operatorname{Var}[x_1] + \operatorname{Var}[x_2]}
96
+ $$
97
+
98
+ Finally, the **spread estimate** is given by:
99
+
100
+ $$
101
+ S = \sqrt{\max(0, S^2)}
102
+ $$
103
+
104
+ ##### Rolling Estimation
105
+
106
+ The estimator uses a **rolling window approach**:
107
+ - The window size is user-defined.
108
+ - The estimates update dynamically, allowing for time-varying spread analysis.
109
+ - Negative values are reset to zero for consistency.
110
+
111
+ ##### Implementation
112
+
113
+ The function `edge_rolling` computes **rolling bid-ask spread estimates** from OHLC prices. It accepts:
114
+ - `data`: A DataFrame with `open`, `high`, `low`, and `close` prices.
115
+ - `window`: The rolling window size.
116
+ - `sign`: Boolean to preserve the sign of the estimate.
117
+ - `kwargs`: Additional arguments for the Pandas rolling function.
118
+
119
+ This estimator improves accuracy in markets with varying trading frequencies.
120
+
121
+ For further details, see:
122
+ - [Ardia, D., Guidotti, E., & Kroencke, T. (2024). Efficient Estimation of Bid-Ask Spreads from OHLC Prices. Journal of Financial Economics.](https://doi.org/10.1016/j.jfineco.2024.103916)
123
+ """, unsafe_allow_html=True)
124
+
125
+ with st.sidebar:
126
+ with st.expander("User Inputs", expanded=True):
127
+ ticker = st.text_input(
128
+ "Ticker", value="BTC-USD",
129
+ help="Enter the ticker symbol or cryptopair (e.g., 'AAPL', 'BTC-USD')."
130
+ )
131
+ start_date = st.date_input(
132
+ "Start Date", value=pd.to_datetime("2015-01-01"),
133
+ help="Select the start date for the analysis."
134
+ )
135
+ default_end_date = datetime.date.today() + datetime.timedelta(days=1)
136
+ end_date = st.date_input(
137
+ "End Date", value=default_end_date,
138
+ help="Select the end date for the analysis (default is tomorrow)."
139
+ )
140
+ run_analysis = st.button("Run Analysis")
141
+
142
+ # Function to compute the rolling spread estimate
143
+ def edge_rolling(data: pd.DataFrame, window: int, sign: bool = False, **kwargs) -> pd.Series:
144
+ df = data.rename(columns=str.lower, inplace=False)
145
+ log_open = np.log(df['open'])
146
+ log_high = np.log(df['high'])
147
+ log_low = np.log(df['low'])
148
+ log_close = np.log(df['close'])
149
+ log_mid = (log_high + log_low) / 2.0
150
+
151
+ log_high_prev = log_high.shift(1)
152
+ log_low_prev = log_low.shift(1)
153
+ log_close_prev = log_close.shift(1)
154
+ log_mid_prev = log_mid.shift(1)
155
+
156
+ r1 = log_mid - log_open
157
+ r2 = log_open - log_mid_prev
158
+ r3 = log_mid - log_close_prev
159
+ r4 = log_close_prev - log_mid_prev
160
+ r5 = log_open - log_close_prev
161
+
162
+ tau = np.where(
163
+ np.isnan(log_high) | np.isnan(log_low) | np.isnan(log_close_prev),
164
+ np.nan,
165
+ (log_high != log_low) | (log_low != log_close_prev)
166
+ )
167
+ ind_o_h = tau * np.where(np.isnan(log_open) | np.isnan(log_high), np.nan, log_open != log_high)
168
+ ind_o_l = tau * np.where(np.isnan(log_open) | np.isnan(log_low), np.nan, log_open != log_low)
169
+ ind_c_h = tau * np.where(np.isnan(log_close_prev) | np.isnan(log_high_prev), np.nan, log_close_prev != log_high_prev)
170
+ ind_c_l = tau * np.where(np.isnan(log_close_prev) | np.isnan(log_low_prev), np.nan, log_close_prev != log_low_prev)
171
+
172
+ prod_12 = r1 * r2
173
+ prod_34 = r3 * r4
174
+ prod_15 = r1 * r5
175
+ prod_45 = r4 * r5
176
+ tau_r1 = tau * r1
177
+ tau_r2 = tau * r2
178
+ tau_r4 = tau * r4
179
+ tau_r5 = tau * r5
180
+
181
+ vals = pd.DataFrame({
182
+ 'prod_12': prod_12,
183
+ 'prod_34': prod_34,
184
+ 'prod_15': prod_15,
185
+ 'prod_45': prod_45,
186
+ 'tau': tau,
187
+ 'r1': r1,
188
+ 'tau_r2': tau_r2,
189
+ 'r3': r3,
190
+ 'tau_r4': tau_r4,
191
+ 'r5': r5,
192
+ 'prod_12_sq': prod_12 ** 2,
193
+ 'prod_34_sq': prod_34 ** 2,
194
+ 'prod_15_sq': prod_15 ** 2,
195
+ 'prod_45_sq': prod_45 ** 2,
196
+ 'prod_12_34': prod_12 * prod_34,
197
+ 'prod_15_45': prod_15 * prod_45,
198
+ 'tau_r2_r2': tau_r2 * r2,
199
+ 'tau_r4_r4': tau_r4 * r4,
200
+ 'tau_r5_r5': tau_r5 * r5,
201
+ 'tau_r2_prod12': tau_r2 * prod_12,
202
+ 'tau_r4_prod34': tau_r4 * prod_34,
203
+ 'tau_r5_prod15': tau_r5 * prod_15,
204
+ 'tau_r4_prod45': tau_r4 * prod_45,
205
+ 'tau_r4_prod12': tau_r4 * prod_12,
206
+ 'tau_r2_prod34': tau_r2 * prod_34,
207
+ 'tau_r2_r4': tau_r2 * r4,
208
+ 'tau_r1_prod45': tau_r1 * prod_45,
209
+ 'tau_r5_prod45': tau_r5 * prod_45,
210
+ 'tau_r4_r5': tau_r4 * r5,
211
+ 'tau_r5_only': tau_r5,
212
+ 'ind_o_h': ind_o_h,
213
+ 'ind_o_l': ind_o_l,
214
+ 'ind_c_h': ind_c_h,
215
+ 'ind_c_l': ind_c_l
216
+ }, index=df.index)
217
+
218
+ vals.iloc[0] = np.nan
219
+
220
+ window_adj = window - 1 if isinstance(window, (int, np.integer)) else window
221
+ if 'min_periods' in kwargs and isinstance(kwargs['min_periods'], (int, np.integer)):
222
+ kwargs['min_periods'] = max(0, kwargs['min_periods'] - 1)
223
+
224
+ roll_vals = vals.rolling(window=window_adj, **kwargs).mean()
225
+
226
+ p_tau = roll_vals['tau']
227
+ p_open = roll_vals['ind_o_h'] + roll_vals['ind_o_l']
228
+ p_close = roll_vals['ind_c_h'] + roll_vals['ind_c_l']
229
+
230
+ count_tau = vals['tau'].rolling(window=window_adj, **kwargs).sum()
231
+ roll_vals[(count_tau < 2) | (p_open == 0) | (p_close == 0)] = np.nan
232
+
233
+ a1 = -4.0 / p_open
234
+ a2 = -4.0 / p_close
235
+ a3 = roll_vals['r1'] / p_tau
236
+ a4 = roll_vals['tau_r4'] / p_tau
237
+ a5 = roll_vals['r3'] / p_tau
238
+ a6 = roll_vals['r5'] / p_tau
239
+
240
+ a12 = 2 * a1 * a2
241
+ a11 = a1 ** 2
242
+ a22 = a2 ** 2
243
+ a33 = a3 ** 2
244
+ a55 = a5 ** 2
245
+ a66 = a6 ** 2
246
+
247
+ E1 = a1 * (roll_vals['prod_12'] - a3 * roll_vals['tau_r2']) + \
248
+ a2 * (roll_vals['prod_34'] - a4 * roll_vals['r3'])
249
+ E2 = a1 * (roll_vals['prod_15'] - a3 * roll_vals['tau_r5_only']) + \
250
+ a2 * (roll_vals['prod_45'] - a4 * roll_vals['r5'])
251
+
252
+ V1 = - E1**2 + (
253
+ a11 * (roll_vals['prod_12_sq'] - 2 * a3 * roll_vals['tau_r2_prod12'] + a33 * roll_vals['tau_r2_r2']) +
254
+ a22 * (roll_vals['prod_34_sq'] - 2 * a5 * roll_vals['tau_r4_prod34'] + a55 * roll_vals['tau_r4_r4']) +
255
+ a12 * (roll_vals['prod_12_34'] - a3 * roll_vals['tau_r2_prod34'] - a5 * roll_vals['tau_r4_prod12'] + a3 * a5 * roll_vals['tau_r2_r4'])
256
+ )
257
+ V2 = - E2**2 + (
258
+ a11 * (roll_vals['prod_15_sq'] - 2 * a3 * roll_vals['tau_r5_prod15'] + a33 * roll_vals['tau_r5_r5']) +
259
+ a22 * (roll_vals['prod_45_sq'] - 2 * a6 * roll_vals['tau_r4_prod45'] + a66 * roll_vals['tau_r4_r4']) +
260
+ a12 * (roll_vals['prod_15_45'] - a3 * roll_vals['tau_r5_prod45'] - a6 * roll_vals['tau_r4_r5'] + a3 * a6 * roll_vals['tau_r4_r5'])
261
+ )
262
+
263
+ tot_var = V1 + V2
264
+ s2 = np.where(tot_var > 0, (V2 * E1 + V1 * E2) / tot_var, (E1 + E2) / 2.0)
265
+ spread = np.sqrt(np.abs(s2))
266
+ if sign:
267
+ spread *= np.sign(s2)
268
+
269
+ return pd.Series(spread, index=df.index)
270
+
271
+ # Download data function supporting different intervals
272
+ def download_data(ticker, start, end, interval="1d"):
273
+ if interval in ["1d", "1wk", "1mo"]:
274
+ data = yf.download(ticker, start=start, end=end, interval=interval, auto_adjust=True)
275
+ return data
276
+ else:
277
+ period_mapping = {"1m": "8d", "5m": "60d", "60m": "720d"}
278
+ if interval in period_mapping:
279
+ period = period_mapping[interval]
280
+ data = yf.download(ticker, period=period, interval=interval, auto_adjust=True)
281
+ return data
282
+ else:
283
+ data = yf.download(ticker, start=start, end=end, interval=interval, auto_adjust=True)
284
+ return data
285
+
286
+ # Run analysis when button is clicked
287
+ if run_analysis:
288
+ start_date_str = pd.to_datetime(start_date).strftime("%Y-%m-%d")
289
+ end_date_str = pd.to_datetime(end_date).strftime("%Y-%m-%d")
290
+
291
+ intervals = ["1d", "60m", "5m", "1m"]
292
+
293
+ for interval in intervals:
294
+ st.markdown(f"## Spread Estimation at {interval} data")
295
+ with st.spinner(f"Downloading {interval} data..."):
296
+ data = download_data(ticker, start_date_str, end_date_str, interval=interval)
297
+
298
+ if data.empty:
299
+ st.error(f"No data available for the {interval} interval.")
300
+ continue
301
+
302
+ if isinstance(data.columns, pd.MultiIndex):
303
+ data.columns = data.columns.get_level_values(0)
304
+
305
+ try:
306
+ ohlc = data[['Open', 'High', 'Low', 'Close']]
307
+ except Exception as e:
308
+ st.error("Error processing data columns.")
309
+ continue
310
+
311
+ try:
312
+ rolling_spreads = edge_rolling(ohlc, window=20, min_periods=10, sign=False)
313
+ except Exception as e:
314
+ st.error("Error computing rolling spread.")
315
+ continue
316
+
317
+ data_with_spread = data.copy()
318
+ data_with_spread["Spread"] = rolling_spreads
319
+
320
+ volume = data['Volume']
321
+ returns = ohlc['Close'].pct_change()
322
+ rolling_vol = returns.rolling(window=20).std()
323
+ upper_band = ohlc['Close'] * (1 + rolling_spreads / 2)
324
+ lower_band = ohlc['Close'] * (1 - rolling_spreads / 2)
325
+
326
+ # Create the main Plotly chart
327
+ fig_ts = go.Figure()
328
+ fig_ts.add_trace(go.Bar(
329
+ x=ohlc.index, y=volume,
330
+ name="Volume",
331
+ marker_color="gray",
332
+ opacity=1,
333
+ yaxis="y"
334
+ ))
335
+ fig_ts.add_trace(go.Scatter(
336
+ x=ohlc.index, y=ohlc['Close'],
337
+ mode="lines",
338
+ name="Close Price",
339
+ line=dict(color="lime"),
340
+ yaxis="y2"
341
+ ))
342
+ fig_ts.add_trace(go.Scatter(
343
+ x=ohlc.index, y=lower_band,
344
+ mode="lines",
345
+ name="Lower Band",
346
+ line=dict(color="rgba(0,0,0,0)"),
347
+ showlegend=False,
348
+ yaxis="y2"
349
+ ))
350
+ fig_ts.add_trace(go.Scatter(
351
+ x=ohlc.index, y=upper_band,
352
+ mode="lines",
353
+ name="Spread Band",
354
+ line=dict(color="gray"),
355
+ fill="tonexty",
356
+ fillcolor="rgba(128,128,128,0.3)",
357
+ yaxis="y2"
358
+ ))
359
+ fig_ts.add_trace(go.Scatter(
360
+ x=ohlc.index, y=rolling_vol,
361
+ mode="lines",
362
+ name="Rolling Volatility",
363
+ line=dict(color="orange", dash="dash"),
364
+ yaxis="y3"
365
+ ))
366
+ fig_ts.add_trace(go.Scatter(
367
+ x=ohlc.index, y=rolling_spreads,
368
+ mode="lines",
369
+ name="Rolling Spread",
370
+ line=dict(color="blue"),
371
+ yaxis="y4"
372
+ ))
373
+ fig_ts.update_layout(
374
+ template="plotly_dark",
375
+ paper_bgcolor='#0e1117',
376
+ plot_bgcolor='#0e1117',
377
+ title=dict(text=f"Rolling Spread, Volume, Close Price, and Rolling Volatility ({interval} data)", font=dict(color="white")),
378
+ xaxis=dict(
379
+ title="Date",
380
+ tickformat="%Y-%m-%d",
381
+ nticks=20,
382
+ showgrid=True,
383
+ gridcolor="rgba(255,255,255,0.2)",
384
+ color="white",
385
+ tickfont=dict(color="white")
386
+ ),
387
+ yaxis=dict(
388
+ title="Volume",
389
+ side="left",
390
+ showgrid=True,
391
+ gridcolor="rgba(255,255,255,0.2)",
392
+ color="white",
393
+ tickfont=dict(color="white")
394
+ ),
395
+ yaxis2=dict(
396
+ title="Close Price",
397
+ overlaying="y",
398
+ side="left",
399
+ position=0.05,
400
+ showgrid=True,
401
+ gridcolor="rgba(255,255,255,0.2)",
402
+ color="white",
403
+ tickfont=dict(color="white")
404
+ ),
405
+ yaxis3=dict(
406
+ title="Rolling Volatility",
407
+ overlaying="y",
408
+ side="right",
409
+ position=0.95,
410
+ showgrid=True,
411
+ gridcolor="rgba(255,255,255,0.2)",
412
+ color="white",
413
+ tickfont=dict(color="white")
414
+ ),
415
+ yaxis4=dict(
416
+ title="Rolling Spread",
417
+ overlaying="y",
418
+ side="right",
419
+ position=1,
420
+ showgrid=True,
421
+ gridcolor="rgba(255,255,255,0.2)",
422
+ color="white",
423
+ tickfont=dict(color="white")
424
+ ),
425
+ legend=dict(
426
+ orientation="h",
427
+ yanchor="bottom",
428
+ y=1.02,
429
+ xanchor="right",
430
+ x=1,
431
+ font=dict(color="white")
432
+ ),
433
+ margin=dict(l=50, r=50, t=80, b=50)
434
+ )
435
+ st.plotly_chart(fig_ts, use_container_width=True)
436
+
437
+ with st.expander(f"Additional Analysis for {interval}", expanded=False):
438
+ st.write("This section shows a preview of the raw data, scatter plots of lagged relationships, and a rolling correlation chart.")
439
+
440
+ st.subheader("Raw Data Preview")
441
+ st.dataframe(data_with_spread, use_container_width=True)
442
+
443
+ lag_period = 1
444
+ lagged_spreads = rolling_spreads.shift(lag_period)
445
+ lagged_volume = volume.shift(lag_period)
446
+ lagged_vol = rolling_vol.shift(lag_period)
447
+
448
+ titles = [
449
+ f"Returns vs Lagged Spreads ({lag_period})",
450
+ f"Spreads vs Lagged Volume ({lag_period})",
451
+ f"Returns vs Lagged Volume ({lag_period})",
452
+ f"Volatility vs Lagged Spreads ({lag_period})",
453
+ f"Lagged Volatility vs Current Spreads ({lag_period})"
454
+ ]
455
+ data_pairs = [
456
+ (lagged_spreads, returns),
457
+ (lagged_volume, rolling_spreads),
458
+ (lagged_volume, returns),
459
+ (lagged_spreads, rolling_vol),
460
+ (lagged_vol, rolling_spreads)
461
+ ]
462
+ colors = ["blue", "red", "green", "orange", "magenta"]
463
+
464
+ fig_scatter = make_subplots(
465
+ rows=1, cols=5,
466
+ shared_xaxes=False, horizontal_spacing=0.08,
467
+ subplot_titles=titles
468
+ )
469
+ for i, (x, y) in enumerate(data_pairs, start=1):
470
+ mask = (~x.isna()) & (~y.isna())
471
+ fig_scatter.add_trace(go.Scatter(
472
+ x=x[mask], y=y[mask],
473
+ mode="markers",
474
+ marker=dict(color=colors[i-1], size=5),
475
+ name=titles[i-1]
476
+ ), row=1, col=i)
477
+ fig_scatter.update_layout(
478
+ template="plotly_dark",
479
+ paper_bgcolor='#0e1117',
480
+ plot_bgcolor='#0e1117',
481
+ title=dict(text=f"Lagged Value Analysis ({lag_period})", font=dict(color="white")),
482
+ xaxis1=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
483
+ xaxis2=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
484
+ xaxis3=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
485
+ xaxis4=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
486
+ xaxis5=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
487
+ yaxis=dict(gridcolor="rgba(255,255,255,0.2)", color="white", tickfont=dict(color="white")),
488
+ legend=dict(font=dict(color="white"))
489
+ )
490
+ st.plotly_chart(fig_scatter, use_container_width=True)
491
+
492
+ window_corr = 60
493
+ lag_period_corr = 1
494
+ lagged_volume_corr = volume.shift(lag_period_corr)
495
+ lagged_vol_corr = rolling_vol.shift(lag_period_corr)
496
+
497
+ rolling_corr_volume_spread = lagged_volume_corr.rolling(window=window_corr).corr(rolling_spreads)
498
+ rolling_corr_volatility_spread = lagged_vol_corr.rolling(window=window_corr).corr(rolling_spreads)
499
+
500
+ fig_corr = go.Figure()
501
+ fig_corr.add_trace(go.Scatter(
502
+ x=rolling_corr_volume_spread.index, y=rolling_corr_volume_spread,
503
+ mode="lines",
504
+ name=f"Rolling Corr (Lagged Volume vs Spread, lag={lag_period_corr}, window={window_corr})",
505
+ line=dict(color="red")
506
+ ))
507
+ fig_corr.add_trace(go.Scatter(
508
+ x=rolling_corr_volatility_spread.index, y=rolling_corr_volatility_spread,
509
+ mode="lines",
510
+ name=f"Rolling Corr (Lagged Volatility vs Spread, lag={lag_period_corr}, window={window_corr})",
511
+ line=dict(color="orange")
512
+ ))
513
+ fig_corr.update_layout(
514
+ template="plotly_dark",
515
+ paper_bgcolor='#0e1117',
516
+ plot_bgcolor='#0e1117',
517
+ title=dict(text=f"Rolling Correlations (Window={window_corr}, Lag={lag_period_corr})", font=dict(color="white")),
518
+ xaxis=dict(
519
+ title="Date",
520
+ tickformat="%Y-%m-%d",
521
+ nticks=20,
522
+ showgrid=True,
523
+ gridcolor="rgba(255,255,255,0.2)",
524
+ color="white",
525
+ tickfont=dict(color="white")
526
+ ),
527
+ yaxis=dict(
528
+ title="Correlation",
529
+ showgrid=True,
530
+ gridcolor="rgba(255,255,255,0.2)",
531
+ color="white",
532
+ tickfont=dict(color="white")
533
+ ),
534
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1, font=dict(color="white")),
535
+ margin=dict(l=50, r=50, t=80, b=50)
536
+ )
537
+ st.plotly_chart(fig_corr, use_container_width=True)
538
+
539
+ st.markdown(
540
+ """
541
+ <style>
542
+ #MainMenu {visibility: hidden;}
543
+ footer {visibility: hidden;}
544
+ </style>
545
+ """,
546
+ unsafe_allow_html=True
547
+ )