QuantumLearner commited on
Commit
100b749
·
verified ·
1 Parent(s): 87a61ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -114
app.py CHANGED
@@ -7,19 +7,23 @@ from datetime import datetime, timedelta
7
 
8
  # Fetch stock data
9
  def get_stock_data(ticker, start_date, end_date):
10
- stock_data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False) # Set auto_adjust=False for unadjusted closes
11
- return stock_data['Close']
 
 
 
 
12
 
13
  # Bootstrapping simulation function
14
  def bootstrap_simulation(data, days, n_iterations=10000):
15
  daily_returns = data.pct_change().dropna()
16
- # Convert to 1D NumPy array to fix previous ValueError
17
- daily_returns = daily_returns.values.flatten()
18
  simulations = np.zeros((n_iterations, days))
 
19
 
20
  for i in range(n_iterations):
21
  sample = np.random.choice(daily_returns, size=days, replace=True)
22
- simulations[i] = np.cumprod(1 + sample) * data.iloc[-1].item() # Use .item() to get scalar value
23
 
24
  return simulations
25
 
@@ -29,7 +33,6 @@ def calculate_probabilities(simulations, thresholds):
29
  below = np.mean(final_prices < thresholds[0])
30
  above = np.mean(final_prices > thresholds[1])
31
  between = np.mean((final_prices >= thresholds[0]) & (final_prices <= thresholds[1]))
32
-
33
  return {'below': below, 'between': between, 'above': above}
34
 
35
  # Calculate percentiles
@@ -37,99 +40,62 @@ def calculate_percentiles(simulations):
37
  percentiles = np.percentile(simulations, [2.5, 16, 50, 84, 97.5], axis=0)
38
  return percentiles
39
 
 
40
  def plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities):
41
  final_bootstrap_prices = bootstrap_simulations[:, -1]
42
-
43
  mean_bootstrap_price = np.mean(final_bootstrap_prices)
44
  median_bootstrap_price = np.median(final_bootstrap_prices)
45
  ci_68_bootstrap = np.percentile(final_bootstrap_prices, [16, 84])
46
  ci_95_bootstrap = np.percentile(final_bootstrap_prices, [2.5, 97.5])
47
- latest_price = data.iloc[-1].item() # Convert Series to scalar with .item()
48
 
49
  fig = go.Figure()
50
-
51
- # Plot for Bootstrapping
52
- fig.add_trace(go.Histogram(x=final_bootstrap_prices, nbinsx=50, name='Simulated Final Prices',
53
- marker_color='blue', opacity=0.7))
54
-
55
- # Replace add_vline with add_trace for vertical lines with legends
56
- fig.add_trace(go.Scatter(
57
- x=[mean_bootstrap_price, mean_bootstrap_price],
58
- y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()],
59
- mode='lines',
60
- line=dict(color='red', dash='dash'),
61
- name=f'Mean: {mean_bootstrap_price:.2f}'
62
- ))
63
-
64
- fig.add_trace(go.Scatter(
65
- x=[median_bootstrap_price, median_bootstrap_price],
66
- y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()],
67
- mode='lines',
68
- line=dict(color='orange', dash='dash'),
69
- name=f'Median: {median_bootstrap_price:.2f}'
70
- ))
71
-
72
- fig.add_trace(go.Scatter(
73
- x=[latest_price, latest_price],
74
- y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()],
75
- mode='lines',
76
- line=dict(color='green', dash='dash'),
77
- name=f'Latest Price: {latest_price:.2f}'
78
- ))
79
-
80
- # Add confidence intervals as vertical shaded regions
81
  fig.add_vrect(x0=ci_68_bootstrap[0], x1=ci_68_bootstrap[1], fillcolor='yellow', opacity=0.2, layer="below", line_width=0)
82
  fig.add_vrect(x0=ci_95_bootstrap[0], x1=ci_95_bootstrap[1], fillcolor='grey', opacity=0.2, layer="below", line_width=0)
83
 
84
- # Annotations for probabilities
85
- textstr = f'P(>{thresholds[1]:.2f}): {bootstrap_probabilities["above"]:.2%}<br>' + \
86
- f'P(<{thresholds[0]:.2f}): {bootstrap_probabilities["below"]:.2%}<br>' + \
87
- f'P({thresholds[0]:.2f} - {thresholds[1]:.2f}): {bootstrap_probabilities["between"]:.2%}'
88
- fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr, showarrow=False,
89
- bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4, font=dict(color="black"))
90
-
91
- # Update layout
92
- fig.update_layout(
93
- title='Bootstrapping Simulation',
94
- xaxis_title='Final Price',
95
- yaxis_title='Frequency',
96
- showlegend=True
97
- )
98
 
 
99
  return fig, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price
