54justin commited on
Commit
8451e8f
·
verified ·
1 Parent(s): 247bab4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -432
app.py CHANGED
@@ -124,241 +124,6 @@ class StockAnalyzer:
124
  'symbol': symbol
125
  }
126
 
127
- def get_fundamental_data(self, symbol):
128
- """獲取基本面數據"""
129
- try:
130
- ticker = yf.Ticker(symbol)
131
- info = ticker.info
132
-
133
- # 獲取財務數據
134
- fundamental_data = {
135
- 'pe_ratio': info.get('trailingPE', None), # 本益比
136
- 'peg_ratio': info.get('pegRatio', None), # PEG比率
137
- 'pb_ratio': info.get('priceToBook', None), # 股價淨值比
138
- 'dividend_yield': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 0, # 股息殖利率
139
- 'roe': info.get('returnOnEquity', None), # 股東權益報酬率
140
- 'debt_to_equity': info.get('debtToEquity', None), # 負債權益比
141
- 'current_ratio': info.get('currentRatio', None), # 流動比率
142
- 'gross_margins': info.get('grossMargins', None), # 毛利率
143
- 'operating_margins': info.get('operatingMargins', None), # 營業利益率
144
- 'profit_margins': info.get('profitMargins', None), # 淨利率
145
- 'revenue_growth': info.get('revenueGrowth', None), # 營收成長率
146
- 'earnings_growth': info.get('earningsGrowth', None), # 盈餘成長率
147
- 'book_value': info.get('bookValue', None), # 每股淨值
148
- 'eps': info.get('trailingEps', None), # 每股盈餘
149
- 'forward_eps': info.get('forwardEps', None), # 預估每股盈餘
150
- 'market_cap': info.get('marketCap', None), # 市值
151
- 'enterprise_value': info.get('enterpriseValue', None), # 企業價值
152
- 'price_to_sales': info.get('priceToSalesTrailing12Months', None), # 股價營收比
153
- 'beta': info.get('beta', None), # 貝塔值
154
- }
155
-
156
- return fundamental_data
157
-
158
- except Exception as e:
159
- print(f"基本面數據獲取失敗 {symbol}: {str(e)}")
160
- return {}
161
-
162
- def calculate_fair_value(self, symbol, fundamental_data, current_price):
163
- """計算合理價位"""
164
- try:
165
- fair_values = []
166
- methods_used = []
167
-
168
- # 方法1: 本益比法 (PE Method)
169
- if fundamental_data.get('eps') and fundamental_data.get('pe_ratio'):
170
- industry_avg_pe = 15 # 假設產業平均本益比
171
- pe_fair_value = fundamental_data['eps'] * industry_avg_pe
172
- fair_values.append(pe_fair_value)
173
- methods_used.append('本益比法')
174
-
175
- # 方法2: 淨值比法 (PB Method)
176
- if fundamental_data.get('book_value') and fundamental_data.get('pb_ratio'):
177
- industry_avg_pb = 1.5 # 假設產業平均淨值比
178
- pb_fair_value = fundamental_data['book_value'] * industry_avg_pb
179
- fair_values.append(pb_fair_value)
180
- methods_used.append('淨值比法')
181
-
182
- # 方法3: 股息折現模型 (DDM)
183
- if fundamental_data.get('dividend_yield') and fundamental_data['dividend_yield'] > 0:
184
- required_return = 0.08 # 假設要求報酬率8%
185
- dividend_per_share = current_price * (fundamental_data['dividend_yield'] / 100)
186
- growth_rate = fundamental_data.get('earnings_growth', 0.03) # 假設3%成長率
187
- if required_return > growth_rate:
188
- ddm_fair_value = dividend_per_share * (1 + growth_rate) / (required_return - growth_rate)
189
- fair_values.append(ddm_fair_value)
190
- methods_used.append('股息折現法')
191
-
192
- # 方法4: 自由現金流折現 (簡化版)
193
- if fundamental_data.get('market_cap') and fundamental_data.get('profit_margins'):
194
- # 簡化的DCF估算
195
- estimated_fcf = fundamental_data['market_cap'] * (fundamental_data['profit_margins'] or 0.05)
196
- discount_rate = 0.1 # 10%折現率
197
- growth_rate = 0.03 # 3%永續成長率
198
- terminal_value = estimated_fcf * (1 + growth_rate) / (discount_rate - growth_rate)
199
- shares_outstanding = fundamental_data['market_cap'] / current_price if current_price else 1
200
- dcf_fair_value = terminal_value / shares_outstanding
201
- fair_values.append(dcf_fair_value)
202
- methods_used.append('現金流折現法')
203
-
204
- # 計算平均合理價位
205
- if fair_values:
206
- average_fair_value = sum(fair_values) / len(fair_values)
207
- return {
208
- 'fair_value': average_fair_value,
209
- 'methods_used': methods_used,
210
- 'individual_values': dict(zip(methods_used, fair_values)),
211
- 'upside_potential': ((average_fair_value - current_price) / current_price * 100) if current_price else 0
212
- }
213
- else:
214
- return {
215
- 'fair_value': None,
216
- 'methods_used': [],
217
- 'individual_values': {},
218
- 'upside_potential': 0
219
- }
220
-
221
- except Exception as e:
222
- print(f"合理價位計算失敗 {symbol}: {str(e)}")
223
- return {
224
- 'fair_value': None,
225
- 'methods_used': [],
226
- 'individual_values': {},
227
- 'upside_potential': 0
228
- }
229
-
230
- def generate_investment_recommendation(self, symbol, current_price, technical_probs, fair_value_data, fundamental_data):
231
- """生成投資建議"""
232
- try:
233
- recommendations = []
234
- overall_score = 0
235
- max_score = 100
236
-
237
- # 技術面評分 (30%)
238
- tech_score = 0
239
- if technical_probs['up'] > 50:
240
- tech_score = 25
241
- recommendations.append("✅ 技術面: 上漲機率較高,技術指標偏多")
242
- elif technical_probs['down'] > 50:
243
- tech_score = 5
244
- recommendations.append("❌ 技術面: 下跌機率較高,技術指標偏空")
245
- else:
246
- tech_score = 15
247
- recommendations.append("⚠️ 技術面: 盤整機率較高,缺乏明確方向")
248
-
249
- # 基本面評分 (40%)
250
- fundamental_score = 0
251
-
252
- # PE比評估
253
- pe_ratio = fundamental_data.get('pe_ratio')
254
- if pe_ratio:
255
- if pe_ratio < 15:
256
- fundamental_score += 8
257
- recommendations.append(f"✅ 本益比: {pe_ratio:.1f} (合理偏低)")
258
- elif pe_ratio > 25:
259
- fundamental_score += 2
260
- recommendations.append(f"⚠️ 本益比: {pe_ratio:.1f} (偏高)")
261
- else:
262
- fundamental_score += 5
263
- recommendations.append(f"✅ 本益比: {pe_ratio:.1f} (合理)")
264
-
265
- # ROE評估
266
- roe = fundamental_data.get('roe')
267
- if roe:
268
- if roe > 0.15:
269
- fundamental_score += 8
270
- recommendations.append(f"✅ ROE: {roe*100:.1f}% (優秀)")
271
- elif roe > 0.10:
272
- fundamental_score += 5
273
- recommendations.append(f"✅ ROE: {roe*100:.1f}% (良好)")
274
- else:
275
- fundamental_score += 2
276
- recommendations.append(f"⚠️ ROE: {roe*100:.1f}% (偏低)")
277
-
278
- # 負債比評估
279
- debt_ratio = fundamental_data.get('debt_to_equity')
280
- if debt_ratio is not None:
281
- if debt_ratio < 0.5:
282
- fundamental_score += 6
283
- recommendations.append(f"✅ 負債比: {debt_ratio:.2f} (健康)")
284
- elif debt_ratio < 1.0:
285
- fundamental_score += 3
286
- recommendations.append(f"⚠️ 負債比: {debt_ratio:.2f} (適中)")
287
- else:
288
- fundamental_score += 1
289
- recommendations.append(f"❌ 負債比: {debt_ratio:.2f} (偏高)")
290
-
291
- # 成長性評估
292
- revenue_growth = fundamental_data.get('revenue_growth')
293
- if revenue_growth:
294
- if revenue_growth > 0.1:
295
- fundamental_score += 8
296
- recommendations.append(f"✅ 營收成長: {revenue_growth*100:.1f}% (強勁)")
297
- elif revenue_growth > 0:
298
- fundamental_score += 5
299
- recommendations.append(f"✅ 營收成長: {revenue_growth*100:.1f}% (正成長)")
300
- else:
301
- fundamental_score += 1
302
- recommendations.append(f"❌ 營收成長: {revenue_growth*100:.1f}% (負成長)")
303
-
304
- # 合理價位評估 (30%)
305
- valuation_score = 0
306
- if fair_value_data['fair_value'] and current_price:
307
- upside = fair_value_data['upside_potential']
308
- if upside > 20:
309
- valuation_score = 25
310
- recommendations.append(f"✅ 估值: 現價相對合理價位有{upside:.1f}%上漲空間")
311
- elif upside > 0:
312
- valuation_score = 20
313
- recommendations.append(f"✅ 估值: 現價相對合理價位有{upside:.1f}%上漲空間")
314
- elif upside > -10:
315
- valuation_score = 15
316
- recommendations.append(f"⚠️ 估值: 現價接近合理價位 ({upside:+.1f}%)")
317
- else:
318
- valuation_score = 5
319
- recommendations.append(f"❌ 估值: 現價高於合理價位{abs(upside):.1f}%")
320
-
321
- overall_score = tech_score + fundamental_score + valuation_score
322
-
323
- # 生成最終建議
324
- if overall_score >= 70:
325
- final_recommendation = "🚀 強力買入"
326
- action_color = "green"
327
- elif overall_score >= 55:
328
- final_recommendation = "✅ 買入"
329
- action_color = "lightgreen"
330
- elif overall_score >= 40:
331
- final_recommendation = "⚠️ 觀望"
332
- action_color = "orange"
333
- elif overall_score >= 25:
334
- final_recommendation = "❌ 避開"
335
- action_color = "lightcoral"
336
- else:
337
- final_recommendation = "🔻 賣出"
338
- action_color = "red"
339
-
340
- return {
341
- 'overall_score': overall_score,
342
- 'max_score': max_score,
343
- 'final_recommendation': final_recommendation,
344
- 'action_color': action_color,
345
- 'detailed_analysis': recommendations,
346
- 'fair_value': fair_value_data['fair_value'],
347
- 'upside_potential': fair_value_data['upside_potential']
348
- }
349
-
350
- except Exception as e:
351
- print(f"投資建議生成失敗 {symbol}: {str(e)}")
352
- return {
353
- 'overall_score': 50,
354
- 'max_score': 100,
355
- 'final_recommendation': "⚠️ 資料不足",
356
- 'action_color': "gray",
357
- 'detailed_analysis': ["資料不足,無法提供完整分析"],
358
- 'fair_value': None,
359
- 'upside_potential': 0
360
- }
361
-
362
  def calculate_technical_indicators(self):
