QuantumLearner commited on
Commit
963e099
·
verified ·
1 Parent(s): 9d58ded

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +297 -0
app.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import yfinance as yf
3
+ import pandas as pd
4
+ import numpy as np
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ import seaborn as sns
8
+ import matplotlib.pyplot as plt
9
+ import matplotlib.colors as mcolors
10
+
11
+ # Streamlit app setup
12
+ st.set_page_config(layout="wide")
13
+
14
+ st.title("Conditional Probability Analysis of Stock Price Movements")
15
+
16
+ st.markdown(
17
+ """
18
+ This tool analyzes the conditional probabilities of price movements using historical data.
19
+ You can specify the stock symbol, the periods for the analysis, and the percentage change thresholds.
20
+ The analysis provides insights into the likelihood of subsequent price changes based on initial movements.
21
+ """
22
+ )
23
+
24
+ st.markdown(
25
+ """
26
+ - **First Move**: The percentage change in stock price over a specified number of days.
27
+ - **Second Move**: The percentage change in stock price over another specified number of days, analyzed conditionally based on the first move.
28
+ - **Thresholds**: The percentage change thresholds define what constitutes a significant move for both the first and second periods.
29
+ """
30
+ )
31
+ #st.markdown("#### Conditional Probability Formulas")
32
+ st.latex(r"""
33
+ P(P2 \geq T2 \mid |P1| \geq T1)
34
+ """)
35
+ st.latex(r"""
36
+ P(P2 \leq -T2 \mid |P1| \geq T1)
37
+ """)
38
+
39
+ st.markdown(
40
+ """
41
+ - \(P1\): Percentage change in stock price during the first period.
42
+ - \(P2\): Percentage change in stock price during the second period.
43
+ - \(T1\): Threshold for significant change in the first period.
44
+ - \(T2\): Threshold for significant change in the second period.
45
+
46
+ The formulas calculate the probability of the second move (\(P2\)) being above or below the threshold (\(T2\)) given that the first move (\(P1\)) is above the threshold (\(T1\)).
47
+ """
48
+ )
49
+
50
+ # Sidebar for user inputs
51
+ with st.sidebar:
52
+ st.header("How to use")
53
+ st.write(
54
+ """
55
+ 1. Select the stock or Crypto symbol.
56
+ 2. Choose the analysis periods.
57
+ 3. Set the percentage change thresholds.
58
+ 4. Click 'Run Analysis' to see the results.
59
+ """
60
+ )
61
+
62
+ symbol = st.text_input("Asset Symbol", value="ASML.AS")
63
+ start_date = st.date_input("Start Date", value=pd.to_datetime("2000-01-01"))
64
+ end_date = st.date_input("End Date", value=pd.to_datetime("2025-12-02"))
65
+
66
+ first_move_days = st.number_input("First Move Days", min_value=1, value=1)
67
+ second_move_days = st.number_input("Second Move Days", min_value=1, value=2)
68
+
69
+ first_threshold_percentage = st.number_input("First Threshold (%)", min_value=0.0, value=10.3) / 100
70
+ second_threshold_percentage = st.number_input("Second Threshold (%)", min_value=0.0, value=8.0) / 100
71
+
72
+ run_button = st.button("Run Analysis")
73
+
74
+ if run_button:
75
+ # Download historical data for the selected stock
76
+ data = yf.download(symbol, start=start_date, end=end_date)
77
+
78
+ # Calculate the first move percentage change
79
+ data[f'{first_move_days}d_pct_change_first'] = data['Adj Close'].pct_change(first_move_days)
80
+
81
+ # Calculate the second move percentage change only if the first move change is above the threshold
82
+ data.loc[data[f'{first_move_days}d_pct_change_first'].abs() >= first_threshold_percentage, f'{second_move_days}d_pct_change_second'] = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days)
83
+
84
+ # Calculate the frequency of the conditional threshold percentage change (increase or decrease) for the second move
85
+ total_periods = len(data)
86
+ down_periods = len(data[data[f'{second_move_days}d_pct_change_second'] <= -second_threshold_percentage])
87
+ up_periods = len(data[data[f'{second_move_days}d_pct_change_second'] >= second_threshold_percentage])
88
+ down_frequency = down_periods / total_periods
89
+ up_frequency = up_periods / total_periods
90
+
91
+ st.markdown(
92
+ """
93
+ #### Distribution of Second Move Percentage Changes
94
+ This plot shows the distribution of the second move percentage changes. It highlights the probabilities of the second move being above or below the threshold given the first move exceeded its threshold.
95
+ """
96
+ )
97
+
98
+
99
+ # Plot the distribution of second move percentage changes
100
+ fig1 = px.histogram(data, x=f'{second_move_days}d_pct_change_second', nbins=100, title=f'Distribution of {second_move_days}-day {second_threshold_percentage * 100:.2f}% changes for {symbol} after a {first_move_days}-day {first_threshold_percentage * 100:.2f}% change')
101
+ fig1.add_vline(x=-second_threshold_percentage, line_dash="dash", line_color="red", annotation_text=f"Probability of {second_move_days}-day {second_threshold_percentage * 100:.2f}% decrease after a {first_move_days}-day {first_threshold_percentage * 100:.2f}% change: {down_frequency * 100:.2f}%")
102
+ fig1.add_vline(x=second_threshold_percentage, line_dash="dash", line_color="blue", annotation_text=f"Probability of {second_move_days}-day {second_threshold_percentage * 100:.2f}% increase after a {first_move_days}-day {first_threshold_percentage * 100:.2f}% change: {up_frequency * 100:.2f}%")
103
+ fig1.update_xaxes(title_text='Percentage change')
104
+ fig1.update_yaxes(title_text='Frequency')
105
+
106
+
107
+ # Plot the stock price
108
+ st.markdown(
109
+ """
110
+ #### Stock Price and Significant Moves
111
+ This plot shows the adjusted closing price of the stock over time. Markers indicate significant moves based on the defined thresholds.
112
+ """
113
+ )
114
+
115
+
116
+ # Plot the stock price
117
+ fig2 = go.Figure()
118
+ fig2.add_trace(go.Scatter(x=data.index, y=data['Adj Close'], mode='lines', name='Adjusted Close Price'))
119
+
120
+ up_instances = data[data[f'{second_move_days}d_pct_change_second'] >= second_threshold_percentage]
121
+ down_instances = data[data[f'{second_move_days}d_pct_change_second'] <= -second_threshold_percentage]
122
+
123
+ fig2.add_trace(go.Scatter(x=up_instances.index, y=up_instances['Adj Close'], mode='markers', marker=dict(color='blue', symbol='triangle-up', size=10), name=f"{second_move_days}-day {second_threshold_percentage * 100:.2f}% increase after {first_move_days}-day {first_threshold_percentage * 100:.2f}% change"))
124
+ fig2.add_trace(go.Scatter(x=down_instances.index, y=down_instances['Adj Close'], mode='markers', marker=dict(color='red', symbol='triangle-down', size=10), name=f"{second_move_days}-day {second_threshold_percentage * 100:.2f}% decrease after {first_move_days}-day {first_threshold_percentage * 100:.2f}% change"))
125
+
126
+ fig2.update_layout(title=f'Stock Price for {symbol} with {second_move_days}-day {second_threshold_percentage * 100:.2f}% changes after a {first_move_days}-day {first_threshold_percentage * 100:.2f}% change markers', xaxis_title='Date', yaxis_title='Adjusted Close Price')
127
+
128
+ st.plotly_chart(fig1, use_container_width=True)
129
+ st.plotly_chart(fig2, use_container_width=True)
130
+
131
+
132
+ # Rolling window analysis with different window sizes
133
+ st.markdown(
134
+ """
135
+ #### Rolling Window Analysis
136
+ This analysis examines the conditional probabilities of significant price movements over different rolling windows (e.g., 6 months, 1 year, 2 years). It helps to understand how these probabilities change over time.
137
+ """
138
+ )
139
+
140
+
141
+
142
+ # Rolling window analysis with different window sizes
143
+ rolling_windows = [126, 252, 504] # 6 months, 1 year, and 2 years
144
+
145
+ def get_shade(base_color, factor):
146
+ return tuple(min(1, base + factor) for base in base_color)
147
+
148
+ base_red = (1, 0, 0)
149
+ base_blue = (0, 0, 1)
150
+
151
+ colors_down = [mcolors.to_hex(get_shade(base_red, i * 0.35)) for i in range(len(rolling_windows))]
152
+ colors_up = [mcolors.to_hex(get_shade(base_blue, i * 0.35)) for i in range(len(rolling_windows))]
153
+
154
+ fig3 = go.Figure()
155
+
156
+ for i, window in enumerate(rolling_windows):
157
+ rolling_down = data['Adj Close'].rolling(window=window).apply(
158
+ lambda x: np.mean((x.pct_change(first_move_days).abs() >= first_threshold_percentage) &
159
+ (x.pct_change(second_move_days).shift(-second_move_days) <= -second_threshold_percentage))
160
+ )
161
+ rolling_up = data['Adj Close'].rolling(window=window).apply(
162
+ lambda x: np.mean((x.pct_change(first_move_days).abs() >= first_threshold_percentage) &
163
+ (x.pct_change(second_move_days).shift(-second_move_days) >= second_threshold_percentage))
164
+ )
165
+
166
+ fig3.add_trace(go.Scatter(x=data.index, y=rolling_down, mode='lines', name=f'Down Movements (Window: {window} days)', line=dict(color=colors_down[i])))
167
+ fig3.add_trace(go.Scatter(x=data.index, y=rolling_up, mode='lines', name=f'Up Movements (Window: {window} days)', line=dict(color=colors_up[i])))
168
+
169
+ fig3.update_layout(
170
+ title=f'Rolling Window Analysis of {second_move_days}-day {second_threshold_percentage * 100:.2f}% Conditional Price Movements\nconditional on {first_move_days}-day {first_threshold_percentage * 100:.2f}% change',
171
+ xaxis_title='Date',
172
+ yaxis_title='Conditional Probability',
173
+ legend_title_text='Movements'
174
+ )
175
+
176
+ st.plotly_chart(fig3, use_container_width=True)
177
+
178
+
179
+
180
+ # Heatmap analysis
181
+ st.markdown(
182
+ """
183
+ #### Heatmap Analysis
184
+ These heatmaps show the conditional probabilities of second move percentage changes across a range of thresholds. Each heatmap represents a different scenario, such as up movements, down movements, or mixed conditions.
185
+ """
186
+ )
187
+
188
+
189
+ # Heatmap analysis
190
+ thresholds = np.linspace(0.01, 0.15, 11)
191
+ data['30d_volatility'] = data['Adj Close'].pct_change().rolling(window=30).std()
192
+
193
+ prob_matrix = np.zeros((len(thresholds), len(thresholds)), dtype=object)
194
+ prob_matrix_downs = np.zeros((len(thresholds), len(thresholds)), dtype=object)
195
+ prob_matrix_ups = np.zeros((len(thresholds), len(thresholds)), dtype=object)
196
+ prob_matrix_up_after_down = np.zeros((len(thresholds), len(thresholds)), dtype=object)
197
+ prob_matrix_down_after_up = np.zeros((len(thresholds), len(thresholds)), dtype=object)
198
+
199
+ for i, first_threshold in enumerate(thresholds):
200
+ for j, second_threshold in enumerate(thresholds):
201
+ first_condition = data['Adj Close'].pct_change(first_move_days).abs() >= first_threshold
202
+ second_condition = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days).abs() >= second_threshold
203
+ combined_condition = first_condition & second_condition
204
+ prob_matrix[i, j] = (
205
+ np.mean(combined_condition),
206
+ np.mean(data['30d_volatility'][combined_condition])
207
+ )
208
+
209
+ first_condition_down = data['Adj Close'].pct_change(first_move_days) <= -first_threshold
210
+ second_condition_down = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days) <= -second_threshold
211
+ combined_condition_downs = first_condition_down & second_condition_down
212
+ prob_matrix_downs[i, j] = (
213
+ np.mean(combined_condition_downs),
214
+ np.mean(data['30d_volatility'][combined_condition_downs])
215
+ )
216
+
217
+ first_condition_up = data['Adj Close'].pct_change(first_move_days) >= first_threshold
218
+ second_condition_up = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days) >= second_threshold
219
+ combined_condition_ups = first_condition_up & second_condition_up
220
+ prob_matrix_ups[i, j] = (
221
+ np.mean(combined_condition_ups),
222
+ np.mean(data['30d_volatility'][combined_condition_ups])
223
+ )
224
+
225
+ second_condition_up_after_down = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days) >= second_threshold
226
+ combined_condition_up_after_down = first_condition_down & second_condition_up_after_down
227
+ prob_matrix_up_after_down[i, j] = (
228
+ np.mean(combined_condition_up_after_down),
229
+ np.mean(data['30d_volatility'][combined_condition_up_after_down])
230
+ )
231
+
232
+ second_condition_down_after_up = data['Adj Close'].shift(-second_move_days).pct_change(second_move_days) <= -second_threshold
233
+ combined_condition_down_after_up = first_condition_up & second_condition_down_after_up
234
+ prob_matrix_down_after_up[i, j] = (
235
+ np.mean(combined_condition_down_after_up),
236
+ np.mean(data['30d_volatility'][combined_condition_down_after_up])
237
+ )
238
+
239
+ def format_annotation(value):
240
+ return f"{value[0]:.3f}\n({value[1]:.3f})" if value[0] > 0 else "N/A"
241
+
242
+ annot_matrix = np.vectorize(format_annotation)(prob_matrix)
243
+ annot_matrix_downs = np.vectorize(format_annotation)(prob_matrix_downs)
244
+ annot_matrix_ups = np.vectorize(format_annotation)(prob_matrix_ups)
245
+ annot_matrix_up_after_down = np.vectorize(format_annotation)(prob_matrix_up_after_down)
246
+ annot_matrix_down_after_up = np.vectorize(format_annotation)(prob_matrix_down_after_up)
247
+
248
+ plt.rcParams['axes.facecolor'] = 'black'
249
+ plt.rcParams['figure.facecolor'] = 'black'
250
+ plt.rcParams['text.color'] = 'white'
251
+ plt.rcParams['xtick.color'] = 'white'
252
+ plt.rcParams['ytick.color'] = 'white'
253
+ plt.rcParams['axes.labelcolor'] = 'white'
254
+ plt.rcParams['axes.edgecolor'] = 'white'
255
+
256
+ fig, ax = plt.subplots(figsize=(12, 6))
257
+ sns.heatmap(np.array([[v[0] for v in row] for row in prob_matrix]), xticklabels=np.round(thresholds, 2), yticklabels=np.round(thresholds, 2), cmap='coolwarm', annot=annot_matrix, fmt="", cbar_kws={'label': 'Conditional Probability'}, ax=ax)
258
+ ax.set_xlabel(f'Second Move Threshold')
259
+ ax.set_ylabel(f'First Move Threshold')
260
+ ax.set_title(f'Conditional Probabilities of {second_move_days}-day Absolute Price Movements for {symbol}\nconditional on {first_move_days}-day Absolute Movements (30d Volatility in Parentheses)')
261
+ st.pyplot(fig)
262
+
263
+ fig, ax = plt.subplots(figsize=(12, 6))
264
+ sns.heatmap(np.array([[v[0] for v in row] for row in prob_matrix_downs]), xticklabels=np.round(thresholds, 2), yticklabels=np.round(thresholds, 2), cmap='coolwarm', annot=annot_matrix_downs, fmt="", cbar_kws={'label': 'Conditional Probability'}, ax=ax)
265
+ ax.set_xlabel(f'Second Move Down Threshold')
266
+ ax.set_ylabel(f'First Move Down Threshold')
267
+ ax.set_title(f'Conditional Probabilities of {second_move_days}-day Down Price Movements for {symbol}\nconditional on {first_move_days}-day Down Movements (30d Volatility in Parentheses)')
268
+ st.pyplot(fig)
269
+
270
+ fig, ax = plt.subplots(figsize=(12, 6))
271
+ sns.heatmap(np.array([[v[0] for v in row] for row in prob_matrix_ups]), xticklabels=np.round(thresholds, 2), yticklabels=np.round(thresholds, 2), cmap='coolwarm', annot=annot_matrix_ups, fmt="", cbar_kws={'label': 'Conditional Probability'}, ax=ax)
272
+ ax.set_xlabel(f'Second Move Up Threshold')
273
+ ax.set_ylabel(f'First Move Up Threshold')
274
+ ax.set_title(f'Conditional Probabilities of {second_move_days}-day Up Price Movements for {symbol}\nconditional on {first_move_days}-day Up Movements (30d Volatility in Parentheses)')
275
+ st.pyplot(fig)
276
+
277
+ fig, ax = plt.subplots(figsize=(12, 6))
278
+ sns.heatmap(np.array([[v[0] for v in row] for row in prob_matrix_up_after_down]), xticklabels=np.round(thresholds, 2), yticklabels=np.round(thresholds, 2), cmap='coolwarm', annot=annot_matrix_up_after_down, fmt="", cbar_kws={'label': 'Conditional Probability'}, ax=ax)
279
+ ax.set_xlabel(f'Second Move Up Threshold')
280
+ ax.set_ylabel(f'First Move Down Threshold')
281
+ ax.set_title(f'Conditional Probabilities of {second_move_days}-day Up Price Movements for {symbol}\nconditional on {first_move_days}-day Down Movements (30d Volatility in Parentheses)')
282
+ st.pyplot(fig)
283
+
284
+ fig, ax = plt.subplots(figsize=(12, 6))
285
+ sns.heatmap(np.array([[v[0] for v in row] for row in prob_matrix_down_after_up]), xticklabels=np.round(thresholds, 2), yticklabels=np.round(thresholds, 2), cmap='coolwarm', annot=annot_matrix_down_after_up, fmt="", cbar_kws={'label': 'Conditional Probability'}, ax=ax)
286
+ ax.set_xlabel(f'Second Move Down Threshold')
287
+ ax.set_ylabel(f'First Move Up Threshold')
288
+ ax.set_title(f'Conditional Probabilities of {second_move_days}-day Down Price Movements for {symbol}\nconditional on {first_move_days}-day Up Movements (30d Volatility in Parentheses)')
289
+ st.pyplot(fig)
290
+
291
+ hide_streamlit_style = """
292
+ <style>
293
+ #MainMenu {visibility: hidden;}
294
+ footer {visibility: hidden;}
295
+ </style>
296
+ """
297
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)