markytools commited on
Commit
297af86
·
1 Parent(s): 08aff20

beautified stock analysis app

Browse files
Files changed (2) hide show
  1. .gitignore +3 -0
  2. app.py +290 -90
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ app_local.py
3
+ app_localBackup.py
app.py CHANGED
@@ -5,16 +5,17 @@ import plotly.graph_objs as go
5
  import numpy as np
6
  from plotly.subplots import make_subplots
7
  import os
 
8
 
9
  from langchain_openai import ChatOpenAI
10
 
11
 
12
- isPswdValid = False # Set to True to temporarily disable password checking
13
- OPEN_ROUTER_KEY = st.secrets["OPEN_ROUTER_KEY"]
14
- OPEN_ROUTER_MODEL = "meta-llama/llama-3.1-70b-instruct:free"
15
 
16
  try:
17
- pswdVal = st.experimental_get_query_params()['pwd'][0]
18
  if pswdVal==st.secrets["PSWD"]:
19
  isPswdValid = True
20
  except:
@@ -27,13 +28,92 @@ else:
27
  llm = ChatOpenAI(model=OPEN_ROUTER_MODEL, temperature=0.1, openai_api_key=OPEN_ROUTER_KEY, openai_api_base="https://openrouter.ai/api/v1")
28
 
29
  # Set the Streamlit app title and icon
30
- st.set_page_config(page_title="Stock Analysis", page_icon="📈")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  # Create a Streamlit sidebar for user input
33
  st.sidebar.title("Stock Analysis")
34
  ticker_symbol = st.sidebar.text_input("Enter Stock Ticker Symbol:", value='AAPL')
35
- start_date = st.sidebar.date_input("Start Date", pd.to_datetime('2024-01-01'))
36
- end_date = st.sidebar.date_input("End Date", pd.to_datetime('2024-10-01'))
 
 
 
 
37
 
38
  # Fetch stock data from Yahoo Finance
39
  try:
@@ -42,86 +122,206 @@ else:
42
  st.error("Error fetching stock data. Please check the ticker symbol and date range.")
43
  df = stock_data
44
  df.reset_index(inplace=True) # Reset index to ensure 'Date' becomes a column