363
  """計算技術指標"""
364
  if self.data is None:
@@ -578,27 +343,20 @@ class StockAnalyzer:
578
 
579
  report = f"""
580
  ## 📊 {self.symbol} AI 分析報告
581
-
582
  ### 📈 技術面分析:
583
  {chr(10).join(f"• {signal}" for signal in technical_signals)}
584
-
585
  ### 💭 市場情感:{sentiment}
586
-
587
  ### 📊 近期表現:
588
  - 5日漲跌幅:{price_change:+.2f}%
589
  - 當前價位:${recent_data['Close'].iloc[-1]:.2f}
590
-
591
  ### 🤖 AI 預測機率(短期 1-7天):
592
-
593
  | 方向 | 機率 | 說明 |
594
  |------|------|------|
595
  | 📈 **上漲** | **{probabilities['up']:.1f}%** | 股價向上突破的可能性 |
596
  | 📉 **下跌** | **{probabilities['down']:.1f}%** | 股價向下修正的可能性 |
597
  | ➡️ **盤整** | **{probabilities['sideways']:.1f}%** | 股價維持震盪的可能性 |
598
-
599
  ### 🎯 主要預測方向:
600
  {direction_emoji} **{main_direction}** ({confidence_desc} - {confidence*100:.0f}%)
601
-
602
  ### 📋 投資建議:
603
  """