100
 
101
-
102
  # Plot price data with simulation cones
103
  def plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities):
104
  last_date = data.index[-1]
105
  future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=days, freq='D')
106
 
107
  fig = go.Figure()
108
-
109
- # Plot historical prices
110
  fig.add_trace(go.Scatter(x=data.index, y=data, mode='lines', name='Historical Prices', line=dict(color='white')))
111
-
112
- # Plot bootstrapping simulation cone
113
- fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[2], mode='lines', name='Bootstrap Median', line=dict(color='red', dash='dash')))
114
- fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[0], fill=None, mode='lines', line=dict(color='lightgrey'), showlegend=False))
115
- fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[4], fill='tonexty', mode='lines', line=dict(color='lightgrey'), name='Bootstrap 95% CI'))
116
- fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[1], fill=None, mode='lines', line=dict(color='lightyellow'), showlegend=False))
117
- fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[3], fill='tonexty', mode='lines', line=dict(color='lightyellow'), name='Bootstrap 68% CI'))
118
-
119
- # Annotate the thresholds
 
 
 
120
  fig.add_hline(y=thresholds[0], line=dict(color='blue', dash='dash'), annotation_text=f'Threshold 1: {thresholds[0]}', annotation_position="top left")
121
  fig.add_hline(y=thresholds[1], line=dict(color='green', dash='dash'), annotation_text=f'Threshold 2: {thresholds[1]}', annotation_position="top left")
122
 
123
- # Add probability annotations
124
- textstr_bootstrap = f'Bootstrap Probabilities:<br>Below {thresholds[0]}: {bootstrap_probabilities["below"]:.2%}<br>' + \
125
- f'Between {thresholds[0]} and {thresholds[1]}: {bootstrap_probabilities["between"]:.2%}<br>' + \
126
- f'Above {thresholds[1]}: {bootstrap_probabilities["above"]:.2%}'
127
- fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr_bootstrap, showarrow=False,
128
- bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4, font=dict(color="black"))
129
 
130
  fig.update_layout(title='Bootstrapping Simulation Cone', xaxis_title='Date', yaxis_title='Price', showlegend=True)
131
  fig.update_xaxes(type='date')
132
-
133
  return fig
134
 
135
  # Streamlit app
@@ -138,7 +104,6 @@ st.title('Future Asset Prices Bootstrap Simulation')
138
 
139
  st.sidebar.header('Input Parameters')
140
 
141
- # How to use (in sidebar, closed by default)
142
  with st.sidebar.expander("How to Use", expanded=False):
143
  st.write("""
144
  1. Enter the stock ticker or crypto pair (e.g., 'AAPL' or 'BTC-USD') in the 'Ticker' field.
@@ -149,7 +114,6 @@ with st.sidebar.expander("How to Use", expanded=False):
149
  6. Analyze the resulting charts and statistics.
150
  """)
151
 
152
- # Ticker and dates in an expander (open by default)
153
  with st.sidebar.expander("Symbol and Dates", expanded=True):
154
  ticker = st.text_input('Enter Asset Symbol', 'ASML.AS', help="Enter a stock ticker (e.g., AAPL) or a crypto pair (e.g., BTC-USD)")
155
  start_date = st.date_input('Start Date', pd.to_datetime('2020-01-01'), help="Select the start date for historical data")
@@ -168,7 +132,6 @@ This application simulates future asset prices using bootstrapping simulation me
168
  You can specify the stock ticker or crypto pair, the date range, the number of simulation days, the number of simulations, and price thresholds.
169
  The simulation results will show the probability of the price falling below, between, or above the specified thresholds.""")
170
 
171
-
172
  with st.expander("Click here to read the description"):
173
  st.write("""
174
  ### Description
@@ -203,8 +166,6 @@ with st.expander("Click here to read the description"):
203
  4. **Estimate**: Use the bootstrap statistics to estimate the mean, standard error, and confidence intervals of \( \theta \).
204
  """)
205
 
206
-
207
-
208
  st.write("""**Results:**
209
  The app will display two charts:
210
  1. The distribution of the final simulated prices with key statistical measures.