45
-
46
- # Technical Indicators
47
- st.header("Stock Price Chart")
48
-
49
- # Create figure with secondary y-axis
50
- fig = make_subplots(specs=[[{"secondary_y": True}]])
51
-
52
- # include candlestick with rangeselector
53
- fig.add_trace(go.Candlestick(x=df['Date'], # Except date, query all other data using Symbol
54
- open=df['Open'][ticker_symbol], high=df['High'][ticker_symbol],
55
- low=df['Low'][ticker_symbol], close=df['Close'][ticker_symbol]),
56
- secondary_y=True)
57
-
58
- # include a go.Bar trace for volumes
59
- fig.add_trace(go.Bar(x=df['Date'], y=df['Volume'][ticker_symbol]),
60
- secondary_y=False)
61
-
62
- fig.layout.yaxis2.showgrid=False
63
- st.plotly_chart(fig)
64
-
65
- # Technical Indicators
66
- st.header("Technical Indicators")
67
-
68
- # Moving Averages
69
- st.subheader("Moving Averages")
70
- df['SMA_20'] = df['Close'][ticker_symbol].rolling(window=20).mean()
71
- df['SMA_50'] = df['Close'][ticker_symbol].rolling(window=50).mean()
72
- fig = go.Figure()
73
- fig.add_trace(go.Scatter(x=df['Date'], y=df['Close'][ticker_symbol], mode='lines', name='Close Price'))
74
- fig.add_trace(go.Scatter(x=df['Date'], y=df['SMA_20'], mode='lines', name='20-Day SMA'))
75
- fig.add_trace(go.Scatter(x=df['Date'], y=df['SMA_50'], mode='lines', name='50-Day SMA'))
76
- fig.update_layout(title="Moving Averages", xaxis_title="Date", yaxis_title="Price (USD)")
77
- st.plotly_chart(fig)
78
-
79
- # RSI (Manual Calculation)
80
- st.subheader("Relative Strength Index (RSI)")
81
- window_length = 14
82
-
83
- # Calculate the daily price changes
84
- delta = df['Close'][ticker_symbol].diff()
85
-
86
- # Separate gains and losses
87
- gain = delta.where(delta > 0, 0)
88
- loss = -delta.where(delta < 0, 0)
89
-
90
- # Calculate the average gain and average loss
91
- avg_gain = gain.rolling(window=window_length, min_periods=1).mean()
92
- avg_loss = loss.rolling(window=window_length, min_periods=1).mean()
93
-
94
- # Calculate the RSI
95
- rs = avg_gain / avg_loss
96
- df['RSI'] = 100 - (100 / (1 + rs))
97
-
98
- fig = go.Figure()
99
- fig.add_trace(go.Scatter(x=df['Date'], y=df['RSI'], mode='lines', name='RSI'))
100
- fig.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought")
101
- fig.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold")
102
- fig.update_layout(title="RSI Indicator", xaxis_title="Date", yaxis_title="RSI")
103
- st.plotly_chart(fig)
104
-
105
- # Volume Analysis
106
- st.subheader("Volume Analysis")
107
- fig = go.Figure()
108
- fig.add_trace(go.Bar(x=df['Date'], y=df['Volume'][ticker_symbol], name='Volume'))
109
- fig.update_layout(title="Volume Analysis", xaxis_title="Date", yaxis_title="Volume")
110
- st.plotly_chart(fig)
111
-
112
- # Additional Insights
113
- st.header("In-depth Analysis")
114
- # Prepare text for PaLM
115
- chatTextStr = f"""
116
- Analyze the following stock data for patterns, trends, and insights.
117
- Provide a detailed summary of key market movements.
118
- """
119
-
120
- answer = llm.predict(f'''
121
- I have yfinance data below on {ticker_symbol} symbol:
122
-
123
- {str(df[['Date', 'Open', 'High', 'Low', 'Close']].tail(30))}
124
-
125
- {chatTextStr}
126
- ''')
127
- st.write(answer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import numpy as np
6
  from plotly.subplots import make_subplots
7
  import os
8
+ from datetime import date, timedelta
9
 
10
  from langchain_openai import ChatOpenAI
11
 
12
 
13
+ isPswdValid = False # Set to True to temporarily disable password checking. For production set to False.
14
+ OPEN_ROUTER_KEY = st.secrets["OPEN_ROUTER_KEY"] # st.secrets["OPEN_ROUTER_KEY"]
15
+ OPEN_ROUTER_MODEL = "meta-llama/llama-3.3-70b-instruct:free"
16
 
17
  try:
18
+ pswdVal = st.query_params()['pwd'][0]
19
  if pswdVal==st.secrets["PSWD"]:
20
  isPswdValid = True
21
  except:
 
28
  llm = ChatOpenAI(model=OPEN_ROUTER_MODEL, temperature=0.1, openai_api_key=OPEN_ROUTER_KEY, openai_api_base="https://openrouter.ai/api/v1")
29
 
30
  # Set the Streamlit app title and icon
31
+ st.set_page_config(page_title="Stock Analysis", page_icon="📈", layout="wide", initial_sidebar_state="expanded")
32
+
33
+ # Global styling for a cleaner, modern look
34
+ st.markdown(
35
+ """
36
+ <style>
37
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
38
+ :root {
39
+ --bg: #0b1220;
40
+ --card: rgba(255,255,255,0.03);
41
+ --border: rgba(255,255,255,0.08);
42
+ --text: #e8edf7;
43
+ --muted: #a5b4d4;
44
+ --accent: #6dd6ff;
45
+ --accent-2: #7cf0c6;
46
+ }
47
+ .stApp {
48
+ background:
49
+ radial-gradient(circle at 10% 20%, rgba(80, 160, 255, 0.18), transparent 25%),
50
+ radial-gradient(circle at 85% 10%, rgba(90, 223, 197, 0.15), transparent 22%),
51
+ radial-gradient(circle at 50% 90%, rgba(255, 255, 255, 0.05), transparent 30%),
52
+ var(--bg);
53
+ color: var(--text);
54
+ font-family: 'Space Grotesk', sans-serif;
55
+ }
56
+ div.block-container {
57
+ padding-top: 2rem;
58
+ padding-bottom: 2rem;
59
+ max-width: 1200px;
60
+ }
61
+ div[data-testid="stSidebar"] {
62
+ background: #0f172a;
63
+ border-right: 1px solid rgba(255,255,255,0.05);
64
+ }
65
+ .hero-card {
66
+ background: linear-gradient(135deg, rgba(71, 120, 210, 0.75), rgba(17, 39, 83, 0.9));
67
+ border: 1px solid rgba(255,255,255,0.06);
68
+ border-radius: 16px;
69
+ padding: 18px 20px;
70
+ box-shadow: 0 10px 40px rgba(0,0,0,0.35);
71
+ color: var(--text);
72
+ margin-bottom: 1rem;
73
+ }
74
+ .hero-card h1 { margin-bottom: 0.35rem; font-size: 1.8rem; }
75
+ .hero-pill {
76
+ display: inline-block;
77
+ background: rgba(255,255,255,0.12);
78
+ padding: 6px 12px;
79
+ border-radius: 999px;
80
+ font-size: 0.85rem;
81
+ letter-spacing: .05em;
82
+ text-transform: uppercase;
83
+ }
84
+ .subdued { color: var(--muted); font-size: 0.95rem; }
85
+ .metric-card {
86
+ background: var(--card);
87
+ border: 1px solid var(--border);
88
+ padding: 14px 16px;
89
+ border-radius: 12px;
90
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
91
+ }
92
+ .metric-card h3 {
93
+ margin: 0;
94
+ font-size: .95rem;
95
+ color: var(--muted);
96
+ text-transform: uppercase;
97
+ letter-spacing: .08em;
98
+ }
99
+ .metric-value { font-size: 1.4rem; font-weight: 700; color: var(--text); margin-top: 6px; }
100
+ .delta { color: #7cf0c6; font-weight: 600; font-size: 0.95rem; }
101
+ .delta.negative { color: #ff9b9b; }
102
+ .section-caption { color: var(--muted); margin-top: -6px; margin-bottom: 10px; }
103
+ </style>
104
+ """,
105
+ unsafe_allow_html=True,
106
+ )
107
 
108
  # Create a Streamlit sidebar for user input
109
  st.sidebar.title("Stock Analysis")
110
  ticker_symbol = st.sidebar.text_input("Enter Stock Ticker Symbol:", value='AAPL')
111
+ default_end = date.today() - timedelta(days=1)
112
+ default_start = default_end - timedelta(days=365 * 3)
113
+ start_date = st.sidebar.date_input("Start Date", default_start)
114
+ end_date = st.sidebar.date_input("End Date", default_end)
115
+ st.sidebar.markdown("---")
116
+ st.sidebar.caption("Tip: Choose a wide date range for smoother moving averages and richer AI insights.")
117
 
118
  # Fetch stock data from Yahoo Finance
119
  try:
 
122
  st.error("Error fetching stock data. Please check the ticker symbol and date range.")
123
  df = stock_data
124
  df.reset_index(inplace=True) # Reset index to ensure 'Date' becomes a column
125
+
126
+ close_series = df['Close'][ticker_symbol]
127
+ high_series = df['High'][ticker_symbol]
128
+ low_series = df['Low'][ticker_symbol]
129
+ volume_series = df['Volume'][ticker_symbol]
130
+
131
+ latest_close = close_series.iloc[-1] if not close_series.empty else None
132
+ prev_close = close_series.iloc[-2] if len(close_series) > 1 else None
133
+ change = (latest_close - prev_close) if latest_close is not None and prev_close is not None else None
134
+ change_pct = (change / prev_close * 100) if change not in [None, 0] and prev_close not in [None, 0] else None
135
+ period_high = high_series.max() if not high_series.empty else None
136
+ period_low = low_series.min() if not low_series.empty else None
137
+ avg_volume = volume_series.mean() if not volume_series.empty else None
138
+
139
+ def fmt_currency(val):
140
+ return "-" if val is None or pd.isna(val) else f"${val:,.2f}"
141
+
142
+ def fmt_delta(delta_val, pct_val):
143
+ if delta_val is None or pd.isna(delta_val):
144
+ return "—", ""
145
+ symbol = "negative" if delta_val < 0 else ""
146
+ pct_text = f" ({pct_val:+.2f}%)" if pct_val not in [None, np.nan] else ""
147
+ return f"{delta_val:+.2f}{pct_text}", symbol
148
+
149
+ delta_text, delta_class = fmt_delta(change, change_pct)
150
+
151
+ st.markdown(
152
+ f"""
153
+ <div class="hero-card">
154
+ <div class="hero-pill">Market Pulse</div>
155
+ <h1>{ticker_symbol.upper()} | Stock Intelligence</h1>
156
+ <p class="subdued">Sharper visuals for price action, technicals, and AI commentary across your chosen dates.</p>
157
+ <div class="subdued">Range: {start_date.strftime('%b %d, %Y')} → {end_date.strftime('%b %d, %Y')}</div>
158
+ </div>
159
+ """,
160
+ unsafe_allow_html=True,
161
+ )
162
+
163
+ mc1, mc2, mc3 = st.columns(3)
164
+ mc1.markdown(
165
+ f"""
166
+ <div class="metric-card">
167
+ <h3>Last Close</h3>
168
+ <div class="metric-value">{fmt_currency(latest_close)}</div>
169
+ <div class="delta {delta_class}">{delta_text}</div>
170
+ </div>
171
+ """,
172
+ unsafe_allow_html=True,
173
+ )
174
+ mc2.markdown(
175
+ f"""
176
+ <div class="metric-card">
177
+ <h3>Period Range</h3>
178
+ <div class="metric-value">{fmt_currency(period_low)} – {fmt_currency(period_high)}</div>
179
+ <div class="delta">Session High / Low</div>
180
+ </div>
181
+ """,
182
+ unsafe_allow_html=True,
183
+ )
184
+ mc3.markdown(
185
+ f"""
186
+ <div class="metric-card">
187
+ <h3>Avg Volume</h3>
188
+ <div class="metric-value">{'-' if avg_volume is None or pd.isna(avg_volume) else f"{avg_volume:,.0f}"}</div>
189
+ <div class="delta">Across selected window</div>
190
+ </div>
191
+ """,
192
+ unsafe_allow_html=True,
193
+ )
194
+
195
+ price_tab, indicators_tab, ai_tab = st.tabs(["Price Action", "Technical Indicators", "AI Deep Dive"])
196
+
197
+ with price_tab:
198
+ st.subheader("Stock Price Chart")
199
+ st.caption("Candlestick price action with volume on a shared timeline for quick at-a-glance context.")
200
+
201
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
202
+ fig.add_trace(
203
+ go.Candlestick(
204
+ x=df['Date'],
205
+ open=df['Open'][ticker_symbol],
206
+ high=df['High'][ticker_symbol],
207
+ low=df['Low'][ticker_symbol],
208
+ close=close_series,
209
+ name="Price",
210
+ ),
211
+ secondary_y=True,
212
+ )
213
+ fig.add_trace(
214
+ go.Bar(
215
+ x=df['Date'],
216
+ y=volume_series,
217
+ name="Volume",
218
+ marker_color="rgba(109, 214, 255, 0.55)",
219
+ ),
220
+ secondary_y=False,
221
+ )
222
+ fig.update_layout(
223
+ template="plotly_dark",
224
+ plot_bgcolor="rgba(12,19,32,0.7)",
225
+ paper_bgcolor="rgba(12,19,32,0.7)",
226
+ legend_orientation="h",
227
+ legend_yanchor="bottom",
228
+ legend_y=1.02,
229
+ legend_x=0,
230
+ margin=dict(t=50, l=10, r=10, b=20),
231
+ )
232
+ fig.update_yaxes(showgrid=False, secondary_y=True)
233
+ fig.update_yaxes(gridcolor="rgba(255,255,255,0.08)", secondary_y=False, title_text="Volume")
234
+ fig.update_xaxes(showgrid=False)
235
+ st.plotly_chart(fig, use_container_width=True)
236
+
237
+ with indicators_tab:
238
+ st.subheader("Moving Averages")
239
+ st.caption("Compare recent closes against short and intermediate trend lines.")
240
+ df['SMA_20'] = close_series.rolling(window=20).mean()
241
+ df['SMA_50'] = close_series.rolling(window=50).mean()
242
+ fig = go.Figure()
243
+ fig.add_trace(go.Scatter(x=df['Date'], y=close_series, mode='lines', name='Close Price', line=dict(color="#7cf0c6")))
244
+ fig.add_trace(go.Scatter(x=df['Date'], y=df['SMA_20'], mode='lines', name='20-Day SMA', line=dict(color="#6dd6ff")))
245
+ fig.add_trace(go.Scatter(x=df['Date'], y=df['SMA_50'], mode='lines', name='50-Day SMA', line=dict(color="#b0b8ff")))
246
+ fig.update_layout(
247
+ title="Moving Averages",
248
+ xaxis_title="Date",
249
+ yaxis_title="Price (USD)",
250
+ template="plotly_dark",
251
+ plot_bgcolor="rgba(12,19,32,0.7)",
252
+ paper_bgcolor="rgba(12,19,32,0.7)",
253
+ margin=dict(t=50, l=10, r=10, b=20),
254
+ )
255
+ fig.update_xaxes(showgrid=False)
256
+ fig.update_yaxes(gridcolor="rgba(255,255,255,0.08)")
257
+ st.plotly_chart(fig, use_container_width=True)
258
+
259
+ st.subheader("Relative Strength Index (RSI)")
260
+ st.caption("Momentum oscillator highlighting overbought/oversold zones.")
261
+ window_length = 14
262
+
263
+ delta = close_series.diff()
264
+ gain = delta.where(delta > 0, 0)
265
+ loss = -delta.where(delta < 0, 0)
266
+
267
+ avg_gain = gain.rolling(window=window_length, min_periods=1).mean()
268
+ avg_loss = loss.rolling(window=window_length, min_periods=1).mean()
269
+
270
+ rs = avg_gain / avg_loss
271
+ df['RSI'] = 100 - (100 / (1 + rs))
272
+
273
+ fig = go.Figure()
274
+ fig.add_trace(go.Scatter(x=df['Date'], y=df['RSI'], mode='lines', name='RSI', line=dict(color="#6dd6ff")))
275
+ fig.add_hline(y=70, line_dash="dash", line_color="#ff9b9b", annotation_text="Overbought")
276
+ fig.add_hline(y=30, line_dash="dash", line_color="#7cf0c6", annotation_text="Oversold")
277
+ fig.update_layout(
278
+ title="RSI Indicator",
279
+ xaxis_title="Date",
280
+ yaxis_title="RSI",
281
+ template="plotly_dark",
282
+ plot_bgcolor="rgba(12,19,32,0.7)",
283
+ paper_bgcolor="rgba(12,19,32,0.7)",
284
+ margin=dict(t=50, l=10, r=10, b=20),
285
+ )
286
+ fig.update_xaxes(showgrid=False)
287
+ fig.update_yaxes(gridcolor="rgba(255,255,255,0.08)")
288
+ st.plotly_chart(fig, use_container_width=True)
289
+
290
+ st.subheader("Volume Analysis")
291
+ st.caption("Volume bars styled to match the rest of the dashboard.")
292
+ fig = go.Figure()
293
+ fig.add_trace(go.Bar(x=df['Date'], y=volume_series, name='Volume', marker_color="rgba(109, 214, 255, 0.55)"))
294
+ fig.update_layout(
295
+ title="Volume Analysis",
296
+ xaxis_title="Date",
297
+ yaxis_title="Volume",
298
+ template="plotly_dark",
299
+ plot_bgcolor="rgba(12,19,32,0.7)",
300
+ paper_bgcolor="rgba(12,19,32,0.7)",
301
+ margin=dict(t=50, l=10, r=10, b=20),
302
+ )
303
+ fig.update_xaxes(showgrid=False)
304
+ fig.update_yaxes(gridcolor="rgba(255,255,255,0.08)")
305
+ st.plotly_chart(fig, use_container_width=True)
306
+
307
+ with ai_tab:
308
+ st.subheader("In-depth Analysis")
309
+ st.caption("AI-generated commentary stays on a dedicated tab so charts remain uncluttered.")
310
+ chatTextStr = f"""
311
+ Analyze the following stock market data to identify notable patterns, trends, and anomalies.
312
+ Summarize key price movements, volume behavior, and any significant shifts in market sentiment.
313
+ Provide insights in clear, plain language and do not include any programming code.
314
+ """
315
+
316
+ with st.spinner("Running in-depth AI analysis..."):
317
+ try:
318
+ answer = llm.predict(f'''
319
+ I have yfinance data below on {ticker_symbol} symbol:
320
+
321
+ {str(df[['Date', 'Open', 'High', 'Low', 'Close']].tail(30))}
322
+
323
+ {chatTextStr}
324
+ ''')
325
+ st.write(answer)
326
+ except Exception as exc:
327
+ st.error(f"AI analysis failed: {exc}")