604
 
@@ -620,15 +378,12 @@ class StockAnalyzer:
620
  - 🛡️ **風險管理**:小部位測試,嚴格執行停損"""
621
 
622
  report += f"""
623
-
624
  ### 📅 中期展望(1個月):
625
  基於當前技術面和市場情緒分析,建議持續關注:
626
  - 關鍵技術位:支撐與阻力區間
627
  - 市場情緒變化:新聞面和資金流向
628
  - 整體大盤走勢:系統性風險評估
629
-
630
  ⚠️ **風險提醒**:此分析基於歷史數據和 AI 模型預測,僅供參考。投資有風險,請謹慎評估並做好風險管理!
631
-
632
  ---
633
  *預測信心度:{confidence*100:.0f}% | 分析時間:{datetime.now().strftime('%Y-%m-%d %H:%M')}*
634
  """
@@ -738,20 +493,18 @@ def create_results_table(results):
738
  # 創建表格 HTML
739
  table_html = """
740
  <div style="overflow-x: auto; margin: 20px 0;">
741
- <table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px;">
742
  <thead>
743
  <tr style="background-color: #f0f0f0;">
744
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">代號</th>
745
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">名稱</th>
746
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">價</th>
747
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">合理價</th>
748
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">上漲空間</th>
749
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">本益比</th>
750
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">ROE</th>
751
- <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">綜合評分</th>
752
- <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">投資建議</th>
753
- <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">技術面</th>
754
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">狀態</th>
755
  </tr>
756
  </thead>
757
  <tbody>
@@ -762,50 +515,34 @@ def create_results_table(results):
762
  if result['error_message']:
