bryt666 commited on
Commit
36ad6e7
·
verified ·
1 Parent(s): 3b8919d

Upload 8 files

Browse files
Files changed (8) hide show
  1. app.py +365 -0
  2. index_etfs.txt +1 -0
  3. my_favorites.txt +1 -0
  4. nq100.txt +1 -0
  5. requirements.txt +2 -0
  6. start.bat +8 -0
  7. tech_stocks.txt +1 -0
  8. watchlists_config.json +6 -0
app.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ import requests
6
+ import base64
7
+ from io import BytesIO
8
+
9
+ # Load watchlist configuration
10
+ def load_watchlists():
11
+ """Load watchlist configuration from JSON file"""
12
+ config_path = Path("watchlists_config.json")
13
+ if config_path.exists():
14
+ with open(config_path, 'r') as f:
15
+ return json.load(f)
16
+ return {}
17
+
18
+ def load_stocks_from_file(filename):
19
+ """Load stock symbols from a watchlist file"""
20
+ file_path = Path(filename)
21
+ if file_path.exists():
22
+ with open(file_path, 'r') as f:
23
+ content = f.read().strip()
24
+ # Split by comma and clean whitespace
25
+ stocks = [s.strip() for s in content.split(',') if s.strip()]
26
+ return stocks
27
+ return []
28
+
29
+ def generate_chart_url(symbol, timeframe):
30
+ """Generate StockCharts URL based on symbol and timeframe"""
31
+ base_url = "https://stockcharts.com/c-sc/sc?chart="
32
+
33
+ # Random parameter for cache busting
34
+ import random
35
+ rand_param = random.random()
36
+
37
+ if timeframe == "5-Minute":
38
+ return f"{base_url}{symbol},uu[700,500]iahayaca[bb][i!ub14!la12,26,9][pd20,2]&r={rand_param}"
39
+ elif timeframe == "Daily":
40
+ return f"{base_url}{symbol},uu[700,500]dahayaca[bb][i!ub14!lu][pd20,2]&r={rand_param}"
41
+ elif timeframe == "Weekly":
42
+ return f"{base_url}{symbol},uu[700,500]whhayaca[bb][i!ub14!lu][pd20,2]&r={rand_param}"
43
+ return ""
44
+
45
+ def download_chart_image(url):
46
+ """Download chart image from StockCharts and convert to base64"""
47
+ try:
48
+ headers = {
49
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
50
+ 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
51
+ 'Accept-Language': 'en-US,en;q=0.9',
52
+ 'Accept-Encoding': 'gzip, deflate, br',
53
+ 'Referer': 'https://stockcharts.com/',
54
+ 'Connection': 'keep-alive',
55
+ 'Sec-Fetch-Dest': 'image',
56
+ 'Sec-Fetch-Mode': 'no-cors',
57
+ 'Sec-Fetch-Site': 'same-origin'
58
+ }
59
+
60
+ response = requests.get(url, headers=headers, timeout=10)
61
+ response.raise_for_status()
62
+
63
+ # Convert to base64
64
+ img_base64 = base64.b64encode(response.content).decode('utf-8')
65
+ return f"data:image/png;base64,{img_base64}"
66
+ except Exception as e:
67
+ print(f"Error downloading image from {url}: {str(e)}")
68
+ # Return a placeholder image URL or error message
69
+ return None
70
+
71
+ def generate_single_stock_images(symbol):
72
+ """Generate HTML for single stock analysis (3 charts)"""
73
+ timeframes = ["5-Minute", "Daily", "Weekly"]
74
+
75
+ html = f"<h2>股票分析: {symbol}</h2>"
76
+ html += '<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 20px;">'
77
+
78
+ for tf in timeframes:
79
+ url = generate_chart_url(symbol, tf)
80
+ img_data = download_chart_image(url)
81
+
82
+ if img_data:
83
+ html += f'''
84
+ <div style="text-align: center;">
85
+ <h3>{tf}</h3>
86
+ <img src="{img_data}" alt="{symbol} - {tf}" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
87
+ </div>
88
+ '''
89
+ else:
90
+ html += f'''
91
+ <div style="text-align: center;">
92
+ <h3>{tf}</h3>
93
+ <p style="color: red;">加载图片失败</p>
94
+ </div>
95
+ '''
96
+
97
+ html += '</div>'
98
+ return html
99
+
100
+ def update_single_stock(symbol):
101
+ """Update single stock tab with charts"""
102
+ if not symbol or not symbol.strip():
103
+ return "<p>请输入股票代码</p>"
104
+
105
+ symbol = symbol.strip().upper()
106
+ return generate_single_stock_images(symbol)
107
+
108
+ def generate_watchlist_images(watchlist_name, timeframe):
109
+ """Generate HTML for watchlist with selected timeframe"""
110
+ if not watchlist_name or watchlist_name == "Select a watchlist":
111
+ return "<p>请选择一个观察列表</p>"
112
+
113
+ # Load watchlist config
114
+ config = load_watchlists()
115
+ if watchlist_name not in config:
116
+ return f"<p>观察列表 '{watchlist_name}' 未找到</p>"
117
+
118
+ # Load stocks from file
119
+ filename = config[watchlist_name]
120
+ stocks = load_stocks_from_file(filename)
121
+
122
+ if not stocks:
123
+ return f"<p>文件 {filename} 中没有找到股票</p>"
124
+
125
+ # Generate HTML
126
+ html = f"<h2>观察列表: {watchlist_name} ({timeframe})</h2>"
127
+ html += '<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 20px;">'
128
+
129
+ for symbol in stocks:
130
+ url = generate_chart_url(symbol, timeframe)
131
+ img_data = download_chart_image(url)
132
+
133
+ if img_data:
134
+ html += f'''
135
+ <div style="text-align: center;">
136
+ <h3>{symbol}</h3>
137
+ <img src="{img_data}" alt="{symbol} - {timeframe}" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
138
+ </div>
139
+ '''
140
+ else:
141
+ html += f'''
142
+ <div style="text-align: center;">
143
+ <h3>{symbol}</h3>
144
+ <p style="color: red;">加载图片失败</p>
145
+ </div>
146
+ '''
147
+
148
+ html += '</div>'
149
+ return html
150
+
151
+ def get_watchlist_names():
152
+ """Get list of watchlist names from config"""
153
+ config = load_watchlists()
154
+ return ["Select a watchlist"] + list(config.keys())
155
+
156
+ def save_watchlist_config(config):
157
+ """Save watchlist configuration to JSON file"""
158
+ config_path = Path("watchlists_config.json")
159
+ with open(config_path, 'w') as f:
160
+ json.dump(config, f, indent=2)
161
+
162
+ def save_stocks_to_file(filename, stocks_text):
163
+ """Save stock symbols to a watchlist file"""
164
+ file_path = Path(filename)
165
+ with open(file_path, 'w') as f:
166
+ f.write(stocks_text.strip())
167
+
168
+ def add_or_update_watchlist(watchlist_name, stocks_text):
169
+ """Add a new watchlist or update an existing one"""
170
+ if not watchlist_name or not watchlist_name.strip():
171
+ return "❌ 请输入观察列表名称", gr.update(), gr.update()
172
+
173
+ watchlist_name = watchlist_name.strip()
174
+
175
+ if not stocks_text or not stocks_text.strip():
176
+ return "❌ 请输入股票代码", gr.update(), gr.update()
177
+
178
+ # Generate filename from watchlist name
179
+ filename = watchlist_name.lower().replace(' ', '_') + '.txt'
180
+
181
+ # Save stocks to file
182
+ save_stocks_to_file(filename, stocks_text)
183
+
184
+ # Update config
185
+ config = load_watchlists()
186
+ config[watchlist_name] = filename
187
+ save_watchlist_config(config)
188
+
189
+ # Get updated list for dropdown
190
+ updated_choices = get_watchlist_names()
191
+
192
+ return (
193
+ f"✅ 观察列表 '{watchlist_name}' 已保存!",
194
+ gr.update(choices=updated_choices),
195
+ gr.update(choices=list(config.keys()), value=None)
196
+ )
197
+
198
+ def delete_watchlist(watchlist_name):
199
+ """Delete a watchlist"""
200
+ if not watchlist_name:
201
+ return "❌ 请选择要删除的观察列表", gr.update(), gr.update()
202
+
203
+ config = load_watchlists()
204
+
205
+ if watchlist_name not in config:
206
+ return f"❌ 观察列表 '{watchlist_name}' 不存在", gr.update(), gr.update()
207
+
208
+ # Get filename and delete file
209
+ filename = config[watchlist_name]
210
+ file_path = Path(filename)
211
+ if file_path.exists():
212
+ file_path.unlink()
213
+
214
+ # Remove from config
215
+ del config[watchlist_name]
216
+ save_watchlist_config(config)
217
+
218
+ # Get updated list for dropdown
219
+ updated_choices = get_watchlist_names()
220
+
221
+ return (
222
+ f"✅ 观察列表 '{watchlist_name}' 已删除!",
223
+ gr.update(choices=updated_choices),
224
+ gr.update(choices=list(config.keys()) if config else [], value=None)
225
+ )
226
+
227
+ def load_watchlist_for_edit(watchlist_name):
228
+ """Load watchlist content for editing"""
229
+ if not watchlist_name:
230
+ return "", ""
231
+
232
+ config = load_watchlists()
233
+ if watchlist_name not in config:
234
+ return "", ""
235
+
236
+ filename = config[watchlist_name]
237
+ stocks = load_stocks_from_file(filename)
238
+ stocks_text = ", ".join(stocks)
239
+
240
+ return watchlist_name, stocks_text
241
+
242
+ # Create Gradio interface
243
+ with gr.Blocks(title="股票分析仪表板") as demo:
244
+ gr.Markdown("# 股票分析仪表板")
245
+
246
+ with gr.Tabs():
247
+ # Tab 1: Single Stock Analysis
248
+ with gr.Tab("单股分析"):
249
+ gr.Markdown("### 输入股票代码查看图表")
250
+
251
+ with gr.Row():
252
+ stock_input = gr.Textbox(
253
+ label="股票代码",
254
+ placeholder="例如: QQQ, AAPL, SPY",
255
+ scale=3
256
+ )
257
+ analyze_btn = gr.Button("分析", scale=1)
258
+
259
+ single_stock_output = gr.HTML()
260
+
261
+ # Event handlers
262
+ analyze_btn.click(
263
+ fn=update_single_stock,
264
+ inputs=[stock_input],
265
+ outputs=[single_stock_output]
266
+ )
267
+
268
+ stock_input.submit(
269
+ fn=update_single_stock,
270
+ inputs=[stock_input],
271
+ outputs=[single_stock_output]
272
+ )
273
+
274
+ # Tab 2: Watchlist
275
+ with gr.Tab("观察列表"):
276
+ gr.Markdown("### 选择观察列表和时间周期查看所有图表")
277
+
278
+ with gr.Row():
279
+ watchlist_dropdown = gr.Dropdown(
280
+ choices=get_watchlist_names(),
281
+ value="Select a watchlist",
282
+ label="��择观察列表",
283
+ scale=2
284
+ )
285
+
286
+ timeframe_dropdown = gr.Dropdown(
287
+ choices=["5-Minute", "Daily", "Weekly"],
288
+ value="Daily",
289
+ label="选择时间周期",
290
+ scale=1
291
+ )
292
+
293
+ watchlist_output = gr.HTML()
294
+
295
+ # Event handlers - update on any dropdown change
296
+ watchlist_dropdown.change(
297
+ fn=generate_watchlist_images,
298
+ inputs=[watchlist_dropdown, timeframe_dropdown],
299
+ outputs=[watchlist_output]
300
+ )
301
+
302
+ timeframe_dropdown.change(
303
+ fn=generate_watchlist_images,
304
+ inputs=[watchlist_dropdown, timeframe_dropdown],
305
+ outputs=[watchlist_output]
306
+ )
307
+
308
+ # Tab 3: Manage Watchlists
309
+ with gr.Tab("管理观察列表"):
310
+ gr.Markdown("### 添加、修改或删除观察列表")
311
+
312
+ with gr.Row():
313
+ with gr.Column():
314
+ gr.Markdown("#### 添加/修改观察列表")
315
+
316
+ manage_name_input = gr.Textbox(
317
+ label="观察列表名称",
318
+ placeholder="例如: Tech Stocks"
319
+ )
320
+
321
+ manage_stocks_input = gr.TextArea(
322
+ label="股票代码(逗号分隔)",
323
+ placeholder="例如: AAPL, MSFT, GOOGL, NVDA",
324
+ lines=5
325
+ )
326
+
327
+ with gr.Row():
328
+ save_btn = gr.Button("💾 保存", variant="primary")
329
+
330
+ save_status = gr.Markdown()
331
+
332
+ with gr.Column():
333
+ gr.Markdown("#### 编辑/删除现有列表")
334
+
335
+ edit_dropdown = gr.Dropdown(
336
+ choices=list(load_watchlists().keys()),
337
+ label="选择要编辑的观察列表",
338
+ value=None
339
+ )
340
+
341
+ with gr.Row():
342
+ load_btn = gr.Button("📝 加载到左侧编辑")
343
+ delete_btn = gr.Button("🗑️ 删除", variant="stop")
344
+
345
+ # Event handlers for management tab
346
+ save_btn.click(
347
+ fn=add_or_update_watchlist,
348
+ inputs=[manage_name_input, manage_stocks_input],
349
+ outputs=[save_status, watchlist_dropdown, edit_dropdown]
350
+ )
351
+
352
+ delete_btn.click(
353
+ fn=delete_watchlist,
354
+ inputs=[edit_dropdown],
355
+ outputs=[save_status, watchlist_dropdown, edit_dropdown]
356
+ )
357
+
358
+ load_btn.click(
359
+ fn=load_watchlist_for_edit,
360
+ inputs=[edit_dropdown],
361
+ outputs=[manage_name_input, manage_stocks_input]
362
+ )
363
+
364
+ if __name__ == "__main__":
365
+ demo.launch()
index_etfs.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ QQQ, SPY, DIA, IWM, VTI, VOO,SOXX,VXX
my_favorites.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ QQQ, AAPL, TSLA, SPY
nq100.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ QQQ,SPY,IWM,DIA,SOXX,AAPL, MSFT, NVDA, AVGO, AMZN, META, TSLA, GOOGL, GOOG, COST, NFLX, AMD, ADBE, PEP, LIN, QCOM, TMUS, CSCO, TXN, AMAT, INTU, AMGN, CMCSA, INTC, ISRG, HON, BKNG, MU, VRTX, LRCX, ADI, REGN, KLAC, PANW, ADP, GILD, MDLZ, PDD, SNPS, ASML, SBUX, MELI, CRWD, CTAS, CDNS, NXPI, MAR, CSX, ABNB, PYPL, ROP, ORLY, MRVL, CEG, PCAR, MNST, ADSK, CPRT, AEP, MCHP, WDAY, ROST, MRNA, CHTR, AZN, KDP, PAYX, FTNT, DXCM, TTD, ODFL, KHC, VRSK, FAST, EA, IDXX, DASH, CTSH, FANG, GEHC, DDOG, EXC, BKR, LULU, CCEP, BIIB, ON, GFS, CDW, XEL, CSGP, ZS, TEAM, ANSS, TTWO, DLTR, WBD, ARM, ILMN, MDB
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio
2
+ requests
start.bat ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo Installing dependencies...
3
+ pip install -r requirements.txt
4
+
5
+ echo.
6
+ echo Starting Stock Analysis Dashboard...
7
+ python app.py
8
+ pause
tech_stocks.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ AAPL, MSFT, GOOGL, NVDA, TSLA, META
watchlists_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "Tech Stocks": "tech_stocks.txt",
3
+ "Index ETFs": "index_etfs.txt",
4
+ "My Favorites": "my_favorites.txt",
5
+ "nq100": "nq100.txt"
6
+ }