@@ -212,42 +173,55 @@ The app will display two charts:
212
  """)
213
 
214
  if st.sidebar.button('Run Simulation'):
215
- data = get_stock_data(ticker, start_date, end_date)
216
-
217
- bootstrap_simulations = bootstrap_simulation(data, days, n_iterations)
218
-
219
- bootstrap_probabilities = calculate_probabilities(bootstrap_simulations, thresholds)
220
-
221
- bootstrap_percentiles = calculate_percentiles(bootstrap_simulations)
222
-
223
- fig1, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price = plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities)
224
- fig2 = plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities)
225
-
226
- st.plotly_chart(fig1)
227
- st.plotly_chart(fig2)
228
-
229
- st.write(f"""
230
- ### Interpretation of Results
231
-
232
- **Distribution of Final Simulated Prices:**
233
- - **Mean Final Price:** {mean_bootstrap_price:.2f}
234
- - **Median Final Price:** {median_bootstrap_price:.2f}
235
- - **68% Confidence Interval (CI):** [{ci_68_bootstrap[0]:.2f}, {ci_68_bootstrap[1]:.2f}]
236
- - **95% Confidence Interval (CI):** [{ci_95_bootstrap[0]:.2f}, {ci_95_bootstrap[1]:.2f}]
237
- - **Latest Price:** {latest_price:.2f}
238
-
239
- **Bootstrapping Simulation Cone:**
240
- - **Bootstrap Median:** The median of the simulated future prices for each day.
241
- - **Bootstrap 68% CI:** The 68% confidence interval for the simulated future prices.
242
- - **Bootstrap 95% CI:** The 95% confidence interval for the simulated future prices.
243
- - **Threshold 1 and Threshold 2:** {threshold1:.2f}, {threshold2:.2f}
244
- - **Probability Annotations:**
245
- - The probability of the price being below Threshold 1: {bootstrap_probabilities["below"]:.2%}
246
- - The probability of the price being between Threshold 1 and Threshold 2: {bootstrap_probabilities["between"]:.2%}
247
- - The probability of the price being above Threshold 2: {bootstrap_probabilities["above"]:.2%}
248
-
249
- These results help in understanding the potential future movements of the stock or cryptocurrency price based on historical data and bootstrapping simulation.
250
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
  hide_streamlit_style = """
253
  <style>
@@ -257,7 +231,6 @@ footer {visibility: hidden;}
257
  """
258
  st.markdown(hide_streamlit_style, unsafe_allow_html=True)
259
 