763
  direction = "❌ 錯誤"
764
  row_color = "#fff2f2"
765
- tech_direction = "❌ 錯誤"
766
  else:
767
  up_prob = float(result['up_probability'])
768
  down_prob = float(result['down_probability'])
769
  sideways_prob = float(result['sideways_probability'])
770
 
771
- # 技術面方向
772
  if up_prob > down_prob and up_prob > sideways_prob:
773
- tech_direction = "📈 看多"
 
774
  elif down_prob > up_prob and down_prob > sideways_prob:
775
- tech_direction = "📉 看空"
776
- else:
777
- tech_direction = "➡️ 盤整"
778
-
779
- # 根據投資建議決定背景顏色
780
- recommendation = result.get('recommendation', '')
781
- if '強力買入' in recommendation or '買入' in recommendation:
782
- row_color = "#e8f5e8" # 淡綠色
783
- elif '觀望' in recommendation:
784
- row_color = "#fff8dc" # 淡黃色
785
- elif '避開' in recommendation or '賣出' in recommendation:
786
- row_color = "#ffe4e1" # 淡紅色
787
  else:
 
788
  row_color = "#f8f8f8" # 淡灰色
789
 
790
- status = "✅ 成功" if not result['error_message'] else f"❌ {result['error_message'][:20]}..."
791
-
792
- # 投資建議樣式
793
- rec_color = result.get('recommendation_color', 'gray')
794
- investment_rec = f"<span style='color: {rec_color}; font-weight: bold;'>{result.get('recommendation', 'N/A')}</span>"
795
 
796
  table_html += f"""
797
  <tr style="background-color: {row_color};">
798
- <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 11px;">{result['symbol']}</td>
799
- <td style="border: 1px solid #ddd; padding: 6px; font-size: 11px;">{result['name'][:10]}{'...' if len(result['name']) > 10 else ''}</td>
800
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right;">{result['current_price']}</td>
801
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right;">{result.get('fair_value', 'N/A')}</td>
802
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right;">{result.get('upside_potential', 'N/A')}</td>
803
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right;">{result.get('pe_ratio', 'N/A')}</td>
804
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right;">{result.get('roe', 'N/A')}</td>
805
- <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-weight: bold;">{result.get('investment_score', 'N/A')}</td>
806
- <td style="border: 1px solid #ddd; padding: 6px; text-align: center;">{investment_rec}</td>
807
- <td style="border: 1px solid #ddd; padding: 6px; text-align: center;">{tech_direction}</td>
808
- <td style="border: 1px solid #ddd; padding: 6px; font-size: 10px;">{status}</td>
809
  </tr>
810
  """
811
 
@@ -817,81 +554,6 @@ def create_results_table(results):
817
 
818
  return table_html
819
 
820
- def create_detailed_analysis_report(results):
821
- """創建詳細分析報告"""
822
- if not results:
823
- return ""
824
-
825
- # 過濾成功的結果
826
- success_results = [r for r in results if r['error_message'] == '']
827
-
828
- if not success_results:
829
- return "<p>沒有成功分析的股票可顯示詳細報告。</p>"
830
-
831
- report_html = """
832
- <div style="font-family: Arial, sans-serif; margin: 20px 0;">
833
- """
834
-
835
- for result in success_results[:5]: # 只顯示前5個成功的結果
836
- symbol = result['symbol']
837
- name = result['name']
838
- recommendation = result.get('recommendation', 'N/A')
839
- rec_color = result.get('recommendation_color', 'gray')
840
- score = result.get('investment_score', 'N/A')
841
- detailed_analysis = result.get('detailed_analysis', [])
842
-
843
- report_html += f"""
844
- <div style="border: 2px solid #ddd; border-radius: 10px; padding: 15px; margin: 15px 0; background-color: #fafafa;">
845
- <h3 style="color: #333; margin: 0 0 10px 0;">
846
- 📈 {symbol} - {name}
847
- <span style="float: right; color: {rec_color}; font-weight: bold;">{recommendation}</span>
848
- </h3>
849
-
850
- <div style="background: linear-gradient(90deg, #f0f0f0 0%, #e0e0e0 {score}%, #f0f0f0 100%);
851
- border-radius: 5px; padding: 5px; margin: 10px 0;">
852
- <strong>綜合評分: {score}/100</strong>
853
- </div>
854
-
855
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 15px 0;">
856
- <div>
857
- <h4 style="color: #2e7d32; margin: 5px 0;">📊 基本資訊</h4>
858
- <ul style="margin: 5px 0; padding-left: 20px;">
859
- <li>現價: {result['current_price']}</li>
860
- <li>合理價: {result.get('fair_value', 'N/A')}</li>
861
- <li>上漲空間: {result.get('upside_potential', 'N/A')}</li>
862
- </ul>
863
- </div>
864
- <div>
865
- <h4 style="color: #1976d2; margin: 5px 0;">📈 財務指標</h4>
866
- <ul style="margin: 5px 0; padding-left: 20px;">
867
- <li>本益比: {result.get('pe_ratio', 'N/A')}</li>
868
- <li>ROE: {result.get('roe', 'N/A')}</li>
869
- <li>技術面: {result['up_probability']}%↑ {result['down_probability']}%↓</li>
870
- </ul>
871
- </div>
872
- </div>
873
-
874
- <div>
875
- <h4 style="color: #f57c00; margin: 10px 0 5px 0;">🔍 詳細分析</h4>
876
- <ul style="margin: 0; padding-left: 20px; line-height: 1.6;">
877
- """
878
-
879
- for analysis_point in detailed_analysis:
880
- report_html += f"<li>{analysis_point}</li>"
881
-
882
- report_html += """
883
- </ul>
884
- </div>
885
- </div>
886
- """
887
-
888
- if len(success_results) > 5:
889
- report_html += f"<p style='text-align: center; color: #666;'>... 還有 {len(success_results) - 5} 支股票的詳細分析結果</p>"
890
-
891
- report_html += "</div>"
892
-
893
- return report_html
894
-
895
  def create_batch_analysis_charts(results):