260
-
261
  st.markdown("""
262
  <style>
263
  .stSelectbox, .stNumberInput {
 
7
 
8
  # Fetch stock data
9
  def get_stock_data(ticker, start_date, end_date):
10
+ data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False) # Unadjusted prices
11
+ if isinstance(data.columns, pd.MultiIndex): # Flatten multi-index columns
12
+ data.columns = data.columns.get_level_values(0)
13
+ if data.empty:
14
+ raise ValueError(f"No data found for {ticker} from {start_date} to {end_date}")
15
+ return data['Close']
16
 
17
  # Bootstrapping simulation function
18
  def bootstrap_simulation(data, days, n_iterations=10000):
19
  daily_returns = data.pct_change().dropna()
20
+ daily_returns = daily_returns.values.flatten() # Ensure 1D array
 
21
  simulations = np.zeros((n_iterations, days))
22
+ last_price = data.iloc[-1].item() # Scalar last price
23
 
24
  for i in range(n_iterations):
25
  sample = np.random.choice(daily_returns, size=days, replace=True)
26
+ simulations[i] = np.cumprod(1 + sample) * last_price
27
 
28
  return simulations
29
 
 
33
  below = np.mean(final_prices < thresholds[0])
34
  above = np.mean(final_prices > thresholds[1])
35
  between = np.mean((final_prices >= thresholds[0]) & (final_prices <= thresholds[1]))
 
36
  return {'below': below, 'between': between, 'above': above}
37
 
38
  # Calculate percentiles
 
40
  percentiles = np.percentile(simulations, [2.5, 16, 50, 84, 97.5], axis=0)
41
  return percentiles
42
 
43
+ # Plot distribution of final prices
44
  def plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities):
45
  final_bootstrap_prices = bootstrap_simulations[:, -1]
 
46
  mean_bootstrap_price = np.mean(final_bootstrap_prices)
47
  median_bootstrap_price = np.median(final_bootstrap_prices)
48
  ci_68_bootstrap = np.percentile(final_bootstrap_prices, [16, 84])
49
  ci_95_bootstrap = np.percentile(final_bootstrap_prices, [2.5, 97.5])
50
+ latest_price = data.iloc[-1].item() # Scalar for plotting
51
 
52
  fig = go.Figure()
53
+ fig.add_trace(go.Histogram(x=final_bootstrap_prices, nbinsx=50, name='Simulated Final Prices', marker_color='blue', opacity=0.7))
54
+ fig.add_trace(go.Scatter(x=[mean_bootstrap_price, mean_bootstrap_price], y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()], mode='lines', line=dict(color='red', dash='dash'), name=f'Mean: {mean_bootstrap_price:.2f}'))
55
+ fig.add_trace(go.Scatter(x=[median_bootstrap_price, median_bootstrap_price], y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()], mode='lines', line=dict(color='orange', dash='dash'), name=f'Median: {median_bootstrap_price:.2f}'))
56
+ fig.add_trace(go.Scatter(x=[latest_price, latest_price], y=[0, np.histogram(final_bootstrap_prices, bins=50)[0].max()], mode='lines', line=dict(color='green', dash='dash'), name=f'Latest Price: {latest_price:.2f}'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  fig.add_vrect(x0=ci_68_bootstrap[0], x1=ci_68_bootstrap[1], fillcolor='yellow', opacity=0.2, layer="below", line_width=0)
58
  fig.add_vrect(x0=ci_95_bootstrap[0], x1=ci_95_bootstrap[1], fillcolor='grey', opacity=0.2, layer="below", line_width=0)
59
 
60
+ textstr = (f'P(>{thresholds[1]:.2f}): {bootstrap_probabilities["above"]:.2%}<br>'
61
+ f'P(<{thresholds[0]:.2f}): {bootstrap_probabilities["below"]:.2%}<br>'
62
+ f'P({thresholds[0]:.2f} - {thresholds[1]:.2f}): {bootstrap_probabilities["between"]:.2%}')
63
+ fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr, showarrow=False, bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4)
 
 
 
 
 
 
 
 
 
 
64
 
65
+ fig.update_layout(title='Bootstrapping Simulation', xaxis_title='Final Price', yaxis_title='Frequency', showlegend=True)
66
  return fig, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price
67
 
 
68
  # Plot price data with simulation cones
69
  def plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities):
70
  last_date = data.index[-1]
71
  future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=days, freq='D')
72
 
73
  fig = go.Figure()
74
+ # Historical prices
 
75
  fig.add_trace(go.Scatter(x=data.index, y=data, mode='lines', name='Historical Prices', line=dict(color='white')))
76
+
77
+ # Simulation cone (check for valid percentiles)
78
+ if bootstrap_percentiles.shape[1] == len(future_dates):
79
+ fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[2], mode='lines', name='Bootstrap Median', line=dict(color='red', dash='dash')))
80
+ fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[0], fill=None, mode='lines', line=dict(color='lightgrey'), showlegend=False))
81
+ fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[4], fill='tonexty', mode='lines', line=dict(color='lightgrey'), name='Bootstrap 95% CI'))
82
+ fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[1], fill=None, mode='lines', line=dict(color='lightyellow'), showlegend=False))
83
+ fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[3], fill='tonexty', mode='lines', line=dict(color='lightyellow'), name='Bootstrap 68% CI'))
84
+ else:
85
+ st.warning("Simulation percentiles length does not match future dates. Check simulation output.")
86
+
87
+ # Thresholds
88
  fig.add_hline(y=thresholds[0], line=dict(color='blue', dash='dash'), annotation_text=f'Threshold 1: {thresholds[0]}', annotation_position="top left")
89
  fig.add_hline(y=thresholds[1], line=dict(color='green', dash='dash'), annotation_text=f'Threshold 2: {thresholds[1]}', annotation_position="top left")
90
 
91
+ # Probability annotations
92
+ textstr_bootstrap = (f'Bootstrap Probabilities:<br>Below {thresholds[0]}: {bootstrap_probabilities["below"]:.2%}<br>'
93
+ f'Between {thresholds[0]} and {thresholds[1]}: {bootstrap_probabilities["between"]:.2%}<br>'
94
+ f'Above {thresholds[1]}: {bootstrap_probabilities["above"]:.2%}')
95
+ fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr_bootstrap, showarrow=False, bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4)
 
96
 
97
  fig.update_layout(title='Bootstrapping Simulation Cone', xaxis_title='Date', yaxis_title='Price', showlegend=True)
98
  fig.update_xaxes(type='date')
 
99
  return fig
100
 
101
  # Streamlit app
 
104
 
105
  st.sidebar.header('Input Parameters')
106
 
 
107
  with st.sidebar.expander("How to Use", expanded=False):
108
  st.write("""
109
  1. Enter the stock ticker or crypto pair (e.g., 'AAPL' or 'BTC-USD') in the 'Ticker' field.
 
114
  6. Analyze the resulting charts and statistics.
115
  """)
116
 
 
117
  with st.sidebar.expander("Symbol and Dates", expanded=True):
118
  ticker = st.text_input('Enter Asset Symbol', 'ASML.AS', help="Enter a stock ticker (e.g., AAPL) or a crypto pair (e.g., BTC-USD)")
119
  start_date = st.date_input('Start Date', pd.to_datetime('2020-01-01'), help="Select the start date for historical data")
 
132
  You can specify the stock ticker or crypto pair, the date range, the number of simulation days, the number of simulations, and price thresholds.
133
  The simulation results will show the probability of the price falling below, between, or above the specified thresholds.""")