896
  """創建批次分析結果圖表"""
897
  if not results:
@@ -1025,7 +687,7 @@ def batch_analyze_stocks(stock_input_text):
1025
  """批次分析股票清單"""
1026
  # 檢查輸入是否為空
1027
  if not stock_input_text or not stock_input_text.strip():
1028
- return "❌ 請輸入股票代號!", "", None, None, None, None, "", ""
1029
 
1030
  try:
1031
  # 從文字輸入框解析股票清單
@@ -1035,7 +697,7 @@ def batch_analyze_stocks(stock_input_text):
1035
  stock_symbols = [symbol.strip().upper() for symbol in stock_symbols if symbol.strip()]
1036
 
1037
  if not stock_symbols:
1038
- return "❌ 未能解析出有效的股票代號!", "", None, None, None, None, "", ""
1039
 
1040
  # 準備結果列表
1041
  results = []
@@ -1066,16 +728,8 @@ def batch_analyze_stocks(stock_input_text):
1066
  'down_probability': 'ERROR',
1067
  'sideways_probability': 'ERROR',
1068
  'confidence': 'ERROR',
1069
- 'fair_value': 'N/A',
1070
- 'upside_potential': 'N/A',
1071
- 'investment_score': 'N/A',
1072
- 'recommendation': '❌ 錯誤',
1073
- 'recommendation_color': 'red',
1074
- 'pe_ratio': 'N/A',
1075
- 'roe': 'N/A',
1076
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1077
- 'error_message': message,
1078
- 'detailed_analysis': []
1079
  })
1080
  progress_messages.append(f"❌ {symbol}: {message}")
1081
  continue
@@ -1091,16 +745,8 @@ def batch_analyze_stocks(stock_input_text):
1091
  'down_probability': 'ERROR',
1092
  'sideways_probability': 'ERROR',
1093
  'confidence': 'ERROR',
1094
- 'fair_value': 'N/A',
1095
- 'upside_potential': 'N/A',
1096
- 'investment_score': 'N/A',
1097
- 'recommendation': '❌ 數據不足',
1098
- 'recommendation_color': 'red',
1099
- 'pe_ratio': 'N/A',
1100
- 'roe': 'N/A',
1101
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1102
- 'error_message': '數據不足,無法分析',
1103
- 'detailed_analysis': []
1104
  })
1105
  progress_messages.append(f"❌ {symbol}: 數據不足")
1106
  continue
@@ -1126,38 +772,18 @@ def batch_analyze_stocks(stock_input_text):
1126
 
1127
  # 獲取股票資訊
1128
  stock_info = analyzer.get_stock_info(symbol)
1129
- current_price = latest['Close']
1130
-
1131
- # 獲取基本面數據
1132
- fundamental_data = analyzer.get_fundamental_data(symbol)
1133
-
1134
- # 計算合理價位
1135
- fair_value_data = analyzer.calculate_fair_value(symbol, fundamental_data, current_price)
1136
-
1137
- # 生成投資建議
1138
- investment_rec = analyzer.generate_investment_recommendation(
1139
- symbol, current_price, probabilities, fair_value_data, fundamental_data
1140
- )
1141
 
1142
  # 記錄成功結果
1143
  results.append({
1144
  'symbol': symbol,
1145
  'name': stock_info['name'],
1146
- 'current_price': f"{current_price:.2f}" if current_price else 'N/A',
1147
  'up_probability': f"{probabilities['up']:.1f}",
1148
  'down_probability': f"{probabilities['down']:.1f}",
1149
  'sideways_probability': f"{probabilities['sideways']:.1f}",
1150
  'confidence': f"{probabilities['confidence']*100:.1f}",
1151
- 'fair_value': f"{fair_value_data['fair_value']:.2f}" if fair_value_data['fair_value'] else 'N/A',
1152
- 'upside_potential': f"{fair_value_data['upside_potential']:+.1f}%" if fair_value_data['upside_potential'] else 'N/A',
1153
- 'investment_score': f"{investment_rec['overall_score']:.0f}",
1154
- 'recommendation': investment_rec['final_recommendation'],
1155
- 'recommendation_color': investment_rec['action_color'],
1156
- 'pe_ratio': f"{fundamental_data.get('pe_ratio', 0):.1f}" if fundamental_data.get('pe_ratio') else 'N/A',
1157
- 'roe': f"{fundamental_data.get('roe', 0)*100:.1f}%" if fundamental_data.get('roe') else 'N/A',
1158
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1159
- 'error_message': '',
1160
- 'detailed_analysis': investment_rec['detailed_analysis']
1161
  })
1162
 
1163
  progress_messages.append(f"✅ {symbol}: 分析完成")
@@ -1172,16 +798,8 @@ def batch_analyze_stocks(stock_input_text):
1172
  'down_probability': 'ERROR',
1173
  'sideways_probability': 'ERROR',
1174
  'confidence': 'ERROR',
1175
- 'fair_value': 'N/A',
1176
- 'upside_potential': 'N/A',
1177
- 'investment_score': 'N/A',
1178
- 'recommendation': '❌ 系統錯誤',
1179
- 'recommendation_color': 'red',
1180
- 'pe_ratio': 'N/A',
1181
- 'roe': 'N/A',
1182
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1183
- 'error_message': f'未預期錯誤: {str(e)}',
1184
- 'detailed_analysis': []
1185
  })
1186
  progress_messages.append(f"❌ {symbol}: 未預期錯誤")
1187
 
@@ -1191,18 +809,15 @@ def batch_analyze_stocks(stock_input_text):
1191
 
1192
  summary_message = f"""
1193
  📈 批次分析完成!
1194
-
1195
  📊 **分析統計:**
1196
  - 總計股票數:{len(stock_symbols)}
1197
  - 成功分析:{success_count}
1198
  - 分析失敗:{error_count}
1199
-
1200
  � **圖表已生成:**
1201
  - 📊 機率比較柱狀圖
1202
  - 🎯 信心度散佈圖
1203
  - 📈 綜合評分雷達圖
1204
  - 🥧 市場情緒餅圖
1205
-
1206
  🎯 **請查看下方圖表進行投資決策分析!**
1207
  """
1208
 
@@ -1211,12 +826,11 @@ def batch_analyze_stocks(stock_input_text):
1211
  # 創建圖表和結果表格
1212
  chart_bar, chart_scatter, chart_radar, chart_pie = create_batch_analysis_charts(results)
1213
  results_table = create_results_table(results)
1214
- detailed_report = create_detailed_analysis_report(results)
1215
 
1216
- return summary_message, progress_log, chart_bar, chart_scatter, chart_radar, chart_pie, results_table, detailed_report
1217
 
1218
  except Exception as e:
1219
- return f"❌ 批次分析過程中發生錯誤:{str(e)}", "", None, None, None, None, "", ""
1220
 
1221
  # 創建 Gradio 界面
1222
  with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app:
@@ -1306,16 +920,12 @@ with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app:
1306
  stock_input_batch = gr.Textbox(
1307
  label="📝 股票代號清單",
1308
  placeholder="""請輸入多個股票代號,支援多種分隔方式:
1309
-
1310
  • 換行分隔:
1311
  2330.TW
1312
  2317.TW
1313
  1303.TW
1314
-
1315
  • 逗號分隔:2330.TW, 2317.TW, 1303.TW
1316
-
1317
  • 空格分隔:2330.TW 2317.TW 1303.TW
1318
-
1319
  • 混合分隔:2330.TW, 2317.TW
1320
  1303.TW; AAPL TSLA""",
1321
  lines=8,
@@ -1373,12 +983,8 @@ with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app:
1373
  chart_sentiment = gr.Plot(label="🥧 整體市場情緒分佈")
1374
 
1375
  # 詳細結果表格
1376
- gr.Markdown("## 📋 綜合分析結果")
1377
- results_table = gr.HTML(label="基本面 + 技術面綜合分析表格")
1378
-
1379
- # 詳細分析報告
1380
- gr.Markdown("## 📊 個股詳細分析")
1381
- detailed_analysis = gr.HTML(label="詳細投資分析報告")
1382
 
1383
  # 快速範例按鈕事件綁定
1384
  example_tw_btn.click(
@@ -1412,8 +1018,7 @@ with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app:
1412
  chart_confidence,
1413
  chart_radar,
1414
  chart_sentiment,
1415
- results_table,
1416
- detailed_analysis
1417
  ]
1418
  )