134
 
 
135
  with st.expander("Click here to read the description"):
136
  st.write("""
137
  ### Description
 
166
  4. **Estimate**: Use the bootstrap statistics to estimate the mean, standard error, and confidence intervals of \( \theta \).
167
  """)
168
 
 
 
169
  st.write("""**Results:**
170
  The app will display two charts:
171
  1. The distribution of the final simulated prices with key statistical measures.
 
173
  """)
174
 
175
  if st.sidebar.button('Run Simulation'):
176
+ try:
177
+ st.write(f"Fetching data for {ticker}...")
178
+ data = get_stock_data(ticker, start_date, end_date)
179
+
180
+ # Debug data
181
+ st.write(f"Data shape: {data.shape}")
182
+ st.write(f"Last few rows: {data.tail()}")
183
+
184
+ bootstrap_simulations = bootstrap_simulation(data, days, n_iterations)
185
+ bootstrap_probabilities = calculate_probabilities(bootstrap_simulations, thresholds)
186
+ bootstrap_percentiles = calculate_percentiles(bootstrap_simulations)
187
+
188
+ # Debug simulation output
189
+ st.write(f"Simulations shape: {bootstrap_simulations.shape}")
190
+ st.write(f"Percentiles shape: {bootstrap_percentiles.shape}")
191
+
192
+ fig1, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price = plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities)
193
+ fig2 = plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities)
194
+
195
+ st.plotly_chart(fig1)
196
+ if bootstrap_percentiles.shape[1] == days:
197
+ st.plotly_chart(fig2)
198
+ else:
199
+ st.error("Time series plot failed: Percentiles length does not match simulation days.")
200
+
201
+ st.write(f"""
202
+ ### Interpretation of Results
203
+
204
+ **Distribution of Final Simulated Prices:**
205
+ - **Mean Final Price:** {mean_bootstrap_price:.2f}
206
+ - **Median Final Price:** {median_bootstrap_price:.2f}
207
+ - **68% Confidence Interval (CI):** [{ci_68_bootstrap[0]:.2f}, {ci_68_bootstrap[1]:.2f}]
208
+ - **95% Confidence Interval (CI):** [{ci_95_bootstrap[0]:.2f}, {ci_95_bootstrap[1]:.2f}]
209
+ - **Latest Price:** {latest_price:.2f}
210
+
211
+ **Bootstrapping Simulation Cone:**
212
+ - **Bootstrap Median:** The median of the simulated future prices for each day.
213
+ - **Bootstrap 68% CI:** The 68% confidence interval for the simulated future prices.
214
+ - **Bootstrap 95% CI:** The 95% confidence interval for the simulated future prices.
215
+ - **Threshold 1 and Threshold 2:** {threshold1:.2f}, {threshold2:.2f}
216
+ - **Probability Annotations:**
217
+ - The probability of the price being below Threshold 1: {bootstrap_probabilities["below"]:.2%}
218
+ - The probability of the price being between Threshold 1 and Threshold 2: {bootstrap_probabilities["between"]:.2%}
219
+ - The probability of the price being above Threshold 2: {bootstrap_probabilities["above"]:.2%}
220
+
221
+ These results help in understanding the potential future movements of the stock or cryptocurrency price based on historical data and bootstrapping simulation.
222
+ """)
223
+ except Exception as e:
224
+ st.error(f"Error: {str(e)}. Check ticker, date range, or try again.")
225
 
226
  hide_streamlit_style = """
227
  <style>
 
231
  """
232
  st.markdown(hide_streamlit_style, unsafe_allow_html=True)
233
 
 
234
  st.markdown("""
235
  <style>
236
  .stSelectbox, .stNumberInput {