1419
 
 
124
  'symbol': symbol
125
  }
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  def calculate_technical_indicators(self):
128
  """計算技術指標"""
129
  if self.data is None:
 
343
 
344
  report = f"""
345
  ## 📊 {self.symbol} AI 分析報告
 
346
  ### 📈 技術面分析:
347
  {chr(10).join(f"• {signal}" for signal in technical_signals)}
 
348
  ### 💭 市場情感:{sentiment}
 
349
  ### 📊 近期表現:
350
  - 5日漲跌幅:{price_change:+.2f}%
351
  - 當前價位:${recent_data['Close'].iloc[-1]:.2f}
 
352
  ### 🤖 AI 預測機率(短期 1-7天):
 
353
  | 方向 | 機率 | 說明 |
354
  |------|------|------|
355
  | 📈 **上漲** | **{probabilities['up']:.1f}%** | 股價向上突破的可能性 |
356
  | 📉 **下跌** | **{probabilities['down']:.1f}%** | 股價向下修正的可能性 |
357
  | ➡️ **盤整** | **{probabilities['sideways']:.1f}%** | 股價維持震盪的可能性 |
 
358
  ### 🎯 主要預測方向:
359
  {direction_emoji} **{main_direction}** ({confidence_desc} - {confidence*100:.0f}%)
 
360
  ### 📋 投資建議:
361
  """
362
 
 
378
  - 🛡️ **風險管理**:小部位測試,嚴格執行停損"""
379
 
380
  report += f"""
 
381
  ### 📅 中期展望(1個月):
382
  基於當前技術面和市場情緒分析,建議持續關注:
383
  - 關鍵技術位:支撐與阻力區間
384
  - 市場情緒變化:新聞面和資金流向
385
  - 整體大盤走勢:系統性風險評估
 
386
  ⚠️ **風險提醒**:此分析基於歷史數據和 AI 模型預測,僅供參考。投資有風險,請謹慎評估並做好風險管理!
 
387
  ---
388
  *預測信心度:{confidence*100:.0f}% | 分析時間:{datetime.now().strftime('%Y-%m-%d %H:%M')}*
389
  """
 
493
  # 創建表格 HTML
494
  table_html = """
495
  <div style="overflow-x: auto; margin: 20px 0;">
496
+ <table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif;">
497
  <thead>
498
  <tr style="background-color: #f0f0f0;">
499
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">股票代號</th>
500
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">股票名稱</th>
501
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: right;">當前</th>
502
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: right;">上漲機率(%)</th>
503
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: right;">下跌機率(%)</th>
504
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: right;">盤整機率(%)</th>
505
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: right;">信心度(%)</th>
506
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: center;">預測方向</th>
507
+ <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">狀態</th>
 
 
508
  </tr>
509
  </thead>
510
  <tbody>
 
515
  if result['error_message']:
516
  direction = "❌ 錯誤"
517
  row_color = "#fff2f2"
 
518
  else:
519
  up_prob = float(result['up_probability'])
520
  down_prob = float(result['down_probability'])
521
  sideways_prob = float(result['sideways_probability'])
522
 
 
523
  if up_prob > down_prob and up_prob > sideways_prob:
524
+ direction = "📈 看多"
525
+ row_color = "#f0fff0" # 淡綠色
526
  elif down_prob > up_prob and down_prob > sideways_prob:
527
+ direction = "📉 看空"
528
+ row_color = "#fff0f0" # 淡紅色
 
 
 
 
 
 
 
 
 
 
529
  else:
530
+ direction = "➡️ 盤整"
531
  row_color = "#f8f8f8" # 淡灰色
532
 
533
+ status = "✅ 成功" if not result['error_message'] else f"❌ {result['error_message'][:30]}..."
 
 
 
 
534
 
535
  table_html += f"""
536
  <tr style="background-color: {row_color};">
537
+ <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold;">{result['symbol']}</td>
538
+ <td style="border: 1px solid #ddd; padding: 8px;">{result['name']}</td>
539
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">{result['current_price']}</td>
540
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">{result['up_probability']}</td>
541
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">{result['down_probability']}</td>
542
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">{result['sideways_probability']}</td>
543
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">{result['confidence']}</td>
544
+ <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">{direction}</td>
545
+ <td style="border: 1px solid #ddd; padding: 8px;">{status}</td>
 
 
546
  </tr>
547
  """
548
 
 
554
 
555
  return table_html
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  def create_batch_analysis_charts(results):
558
  """創建批次分析結果圖表"""
559
  if not results:
 
687
  """批次分析股票清單"""
688
  # 檢查輸入是否為空
689
  if not stock_input_text or not stock_input_text.strip():
690
+ return "❌ 請輸入股票代號!", "", None, None, None, None, ""
691
 
692
  try:
693
  # 從文字輸入框解析股票清單
 
697
  stock_symbols = [symbol.strip().upper() for symbol in stock_symbols if symbol.strip()]
698
 
699
  if not stock_symbols:
700
+ return "❌ 未能解析出有效的股票代號!", "", None, None, None, None, ""
701
 
702
  # 準備結果列表
703
  results = []
 
728
  'down_probability': 'ERROR',
729
  'sideways_probability': 'ERROR',
730
  'confidence': 'ERROR',
 
 
 
 
 
 
 
731
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
732
+ 'error_message': message
 
733
  })
734
  progress_messages.append(f"❌ {symbol}: {message}")
735
  continue
 
745
  'down_probability': 'ERROR',
746
  'sideways_probability': 'ERROR',
747
  'confidence': 'ERROR',
 
 
 
 
 
 
 
748
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
749
+ 'error_message': '數據不足,無法分析'
 
750
  })
751
  progress_messages.append(f"❌ {symbol}: 數據不足")
752
  continue
 
772
 
773
  # 獲取股票資訊
774
  stock_info = analyzer.get_stock_info(symbol)
 
 
 
 
 
 
 
 
 
 
 
 
775
 
776
  # 記錄成功結果
777
  results.append({
778
  'symbol': symbol,
779
  'name': stock_info['name'],
780
+ 'current_price': f"{latest['Close']:.2f}" if latest['Close'] else 'N/A',
781
  'up_probability': f"{probabilities['up']:.1f}",
782
  'down_probability': f"{probabilities['down']:.1f}",
783
  'sideways_probability': f"{probabilities['sideways']:.1f}",
784
  'confidence': f"{probabilities['confidence']*100:.1f}",
 
 
 
 
 
 
 
785
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
786
+ 'error_message': ''
 
787
  })
788
 
789
  progress_messages.append(f"✅ {symbol}: 分析完成")
 
798
  'down_probability': 'ERROR',
799
  'sideways_probability': 'ERROR',
800
  'confidence': 'ERROR',
 
 
 
 
 
 
 
801
  'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
802
+ 'error_message': f'未預期錯誤: {str(e)}'
 
803
  })
804
  progress_messages.append(f"❌ {symbol}: 未預期錯誤")
805
 
 
809
 
810
  summary_message = f"""
811
  📈 批次分析完成!
 
812
  📊 **分析統計:**
813
  - 總計股票數:{len(stock_symbols)}
814
  - 成功分析:{success_count}
815
  - 分析失敗:{error_count}
 
816
  � **圖表已生成:**
817
  - 📊 機率比較柱狀圖
818
  - 🎯 信心度散佈圖
819
  - 📈 綜合評分雷達圖
820
  - 🥧 市場情緒餅圖
 
821
  🎯 **請查看下方圖表進行投資決策分析!**
822
  """
823
 
 
826
  # 創建圖表和結果表格
827
  chart_bar, chart_scatter, chart_radar, chart_pie = create_batch_analysis_charts(results)
828
  results_table = create_results_table(results)
 
829
 
830
+ return summary_message, progress_log, chart_bar, chart_scatter, chart_radar, chart_pie, results_table
831
 
832
  except Exception as e:
833
+ return f"❌ 批次分析過程中發生錯誤:{str(e)}", "", None, None, None, None, ""
834
 
835
  # 創建 Gradio 界面
836
  with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app:
 
920
  stock_input_batch = gr.Textbox(
921
  label="📝 股票代號清單",
922
  placeholder="""請輸入多個股票代號,支援多種分隔方式:
 
923
  • 換行分隔:
924
  2330.TW
925
  2317.TW
926
  1303.TW
 
927
  • 逗號分隔:2330.TW, 2317.TW, 1303.TW
 
928
  • 空格分隔:2330.TW 2317.TW 1303.TW
 
929
  • 混合分隔:2330.TW, 2317.TW
930
  1303.TW; AAPL TSLA""",
931
  lines=8,
 
983
  chart_sentiment = gr.Plot(label="🥧 整體市場情緒分佈")
984
 
985
  # 詳細結果表格
986
+ gr.Markdown("## 📋 詳細分析結果")
987
+ results_table = gr.HTML(label="分析結果表格")
 
 
 
 
988
 
989
  # 快速範例按鈕事件綁定
990
  example_tw_btn.click(
 
1018
  chart_confidence,
1019
  chart_radar,
1020
  chart_sentiment,
1021
+ results_table
 
1022
  ]
1023
  )
1024