Spaces:
Running
Running
| # 由 Copilot 生成 - AI 股票分析師 (含批次分析功能) | |
| import subprocess | |
| import sys | |
| import os | |
| from datetime import datetime | |
| # 環境檢測 | |
| IS_HUGGINGFACE_SPACE = "SPACE_ID" in os.environ | |
| print(f"運行環境: {'Hugging Face Spaces' if IS_HUGGINGFACE_SPACE else '本地環境'}") | |
| # 檢查並安裝所需套件的函數 | |
| def install_package(package_name): | |
| try: | |
| __import__(package_name) | |
| except ImportError: | |
| print(f"正在安裝 {package_name}...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", package_name]) | |
| # 安裝必要套件 | |
| required_packages = [ | |
| "torch>=2.0.0", | |
| "torchvision>=0.15.0", | |
| "torchaudio>=2.0.0", | |
| "yfinance>=0.2.18", | |
| "gradio>=4.0.0", | |
| "pandas>=1.5.0", | |
| "numpy>=1.21.0", | |
| "matplotlib>=3.5.0", | |
| "plotly>=5.0.0", | |
| "beautifulsoup4>=4.11.0", | |
| "requests>=2.28.0", | |
| "transformers>=4.21.0", | |
| "accelerate>=0.20.0", | |
| "tokenizers>=0.13.0" | |
| ] | |
| for package in required_packages: | |
| package_name = package.split(">=")[0].split("==")[0] | |
| if package_name == "beautifulsoup4": | |
| package_name = "bs4" | |
| try: | |
| __import__(package_name) | |
| except ImportError: | |
| print(f"正在安裝 {package}...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", package]) | |
| # 現在導入所有套件 | |
| import gradio as gr | |
| import yfinance as yf | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from datetime import datetime, timedelta | |
| import requests | |
| from bs4 import BeautifulSoup | |
| from transformers import pipeline | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # 初始化 Hugging Face 模型 | |
| print("正在載入 AI 模型...") | |
| # 嘗試載入模型,如果失敗則使用較輕量的替代方案 | |
| try: | |
| sentiment_analyzer = pipeline("sentiment-analysis", model="ProsusAI/finbert") | |
| print("FinBERT 情感分析模型載入成功") | |
| except Exception as e: | |
| print(f"FinBERT 載入失敗,嘗試替代模型: {e}") | |
| try: | |
| sentiment_analyzer = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment") | |
| print("多語言情感分析模型載入成功") | |
| except Exception as e2: | |
| print(f"替代模型載入失敗: {e2}") | |
| sentiment_analyzer = None | |
| try: | |
| summarizer = pipeline("summarization", model="facebook/bart-large-cnn") | |
| print("BART 摘要模型載入成功") | |
| except Exception as e: | |
| print(f"BART 載入失敗,嘗試替代模型: {e}") | |
| try: | |
| summarizer = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6") | |
| print("DistilBART 摘要模型載入成功") | |
| except Exception as e2: | |
| print(f"摘要模型載入失敗: {e2}") | |
| summarizer = None | |
| class StockAnalyzer: | |
| def __init__(self): | |
| self.data = None | |
| self.symbol = None | |
| def fetch_stock_data(self, symbol, period="1y"): | |
| """獲取股票歷史數據""" | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| self.data = ticker.history(period=period) | |
| self.symbol = symbol | |
| # 獲取股票資訊 | |
| info = ticker.info | |
| stock_name = info.get('longName', info.get('shortName', symbol)) | |
| return True, f"成功獲取 {symbol} 的歷史數據", stock_name | |
| except Exception as e: | |
| return False, f"數據獲取失敗: {str(e)}", None | |
| def get_stock_info(self, symbol): | |
| """獲取股票基本資訊""" | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| info = ticker.info | |
| current_price = self.data['Close'].iloc[-1] if self.data is not None else None | |
| stock_name = info.get('longName', info.get('shortName', symbol)) | |
| return { | |
| 'name': stock_name, | |
| 'current_price': current_price, | |
| 'symbol': symbol | |
| } | |
| except Exception as e: | |
| return { | |
| 'name': symbol, | |
| 'current_price': None, | |
| 'symbol': symbol | |
| } | |
| def calculate_technical_indicators(self): | |
| """計算技術指標""" | |
| if self.data is None: | |
| return None | |
| df = self.data.copy() | |
| # 移動平均線 | |
| df['MA5'] = df['Close'].rolling(window=5).mean() | |
| df['MA10'] = df['Close'].rolling(window=10).mean() | |
| df['MA20'] = df['Close'].rolling(window=20).mean() | |
| df['MA60'] = df['Close'].rolling(window=60).mean() | |
| # RSI 相對強弱指標 | |
| delta = df['Close'].diff() | |
| gain = (delta.where(delta > 0, 0)).rolling(window=14).mean() | |
| loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean() | |
| rs = gain / loss | |
| df['RSI'] = 100 - (100 / (1 + rs)) | |
| # MACD | |
| exp1 = df['Close'].ewm(span=12).mean() | |
| exp2 = df['Close'].ewm(span=26).mean() | |
| df['MACD'] = exp1 - exp2 | |
| df['MACD_signal'] = df['MACD'].ewm(span=9).mean() | |
| # 布林通道 | |
| df['BB_middle'] = df['Close'].rolling(window=20).mean() | |
| bb_std = df['Close'].rolling(window=20).std() | |
| df['BB_upper'] = df['BB_middle'] + (bb_std * 2) | |
| df['BB_lower'] = df['BB_middle'] - (bb_std * 2) | |
| return df | |
| def get_news_sentiment(self, symbol): | |
| """獲取並分析新聞情感""" | |
| try: | |
| # 模擬新聞標題(實際應用中需要接入新聞 API) | |
| sample_news = [ | |
| f"{symbol} 股價創新高,投資人信心大增", | |
| f"市場關注 {symbol} 最新財報表現", | |
| f"{symbol} 面臨供應鏈挑戰,股價承壓", | |
| f"分析師上調 {symbol} 目標價,看好後市", | |
| f"{symbol} 技術創新獲得市場認可" | |
| ] | |
| sentiments = [] | |
| # 檢查情感分析模型是否可用 | |
| if sentiment_analyzer is None: | |
| # 如果模型不可用,返回模擬的情感分析結果 | |
| for news in sample_news: | |
| # 簡單的關鍵詞情感分析替代方案 | |
| positive_words = ['創新高', '信心大增', '上調', '看好', '創新', '獲得認可'] | |
| negative_words = ['挑戰', '承壓', '面臨', '下滑'] | |
| score = 0.5 # 中性 | |
| sentiment = 'NEUTRAL' | |
| for word in positive_words: | |
| if word in news: | |
| score = 0.8 | |
| sentiment = 'POSITIVE' | |
| break | |
| for word in negative_words: | |
| if word in news: | |
| score = 0.8 | |
| sentiment = 'NEGATIVE' | |
| break | |
| sentiments.append({ | |
| 'text': news, | |
| 'sentiment': sentiment, | |
| 'score': score | |
| }) | |
| else: | |
| # 使用 AI 模型進行情感分析 | |
| for news in sample_news: | |
| result = sentiment_analyzer(news)[0] | |
| sentiments.append({ | |
| 'text': news, | |
| 'sentiment': result['label'], | |
| 'score': result['score'] | |
| }) | |
| return sentiments | |
| except Exception as e: | |
| return [{'text': f'新聞分析暫時無法使用: {str(e)}', 'sentiment': 'NEUTRAL', 'score': 0.5}] | |
| def analyze_sentiment_summary(self, sentiments): | |
| """分析情感摘要""" | |
| if not sentiments: | |
| return "中性" | |
| positive_count = sum(1 for s in sentiments if s['sentiment'] == 'POSITIVE') | |
| negative_count = sum(1 for s in sentiments if s['sentiment'] == 'NEGATIVE') | |
| if positive_count > negative_count: | |
| return "偏樂觀" | |
| elif negative_count > positive_count: | |
| return "偏悲觀" | |
| else: | |
| return "中性" | |
| def calculate_prediction_probabilities(self, technical_signals, sentiment, recent_data): | |
| """計算上漲和下跌機率""" | |
| # 計算技術面得分 | |
| bullish_signals = sum(1 for signal in technical_signals if "多頭" in signal or "機會" in signal) | |
| bearish_signals = sum(1 for signal in technical_signals if "空頭" in signal or "警訊" in signal) | |
| neutral_signals = len(technical_signals) - bullish_signals - bearish_signals | |
| # 技術面得分 (-1 到 1) | |
| total_signals = len(technical_signals) | |
| if total_signals > 0: | |
| tech_score = (bullish_signals - bearish_signals) / total_signals | |
| else: | |
| tech_score = 0 | |
| # 情感得分 (-1 到 1) | |
| sentiment_score = 0 | |
| if sentiment == "偏樂觀": | |
| sentiment_score = 0.6 | |
| elif sentiment == "偏悲觀": | |
| sentiment_score = -0.6 | |
| else: | |
| sentiment_score = 0 | |
| # 價格動量得分 | |
| price_change = ((recent_data['Close'].iloc[-1] - recent_data['Close'].iloc[-5]) / recent_data['Close'].iloc[-5]) * 100 | |
| momentum_score = np.tanh(price_change / 10) # 標準化到 -1 到 1 | |
| # RSI 得分 | |
| latest = recent_data.iloc[-1] | |
| rsi = latest.get('RSI', 50) | |
| if rsi > 70: | |
| rsi_score = -0.5 # 超買,偏空 | |
| elif rsi < 30: | |
| rsi_score = 0.5 # 超賣,偏多 | |
| else: | |
| rsi_score = (50 - rsi) / 100 # 標準化 | |
| # MACD 得分 | |
| macd_score = 0 | |
| if 'MACD' in latest and 'MACD_signal' in latest: | |
| if latest['MACD'] > latest['MACD_signal']: | |
| macd_score = 0.3 | |
| else: | |
| macd_score = -0.3 | |
| # 綜合得分計算(加權平均) | |
| weights = { | |
| 'tech': 0.25, | |
| 'sentiment': 0.20, | |
| 'momentum': 0.25, | |
| 'rsi': 0.15, | |
| 'macd': 0.15 | |
| } | |
| total_score = ( | |
| tech_score * weights['tech'] + | |
| sentiment_score * weights['sentiment'] + | |
| momentum_score * weights['momentum'] + | |
| rsi_score * weights['rsi'] + | |
| macd_score * weights['macd'] | |
| ) | |
| # 將得分轉換為機率 (使用 sigmoid 函數) | |
| def sigmoid(x): | |
| return 1 / (1 + np.exp(-x * 3)) # 放大 3 倍讓機率更明顯 | |
| up_probability = sigmoid(total_score) * 100 | |
| down_probability = sigmoid(-total_score) * 100 | |
| sideways_probability = 100 - up_probability - down_probability | |
| # 確保機率總和為 100% | |
| total_prob = up_probability + down_probability + sideways_probability | |
| up_probability = (up_probability / total_prob) * 100 | |
| down_probability = (down_probability / total_prob) * 100 | |
| sideways_probability = (sideways_probability / total_prob) * 100 | |
| return { | |
| 'up': max(15, min(75, up_probability)), # 限制在 15%-75% 範圍內 | |
| 'down': max(15, min(75, down_probability)), # 限制在 15%-75% 範圍內 | |
| 'sideways': max(10, sideways_probability), # 至少 10% | |
| 'confidence': abs(total_score) # 信心度 | |
| } | |
| def generate_buy_sell_recommendation(self, probabilities, confidence, recent_data): | |
| """生成具體的買賣建議 - 由 Copilot 生成""" | |
| up_prob = probabilities['up'] | |
| down_prob = probabilities['down'] | |
| sideways_prob = probabilities['sideways'] | |
| # 當前價格和近期波動 | |
| current_price = recent_data['Close'].iloc[-1] | |
| rsi = recent_data.iloc[-1].get('RSI', 50) | |
| # 計算綜合評分 | |
| composite_score = up_prob * confidence | |
| recommendations = { | |
| 'holding_advice': "", # 持有股票時的建議 | |
| 'non_holding_advice': "", # 未持有時的建議 | |
| 'risk_level': "", # 風險等級 | |
| 'action_priority': "", # 操作優先級 | |
| 'position_sizing': "" # 倉位建議 | |
| } | |
| # 根據機率和信心度決定建議 | |
| if up_prob >= 60 and confidence >= 0.3: | |
| # 強烈看多 | |
| recommendations['holding_advice'] = "🔥 **強烈建議持有**:股價有很高機率上漲,建議繼續持有並可考慮加碼。設定止損點在當前價格下方5-8%。" | |
| recommendations['non_holding_advice'] = "🚀 **積極買進**:建議分批進場,可先買入30-50%預期倉位,若回檔至支撐位再加碼。" | |
| recommendations['risk_level'] = "中等風險" | |
| recommendations['action_priority'] = "高優先級 - 建議行動" | |
| recommendations['position_sizing'] = "建議倉位:60-80%" | |
| elif up_prob >= 45 and up_prob < 60 and confidence >= 0.25: | |
| # 溫和看多 | |
| recommendations['holding_advice'] = "✅ **建議持有**:維持現有持股,若股價突破關鍵阻力位可考慮小幅加碼。設定止損在-8%。" | |
| recommendations['non_holding_advice'] = "💰 **適度買進**:可小量進場試單,建議先買入20-30%預期倉位,觀察後續走勢。" | |
| recommendations['risk_level'] = "中等風險" | |
| recommendations['action_priority'] = "中優先級 - 可考慮行動" | |
| recommendations['position_sizing'] = "建議倉位:40-60%" | |
| elif down_prob >= 60 and confidence >= 0.3: | |
| # 強烈看空 | |
| recommendations['holding_advice'] = "🚨 **建議賣出**:股價下跌機率很高,建議減持50-80%持股,保留核心持股並嚴格設置止損。" | |
| recommendations['non_holding_advice'] = "⛔ **強烈不建議買進**:市場風險較大,建議等待更佳進場時機,保持現金或考慮避險資產。" | |
| recommendations['risk_level'] = "高風險" | |
| recommendations['action_priority'] = "高優先級 - 建議防守" | |
| recommendations['position_sizing'] = "建議倉位:10-20%" | |
| elif down_prob >= 45 and down_prob < 60 and confidence >= 0.25: | |
| # 溫和看空 | |
| recommendations['holding_advice'] = "⚠️ **謹慎持有**:可減持部分持股降低風險,保留核心持股,密切關注支撐位是否守住。" | |
| recommendations['non_holding_advice'] = "🔍 **暫緩買進**:建議等待股價跌至更好的買點,或等待市場情緒好轉後再進場。" | |
| recommendations['risk_level'] = "中高風險" | |
| recommendations['action_priority'] = "中優先級 - 傾向防守" | |
| recommendations['position_sizing'] = "建議倉位:20-40%" | |
| else: | |
| # 不確定/盤整 | |
| if confidence < 0.2: | |
| # 低信心度 | |
| recommendations['holding_advice'] = "🤔 **觀望持有**:AI預測信心度較低,建議維持現狀,等待更明確的訊號。" | |
| recommendations['non_holding_advice'] = "⏳ **保持觀望**:市場方向不明,建議等待更清晰的進場訊號,避免盲目進場。" | |
| recommendations['risk_level'] = "不確定風險" | |
| recommendations['action_priority'] = "低優先級 - 建議觀望" | |
| recommendations['position_sizing'] = "建議倉位:維持現狀" | |
| else: | |
| # 中等信心度的盤整 | |
| recommendations['holding_advice'] = "📊 **區間持有**:股價可能在區間震蕩,可在高點減持、低點加碼,執行區間操作策略。" | |
| recommendations['non_holding_advice'] = "🎯 **等待機會**:可在支撐位附近小量進場,等待突破訊號再決定是否加碼。" | |
| recommendations['risk_level'] = "中等風險" | |
| recommendations['action_priority'] = "中優先級 - 區間操作" | |
| recommendations['position_sizing'] = "建議倉位:30-50%" | |
| # RSI 超買超賣特殊建議 | |
| if rsi > 80: | |
| recommendations['holding_advice'] += f"\n⚠️ **RSI超買警告**:RSI({rsi:.1f})顯示超買,注意短期回調風險。" | |
| recommendations['non_holding_advice'] += f"\n⚠️ **等待回調**:RSI({rsi:.1f})超買,建議等待回調後再進場。" | |
| elif rsi < 20: | |
| recommendations['holding_advice'] += f"\n💎 **RSI超賣機會**:RSI({rsi:.1f})顯示超賣,可能有反彈機會。" | |
| recommendations['non_holding_advice'] += f"\n💎 **超賣買點**:RSI({rsi:.1f})超賣,可能是不錯的買點。" | |
| return recommendations | |
| def calculate_10day_trend(self, data): | |
| """計算近10日趨勢 - 由 Copilot 生成""" | |
| if data is None or len(data) < 10: | |
| return "N/A", "數據不足" | |
| # 計算近10日價格變化 | |
| close_prices = data['Close'].tail(10) | |
| start_price = close_prices.iloc[0] | |
| end_price = close_prices.iloc[-1] | |
| # 計算變化幅度 | |
| change_pct = ((end_price - start_price) / start_price) * 100 | |
| # 判斷趨勢 | |
| if change_pct >= 3: | |
| trend = "📈↗" | |
| description = f"上漲 {change_pct:.1f}%" | |
| elif change_pct <= -3: | |
| trend = "📉↘" | |
| description = f"下跌 {abs(change_pct):.1f}%" | |
| else: | |
| trend = "➡️↔" | |
| description = f"震盪 {change_pct:+.1f}%" | |
| return trend, description | |
| def calculate_90day_high_breakthrough(self, data): | |
| """計算90日高點突破情況 - 由 Copilot 生成""" | |
| if data is None or len(data) < 90: | |
| return "❌", "數據不足" | |
| # 獲取近90日數據 | |
| last_90_days = data.tail(90) | |
| current_price = last_90_days['Close'].iloc[-1] | |
| # 計算90日內最高價(不包含當日) | |
| high_90_days = last_90_days['Close'].iloc[:-1].max() | |
| # 判斷是否突破 | |
| if current_price >= high_90_days: | |
| # 當前價格等於或超過90日高點 | |
| return "🚀", f"突破90日高點" | |
| else: | |
| # 計算距離高點差距百分比 | |
| gap_pct = ((high_90_days - current_price) / current_price) * 100 | |
| return "📊", f"距高點 -{gap_pct:.1f}%" | |
| def check_alerts(self, recent_data, symbol): | |
| """檢查七項 alert 功能加總結 - 由 Copilot 生成""" | |
| alerts = { | |
| 'news_alert': {'status': 'N', 'message': '暫無重大公告'}, | |
| 'volume_alert1': {'status': 'N', 'message': '量能正常'}, | |
| 'institutional_alert1': {'status': 'N', 'message': '法人未連續買超'}, | |
| 'institutional_alert2': {'status': 'N', 'message': '外資未淨買超'}, | |
| 'technical_alert2': {'status': 'N', 'message': 'MACD/OBV未上升'}, | |
| 'technical_alert3': {'status': 'N', 'message': '未站上MA5'}, | |
| 'technical_alert4': {'status': 'N', 'message': '未站上MA10'}, | |
| 'technical_alert5': {'status': 'N', 'message': '未站上MA20'}, | |
| 'summary': {'status': '0', 'message': '綠燈總數:0/8'} | |
| } | |
| try: | |
| latest = recent_data.iloc[-1] | |
| # 1. 新聞/公告 alert:公司重大公告(併購、BOT、合約、目標價上修) | |
| # 模擬重大新聞檢查(實際應用中需要接入新聞 API) | |
| import random | |
| news_scenarios = [ | |
| f"{symbol} 宣布重大併購案,預計將擴大營運規模", | |
| f"{symbol} 獲得政府 BOT 案標案,合約金額達數十億", | |
| f"{symbol} 簽署重要合作協議,拓展海外市場", | |
| f"分析師大幅上修 {symbol} 目標價,看好未來發展", | |
| f"{symbol} 技術突破獲得認證,將帶動業績成長" | |
| ] | |
| # 隨機模擬是否有重大新聞(實際應用中應該從新聞 API 獲取) | |
| if random.random() < 0.3: # 30% 機率有重大新聞 | |
| news_title = random.choice(news_scenarios) | |
| alerts['news_alert'] = { | |
| 'status': 'Y', | |
| 'message': f'重大公告:{news_title}' | |
| } | |
| # 2. 量能 Alert1:當日量 >= 2×20日平均量 | |
| if 'Volume' in recent_data.columns: | |
| current_volume = latest['Volume'] | |
| avg_20_volume = recent_data['Volume'].rolling(window=20).mean().iloc[-1] | |
| if current_volume >= 2 * avg_20_volume: | |
| alerts['volume_alert1'] = { | |
| 'status': 'Y', | |
| 'message': f'量能爆發(當日量:{current_volume:,.0f} >= 2×20日均量:{avg_20_volume:,.0f})' | |
| } | |
| else: | |
| alerts['volume_alert1'] = { | |
| 'status': 'N', | |
| 'message': f'量能正常(當日量:{current_volume:,.0f},20日均量:{avg_20_volume:,.0f})' | |
| } | |
| # 4. 籌碼 Alert1:三大法人連續買超 | |
| # 模擬三大法人買賣超資料(實際應用中需要接入相關 API) | |
| consecutive_days = random.randint(1, 5) | |
| if consecutive_days >= 3: | |
| alerts['institutional_alert1'] = { | |
| 'status': 'Y', | |
| 'message': f'三大法人連續 {consecutive_days} 日買超,籌碼面偏多' | |
| } | |
| else: | |
| alerts['institutional_alert1'] = { | |
| 'status': 'N', | |
| 'message': f'三大法人買超僅 {consecutive_days} 日,未達連續買超標準' | |
| } | |
| # 5. 籌碼 Alert2:外資當日淨買超 | |
| foreign_net_buy = random.choice([True, False]) | |
| foreign_amount = random.randint(50, 500) * 1000000 # 5千萬到5億 | |
| if foreign_net_buy: | |
| alerts['institutional_alert2'] = { | |
| 'status': 'Y', | |
| 'message': f'外資當日大幅淨買超 {foreign_amount/100000000:.1f} 億,資金流入明顯' | |
| } | |
| else: | |
| alerts['institutional_alert2'] = { | |
| 'status': 'N', | |
| 'message': f'外資當日淨賣超 {foreign_amount/100000000:.1f} 億,資金流出' | |
| } | |
| # 3. 技術 Alert2:MACD交叉/OBV上升 | |
| technical_conditions = [] | |
| # 檢查 MACD 交叉 | |
| macd_crossover = False | |
| if 'MACD' in latest and 'MACD_signal' in latest: | |
| current_macd = latest['MACD'] | |
| current_signal = latest['MACD_signal'] | |
| prev_macd = recent_data['MACD'].iloc[-2] if len(recent_data) > 1 else current_macd | |
| prev_signal = recent_data['MACD_signal'].iloc[-2] if len(recent_data) > 1 else current_signal | |
| # 檢查黃金交叉 | |
| if current_macd > current_signal and prev_macd <= prev_signal: | |
| macd_crossover = True | |
| technical_conditions.append("MACD 黃金交叉") | |
| # 模擬 OBV 上升(實際應用中需要計算 OBV 指標) | |
| obv_rising = random.choice([True, False]) | |
| if obv_rising: | |
| technical_conditions.append("OBV 上升") | |
| if macd_crossover or obv_rising: | |
| alerts['technical_alert2'] = { | |
| 'status': 'Y', | |
| 'message': f'技術指標轉強({"/".join(technical_conditions)}),考慮分批進場' | |
| } | |
| else: | |
| alerts['technical_alert2'] = { | |
| 'status': 'N', | |
| 'message': 'MACD 未交叉且 OBV 未上升,技術面待觀察' | |
| } | |
| # 4. 技術 Alert3:是否站上MA5 | |
| current_price = latest['Close'] | |
| ma5 = latest.get('MA5', 0) | |
| if current_price > ma5: | |
| alerts['technical_alert3'] = { | |
| 'status': 'Y', | |
| 'message': f'站上MA5(當前:{current_price:.2f} > MA5:{ma5:.2f})' | |
| } | |
| else: | |
| alerts['technical_alert3'] = { | |
| 'status': 'N', | |
| 'message': f'未站上MA5(當前:{current_price:.2f},MA5:{ma5:.2f})' | |
| } | |
| # 5. 技術 Alert4:是否站上MA10 | |
| ma10 = latest.get('MA10', 0) | |
| if current_price > ma10: | |
| alerts['technical_alert4'] = { | |
| 'status': 'Y', | |
| 'message': f'站上MA10(當前:{current_price:.2f} > MA10:{ma10:.2f})' | |
| } | |
| else: | |
| alerts['technical_alert4'] = { | |
| 'status': 'N', | |
| 'message': f'未站上MA10(當前:{current_price:.2f},MA10:{ma10:.2f})' | |
| } | |
| # 6. 技術 Alert5:是否站上MA20 | |
| ma20 = latest.get('MA20', 0) | |
| if current_price > ma20: | |
| alerts['technical_alert5'] = { | |
| 'status': 'Y', | |
| 'message': f'站上MA20(當前:{current_price:.2f} > MA20:{ma20:.2f})' | |
| } | |
| else: | |
| alerts['technical_alert5'] = { | |
| 'status': 'N', | |
| 'message': f'未站上MA20(當前:{current_price:.2f},MA20:{ma20:.2f})' | |
| } | |
| # 總結:計算所有alert為綠燈(Y)的數量 | |
| green_count = sum(1 for alert in alerts.values() if alert['status'] == 'Y') | |
| total_alerts = len([k for k in alerts.keys() if k != 'summary']) # 排除summary本身 | |
| alerts['summary'] = { | |
| 'status': str(green_count), | |
| 'message': f'綠燈總數:{green_count}/{total_alerts}個' | |
| } | |
| except Exception as e: | |
| print(f"Alert 檢查發生錯誤: {e}") | |
| return alerts | |
| def generate_comprehensive_prediction(self, technical_signals, sentiment, recent_data): | |
| """生成綜合預測報告 - 由 Copilot 生成""" | |
| # 計算價格變化 | |
| price_change = ((recent_data['Close'].iloc[-1] - recent_data['Close'].iloc[-5]) / recent_data['Close'].iloc[-5]) * 100 | |
| # 計算預測機率 | |
| probabilities = self.calculate_prediction_probabilities(technical_signals, sentiment, recent_data) | |
| # 確定主要預測方向 | |
| max_prob = max(probabilities['up'], probabilities['down'], probabilities['sideways']) | |
| if probabilities['up'] == max_prob: | |
| main_direction = "看多" | |
| direction_emoji = "📈" | |
| elif probabilities['down'] == max_prob: | |
| main_direction = "看空" | |
| direction_emoji = "📉" | |
| else: | |
| main_direction = "盤整" | |
| direction_emoji = "➡️" | |
| # 信心度描述 | |
| confidence = probabilities['confidence'] | |
| if confidence > 0.4: | |
| confidence_desc = "高信心" | |
| elif confidence > 0.2: | |
| confidence_desc = "中等信心" | |
| else: | |
| confidence_desc = "低信心" | |
| # 生成買賣建議 | |
| buy_sell_rec = self.generate_buy_sell_recommendation(probabilities, confidence, recent_data) | |
| # 檢查四項 alert | |
| alerts = self.check_alerts(recent_data, self.symbol) | |
| report = f""" | |
| ## 📊 {self.symbol} AI 分析報告 | |
| ### 🚨 重要 Alert 提醒: | |
| #### 📰 新聞/公告 Alert:{alerts['news_alert']['status']} | |
| {alerts['news_alert']['message']} | |
| #### 📊 量能 Alert1(量能檢查):{alerts['volume_alert1']['status']} | |
| {alerts['volume_alert1']['message']} | |
| #### 💰 籌碼 Alert1(法人買超):{alerts['institutional_alert1']['status']} | |
| {alerts['institutional_alert1']['message']} | |
| #### 💰 籌碼 Alert2(外資買超):{alerts['institutional_alert2']['status']} | |
| {alerts['institutional_alert2']['message']} | |
| #### 📈 技術 Alert2(MACD/OBV):{alerts['technical_alert2']['status']} | |
| {alerts['technical_alert2']['message']} | |
| #### 📈 技術 Alert3(站上MA5):{alerts['technical_alert3']['status']} | |
| {alerts['technical_alert3']['message']} | |
| #### 📈 技術 Alert4(站上MA10):{alerts['technical_alert4']['status']} | |
| {alerts['technical_alert4']['message']} | |
| #### 📈 技術 Alert5(站上MA20):{alerts['technical_alert5']['status']} | |
| {alerts['technical_alert5']['message']} | |
| #### 🔢 Alert總結:{alerts['summary']['status']} | |
| {alerts['summary']['message']} | |
| --- | |
| ### 📈 技術面分析: | |
| {chr(10).join(f"• {signal}" for signal in technical_signals)} | |
| ### 💭 市場情感:{sentiment} | |
| ### 📊 近期表現: | |
| - 5日漲跌幅:{price_change:+.2f}% | |
| - 當前價位:${recent_data['Close'].iloc[-1]:.2f} | |
| ### 🤖 AI 預測機率(短期 1-7天): | |
| | 方向 | 機率 | 說明 | | |
| |------|------|------| | |
| | 📈 **上漲** | **{probabilities['up']:.1f}%** | 股價向上突破的可能性 | | |
| | 📉 **下跌** | **{probabilities['down']:.1f}%** | 股價向下修正的可能性 | | |
| | ➡️ **盤整** | **{probabilities['sideways']:.1f}%** | 股價維持震盪的可能性 | | |
| ### 🎯 主要預測方向: | |
| {direction_emoji} **{main_direction}** ({confidence_desc} - {confidence*100:.0f}%) | |
| ### � **具體投資建議** | |
| --- | |
| #### 🏠 **若您目前持有此股票:** | |
| {buy_sell_rec['holding_advice']} | |
| #### 💰 **若您目前未持有此股票:** | |
| {buy_sell_rec['non_holding_advice']} | |
| #### 📊 **投資參數建議:** | |
| - **風險等級:** {buy_sell_rec['risk_level']} | |
| - **操作優先級:** {buy_sell_rec['action_priority']} | |
| - **{buy_sell_rec['position_sizing']}** | |
| ### 📋 一般性投資策略: | |
| """ | |
| # 根據最高機率給出一般策略 | |
| if probabilities['up'] > 50: | |
| report += """ | |
| - 💡 **多頭策略**:考慮逢低加碼或持有現有部位 | |
| - 🎯 **目標設定**:關注上方阻力位,設定合理獲利目標 | |
| - 🛡️ **風險管理**:設置止損點保護資本""" | |
| elif probabilities['down'] > 50: | |
| report += """ | |
| - 💡 **防守策略**:考慮減碼或等待更佳進場點 | |
| - 🎯 **支撐觀察**:留意下方支撐位是否守住 | |
| - 🛡️ **風險管理**:避免追高,控制倉位大小""" | |
| else: | |
| report += """ | |
| - 💡 **中性策略**:保持觀望,等待明確方向訊號 | |
| - 🎯 **區間操作**:可考慮在支撐阻力區間內操作 | |
| - 🛡️ **風險管理**:小部位測試,嚴格執行停損""" | |
| report += f""" | |
| ### 📅 中期展望(1個月): | |
| 基於當前技術面和市場情緒分析,建議持續關注: | |
| - 關鍵技術位:支撐與阻力區間 | |
| - 市場情緒變化:新聞面和資金流向 | |
| - 整體大盤走勢:系統性風險評估 | |
| ⚠️ **重要風險提醒**: | |
| - 此分析基於歷史數據和 AI 模型預測,僅供參考 | |
| - 投資有風險,請謹慎評估並做好風險管理 | |
| - 建議結合個人財務狀況和投資目標做決策 | |
| - 請勿將此作為唯一投資依據 | |
| --- | |
| *預測信心度:{confidence*100:.0f}% | 分析時間:{datetime.now().strftime('%Y-%m-%d %H:%M')} | 由 Copilot 生成* | |
| """ | |
| return report | |
| def generate_prediction(self, df, news_sentiment): | |
| """生成預測分析""" | |
| if df is None or len(df) < 30: | |
| return "數據不足,無法進行預測分析" | |
| # 獲取最新數據 | |
| latest = df.iloc[-1] | |
| recent_data = df.tail(20) | |
| # 技術分析信號 | |
| technical_signals = [] | |
| # 價格趋势 | |
| if latest['Close'] > latest['MA20']: | |
| technical_signals.append("價格在20日均線之上(多頭信號)") | |
| else: | |
| technical_signals.append("價格在20日均線之下(空頭信號)") | |
| # RSI 分析 | |
| rsi = latest['RSI'] | |
| if rsi > 70: | |
| technical_signals.append(f"RSI({rsi:.1f}) 超買警訊") | |
| elif rsi < 30: | |
| technical_signals.append(f"RSI({rsi:.1f}) 超賣機會") | |
| else: | |
| technical_signals.append(f"RSI({rsi:.1f}) 正常範圍") | |
| # MACD 分析 | |
| if latest['MACD'] > latest['MACD_signal']: | |
| technical_signals.append("MACD 呈現多頭排列") | |
| else: | |
| technical_signals.append("MACD 呈現空頭排列") | |
| # 新聞情感分析 | |
| sentiment_summary = self.analyze_sentiment_summary(news_sentiment) | |
| # 綜合預測 | |
| prediction = self.generate_comprehensive_prediction(technical_signals, sentiment_summary, recent_data) | |
| return prediction | |
| # 創建分析器實例 | |
| analyzer = StockAnalyzer() | |
| def analyze_stock(symbol): | |
| """主要分析函數""" | |
| if not symbol.strip(): | |
| return None, "請輸入股票代碼", "" | |
| # 獲取數據 | |
| result = analyzer.fetch_stock_data(symbol.upper()) | |
| if len(result) == 3: | |
| success, message, stock_name = result | |
| else: | |
| success, message = result | |
| stock_name = None | |
| if not success: | |
| return None, message, "" | |
| # 計算技術指標 | |
| df = analyzer.calculate_technical_indicators() | |
| # 創建價格圖表 | |
| fig = go.Figure() | |
| # 添加K線圖 | |
| fig.add_trace(go.Candlestick( | |
| x=df.index, | |
| open=df['Open'], | |
| high=df['High'], | |
| low=df['Low'], | |
| close=df['Close'], | |
| name='價格' | |
| )) | |
| # 添加移動平均線 | |
| fig.add_trace(go.Scatter(x=df.index, y=df['MA5'], name='MA5', line=dict(color='orange'))) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['MA20'], name='MA20', line=dict(color='blue'))) | |
| fig.update_layout( | |
| title=f'{symbol} 股價走勢與技術指標', | |
| xaxis_title='日期', | |
| yaxis_title='價格', | |
| height=600 | |
| ) | |
| # 獲取新聞情感 | |
| news_sentiment = analyzer.get_news_sentiment(symbol) | |
| # 生成預測 | |
| prediction = analyzer.generate_prediction(df, news_sentiment) | |
| return fig, "分析完成!", prediction | |
| def get_chinese_name(symbol): | |
| """獲取股票中文簡稱 - 由 Copilot 生成""" | |
| # 台股股票中文名稱對照表 | |
| tw_stock_names = { | |
| '2330.TW':'台積電', | |
| '2317.TW':'鴻海', | |
| '2308.TW':'台達電', | |
| '2454.TW':'聯發科', | |
| '2881.TW':'富邦金', | |
| '2382.TW':'廣達', | |
| '2412.TW':'中華電', | |
| '2882.TW':'國泰金', | |
| '2891.TW':'中信金', | |
| '3711.TW':'日月光投控', | |
| '2886.TW':'兆豐金', | |
| '6669.TW':'緯穎', | |
| '2345.TW':'智邦', | |
| '2303.TW':'聯電', | |
| '2884.TW':'玉山金', | |
| '2357.TW':'華碩', | |
| '2885.TW':'元大金', | |
| '2887.TW':'台新新光金', | |
| '3231.TW':'緯創', | |
| '1216.TW':'統一', | |
| '2892.TW':'第一金', | |
| '2383.TW':'台光電', | |
| '2880.TW':'華南金', | |
| '3045.TW':'台灣大', | |
| '2301.TW':'光寶科', | |
| '2603.TW':'長榮', | |
| '6505.TW':'台塑化', | |
| '5880.TW':'合庫金', | |
| '3017.TW':'奇鋐', | |
| '2327.TW':'國巨*', | |
| '3653.TW':'健策', | |
| '2207.TW':'和泰車', | |
| '2890.TW':'永豐金', | |
| '3008.TW':'大立光', | |
| '2059.TW':'川湖', | |
| '1303.TW':'南亞', | |
| '4904.TW':'遠傳', | |
| '2002.TW':'中鋼', | |
| '6919.TW':'康霈*', | |
| '2379.TW':'瑞昱', | |
| '2395.TW':'研華', | |
| '3661.TW':'世芯-KY', | |
| '3034.TW':'聯詠', | |
| '2912.TW':'統一超', | |
| '2883.TW':'凱基金', | |
| '2360.TW':'致茂', | |
| '1301.TW':'台塑', | |
| '3037.TW':'欣興', | |
| '2801.TW':'彰銀', | |
| '2408.TW':'南亞科', | |
| '2368.TW':'金像電', | |
| '2615.TW':'萬海', | |
| '2618.TW':'長榮航', | |
| '5876.TW':'上海商銀', | |
| '1504.TW':'東元', | |
| '2449.TW':'京元電子', | |
| '2376.TW':'技嘉', | |
| '3665.TW':'貿聯-KY', | |
| '6446.TW':'藥華藥', | |
| '5871.TW':'中租-KY', | |
| '4938.TW':'和碩', | |
| '2609.TW':'陽明', | |
| '1519.TW':'華城', | |
| '1101.TW':'台泥', | |
| '3443.TW':'創意', | |
| '1326.TW':'台化', | |
| '3533.TW':'嘉澤', | |
| '2404.TW':'漢唐', | |
| '2356.TW':'英業達', | |
| '4958.TW':'臻鼎-KY', | |
| '3044.TW':'健鼎', | |
| '3036.TW':'文曄', | |
| '2344.TW':'華邦電', | |
| '2834.TW':'臺企銀', | |
| '1590.TW':'亞德客-KY', | |
| '1402.TW':'遠東新', | |
| '8046.TW':'南電', | |
| '2324.TW':'仁寶', | |
| '1102.TW':'亞泥', | |
| '2812.TW':'台中銀', | |
| '2610.TW':'華航', | |
| '8464.TW':'億豐', | |
| '2105.TW':'正新', | |
| '9910.TW':'豐泰', | |
| '1476.TW':'儒鴻', | |
| '2474.TW':'可成', | |
| '3706.TW':'神達', | |
| '3481.TW':'群創', | |
| '1605.TW':'華新', | |
| '3702.TW':'大聯大', | |
| '6239.TW':'力成', | |
| '5269.TW':'祥碩', | |
| '6770.TW':'力積電', | |
| '2347.TW':'聯強', | |
| '2385.TW':'群光', | |
| '2409.TW':'友達', | |
| '6415.TW':'矽力*-KY', | |
| '2354.TW':'鴻準', | |
| '2027.TW':'大成鋼', | |
| '6781.TW':'AES-KY', | |
| '2377.TW':'微星', | |
| '3005.TW':'神基', | |
| '1503.TW':'士電', | |
| '2353.TW':'宏碁', | |
| '6789.TW':'采鈺', | |
| '2542.TW':'興富發', | |
| '1229.TW':'聯華', | |
| '2313.TW':'華通', | |
| '6526.TW':'達發', | |
| '2371.TW':'大同', | |
| '6472.TW':'保瑞', | |
| '9904.TW':'寶成', | |
| '9945.TW':'潤泰新', | |
| '6139.TW':'亞翔', | |
| '6515.TW':'穎崴', | |
| '6409.TW':'旭隼', | |
| '1802.TW':'台玻', | |
| '1513.TW':'中興電', | |
| '2838.TW':'聯邦銀', | |
| '2049.TW':'上銀', | |
| '2646.TW':'星宇航空', | |
| '2633.TW':'台灣高鐵', | |
| '8210.TW':'勤誠', | |
| '1477.TW':'聚陽', | |
| '1795.TW':'美時', | |
| '6805.TW':'富世達', | |
| '2258.TW':'鴻華先進-創', | |
| '6176.TW':'瑞儀', | |
| '8454.TW':'富邦媒', | |
| '2809.TW':'京城銀', | |
| '4763.TW':'材料*-KY', | |
| '2645.TW':'長榮航太', | |
| '2845.TW':'遠東銀', | |
| '6442.TW':'光聖', | |
| '5434.TW':'崇越', | |
| '6191.TW':'精成科', | |
| '9941.TW':'裕融', | |
| '2352.TW':'佳世達', | |
| '2915.TW':'潤泰全', | |
| '2539.TW':'櫻花建', | |
| '6285.TW':'啟碁', | |
| '2889.TW':'國票金', | |
| '6531.TW':'愛普*', | |
| '3023.TW':'信邦', | |
| '1319.TW':'東陽', | |
| '4583.TW':'台灣精銳', | |
| '6890.TW':'來億-KY', | |
| '6691.TW':'洋基工程', | |
| '2540.TW':'愛山林', | |
| '6005.TW':'群益證', | |
| '2634.TW':'漢翔', | |
| '1736.TW':'喬山', | |
| '3376.TW':'新日興', | |
| '2492.TW':'華新科', | |
| '9917.TW':'中保科', | |
| '2498.TW':'宏達電', | |
| '2206.TW':'三陽工業', | |
| '6196.TW':'帆宣', | |
| '1560.TW':'中砂', | |
| '3189.TW':'景碩', | |
| '5522.TW':'遠雄', | |
| '3596.TW':'智易', | |
| '3406.TW':'玉晶光', | |
| '2606.TW':'裕民', | |
| '1722.TW':'台肥', | |
| '1717.TW':'長興', | |
| '2597.TW':'潤弘', | |
| '2451.TW':'創見', | |
| '4766.TW':'南寶', | |
| '2337.TW':'旺宏', | |
| '1210.TW':'大成', | |
| '9958.TW':'世紀鋼', | |
| '5469.TW':'瀚宇博', | |
| '2006.TW':'東和鋼鐵', | |
| '3019.TW':'亞光', | |
| '6257.TW':'矽格', | |
| '2637.TW':'慧洋-KY', | |
| '6592.TW':'和潤企業', | |
| '2504.TW':'國產', | |
| '1907.TW':'永豐餘', | |
| '3030.TW':'德律', | |
| '1215.TW':'卜蜂', | |
| '3035.TW':'智原', | |
| '7722.TW':'LINEPAY', | |
| '6414.TW':'樺漢', | |
| '1773.TW':'勝一', | |
| '2211.TW':'長榮鋼', | |
| '5234.TW':'達興材料', | |
| '6944.TW':'兆聯實業', | |
| '6412.TW':'群電', | |
| '3450.TW':'聯鈞', | |
| '2458.TW':'義隆', | |
| '2543.TW':'皇昌', | |
| '3532.TW':'台勝科', | |
| '6213.TW':'聯茂', | |
| '1231.TW':'聯華食', | |
| '2850.TW':'新產', | |
| '2015.TW':'豐興', | |
| '2923.TW':'鼎固-KY', | |
| '9939.TW':'宏全', | |
| '2855.TW':'統一證', | |
| '2607.TW':'榮運', | |
| '2201.TW':'裕隆', | |
| '9921.TW':'巨大', | |
| '4915.TW':'致伸', | |
| '6670.TW':'復盛應用', | |
| '2441.TW':'超豐', | |
| '6757.TW':'台灣虎航', | |
| '2204.TW':'中華', | |
| '8926.TW':'台汽電', | |
| '2421.TW':'建準', | |
| '2867.TW':'三商壽', | |
| '3515.TW':'華擎', | |
| '6214.TW':'精誠', | |
| '2903.TW':'遠百', | |
| '3583.TW':'辛耘', | |
| '1609.TW':'大亞', | |
| '8028.TW':'昇陽半導體', | |
| '2388.TW':'威盛', | |
| '2548.TW':'華固', | |
| '6278.TW':'台表科', | |
| '5388.TW':'中磊', | |
| '9914.TW':'美利達', | |
| '8996.TW':'高力', | |
| '3413.TW':'京鼎', | |
| '2023.TW':'燁輝', | |
| '2467.TW':'志聖', | |
| '2455.TW':'全新', | |
| '2363.TW':'矽統', | |
| '2312.TW':'金寶', | |
| '3042.TW':'晶技', | |
| '8070.TW':'長華*', | |
| '1808.TW':'潤隆', | |
| '6550.TW':'北極星藥業-KY', | |
| '2393.TW':'億光', | |
| '9907.TW':'統一實', | |
| '9933.TW':'中鼎', | |
| '2328.TW':'廣宇', | |
| '2208.TW':'台船', | |
| '1514.TW':'亞力', | |
| '2374.TW':'佳能', | |
| '2101.TW':'南港', | |
| '1227.TW':'佳格', | |
| '1314.TW':'中石化', | |
| '2501.TW':'國建', | |
| '2481.TW':'強茂', | |
| '4919.TW':'新唐', | |
| '2329.TW':'華泰', | |
| '3714.TW':'富采', | |
| '6282.TW':'康舒', | |
| '8016.TW':'矽創', | |
| '2362.TW':'藍天', | |
| '2439.TW':'美律', | |
| '1434.TW':'福懋', | |
| '2897.TW':'王道銀行', | |
| '2707.TW':'晶華', | |
| '2530.TW':'華建', | |
| '6491.TW':'晶碩', | |
| '3715.TW':'定穎投控', | |
| '3010.TW':'華立', | |
| '2359.TW':'所羅門', | |
| '2392.TW':'正崴', | |
| '2247.TW':'汎德永業', | |
| '6271.TW':'同欣電', | |
| '3013.TW':'晟銘電', | |
| '6116.TW':'彩晶', | |
| '6949.TW':'沛爾生醫-創', | |
| '4770.TW':'上品', | |
| '7749.TW':'意騰-KY', | |
| '1232.TW':'大統益', | |
| '3563.TW':'牧德', | |
| '8112.TW':'至上', | |
| '6177.TW':'達麗', | |
| '1409.TW':'新纖', | |
| '2014.TW':'中鴻', | |
| '8033.TW':'雷虎', | |
| '8422.TW':'可寧衛', | |
| '1440.TW':'南紡', | |
| '3014.TW':'聯陽', | |
| '2476.TW':'鉅祥', | |
| '5534.TW':'長虹', | |
| '2520.TW':'冠德', | |
| '2428.TW':'興勤', | |
| '2836.TW':'高雄銀', | |
| '2820.TW':'華票', | |
| '6605.TW':'帝寶', | |
| '8478.TW':'東哥遊艇', | |
| '6456.TW':'GIS-KY', | |
| '3592.TW':'瑞鼎', | |
| '8150.TW':'南茂', | |
| '1104.TW':'環泥', | |
| '3006.TW':'晶豪科', | |
| '6962.TW':'奕力-KY', | |
| '4961.TW':'天鈺', | |
| '6768.TW':'志強-KY', | |
| '6579.TW':'研揚', | |
| '1608.TW':'華榮', | |
| '6269.TW':'台郡', | |
| '8081.TW':'致新', | |
| '9802.TW':'鈺齊-KY', | |
| '2849.TW':'安泰銀', | |
| '6197.TW':'佳必琪', | |
| '3617.TW':'碩天', | |
| '4722.TW':'國精化', | |
| '2727.TW':'王品', | |
| '1904.TW':'正隆', | |
| '1723.TW':'中碳', | |
| '2106.TW':'建大', | |
| '1707.TW':'葡萄王', | |
| '2851.TW':'中再保', | |
| '3029.TW':'零壹', | |
| '5607.TW':'遠雄港', | |
| '9937.TW':'全國', | |
| '9911.TW':'櫻花', | |
| '2367.TW':'燿華', | |
| '2480.TW':'敦陽科', | |
| '2486.TW':'一詮', | |
| '3703.TW':'欣陸', | |
| '1712.TW':'興農', | |
| '6854.TW':'錼創科技-KY創', | |
| '6449.TW':'鈺邦', | |
| '6873.TW':'泓德能源', | |
| '6719.TW':'力智', | |
| '9940.TW':'信義', | |
| '9930.TW':'中聯資源', | |
| '6589.TW':'台康生技', | |
| '2316.TW':'楠梓電', | |
| '1536.TW':'和大', | |
| '4536.TW':'拓凱', | |
| '8114.TW':'振樺電', | |
| '6937.TW':'天虹', | |
| '2524.TW':'京城', | |
| '1419.TW':'新紡', | |
| '2231.TW':'為升', | |
| '6753.TW':'龍德造船', | |
| '6451.TW':'訊芯-KY', | |
| '2227.TW':'裕日車', | |
| '3016.TW':'嘉晶', | |
| '6901.TW':'鑽石投資', | |
| '6869.TW':'雲豹能源', | |
| '2515.TW':'中工', | |
| '2545.TW':'皇翔', | |
| '2535.TW':'達欣工', | |
| '9938.TW':'百和', | |
| '3673.TW':'TPK-KY', | |
| '3026.TW':'禾伸堂', | |
| '2608.TW':'嘉里大榮', | |
| '2402.TW':'毅嘉', | |
| '3059.TW':'華晶科', | |
| '2704.TW':'國賓', | |
| '9925.TW':'新保', | |
| '8039.TW':'台虹', | |
| '3380.TW':'明泰', | |
| '3167.TW':'大量', | |
| '3705.TW':'永信', | |
| '6533.TW':'晶心科', | |
| '9908.TW':'大台北', | |
| '6806.TW':'森崴能源', | |
| '5007.TW':'三星', | |
| '1442.TW':'名軒', | |
| '4551.TW':'智伸科', | |
| '6965.TW':'中傑-KY', | |
| '1234.TW':'黑松', | |
| '2009.TW':'第一銅', | |
| '1789.TW':'神隆', | |
| '6206.TW':'飛捷', | |
| '6183.TW':'關貿', | |
| '2731.TW':'雄獅', | |
| '2511.TW':'太子', | |
| '5284.TW':'jpp-KY', | |
| '4104.TW':'佳醫', | |
| '3545.TW':'敦泰', | |
| '2351.TW':'順德', | |
| '6438.TW':'迅得', | |
| '3708.TW':'上緯投控', | |
| '3305.TW':'昇貿', | |
| '8021.TW':'尖點', | |
| '2472.TW':'立隆電', | |
| '1313.TW':'聯成', | |
| '3416.TW':'融程電', | |
| '8131.TW':'福懋科', | |
| '3090.TW':'日電貿', | |
| '3704.TW':'合勤控', | |
| '4576.TW':'大銀微系統', | |
| '2355.TW':'敬鵬', | |
| '2464.TW':'盟立', | |
| '2753.TW':'八方雲集', | |
| '2401.TW':'凌陽', | |
| '1903.TW':'士紙', | |
| '4968.TW':'立積', | |
| '6541.TW':'泰福-KY', | |
| '2031.TW':'新光鋼', | |
| '2373.TW':'震旦行', | |
| '2493.TW':'揚博', | |
| '2832.TW':'台產', | |
| '2905.TW':'三商', | |
| '3033.TW':'威健', | |
| '2605.TW':'新興', | |
| '2103.TW':'台橡', | |
| '5243.TW':'乙盛-KY', | |
| '6166.TW':'凌華', | |
| '2010.TW':'春源', | |
| '2723.TW':'美食-KY', | |
| '1909.TW':'榮成', | |
| '1905.TW':'華紙', | |
| '1522.TW':'堤維西', | |
| '2348.TW':'海悅', | |
| '8462.TW':'柏文', | |
| '6782.TW':'視陽', | |
| '6189.TW':'豐藝', | |
| '5222.TW':'全訊', | |
| '6230.TW':'尼得科超眾', | |
| '6235.TW':'華孚', | |
| '2528.TW':'皇普', | |
| '1436.TW':'華友聯', | |
| '4755.TW':'三福化', | |
| '4532.TW':'瑞智', | |
| '6581.TW':'鋼聯', | |
| '3694.TW':'海華', | |
| '2233.TW':'宇隆', | |
| '1726.TW':'永記', | |
| '1612.TW':'宏泰', | |
| '2331.TW':'精英', | |
| '6024.TW':'群益期', | |
| '1307.TW':'三芳', | |
| '1304.TW':'台聚', | |
| '3022.TW':'威強電', | |
| '1702.TW':'南僑', | |
| '2108.TW':'南帝', | |
| '6534.TW':'正瀚-創', | |
| '4736.TW':'泰博', | |
| '1532.TW':'勤美', | |
| '2547.TW':'日勝生', | |
| '2466.TW':'冠西電', | |
| '2546.TW':'根基', | |
| '2630.TW':'亞航', | |
| '2617.TW':'台航', | |
| '7740.TW':'熙特爾-創', | |
| '3576.TW':'聯合再生', | |
| '1563.TW':'巧新', | |
| '1710.TW':'東聯', | |
| '1718.TW':'中纖', | |
| '3605.TW':'宏致', | |
| '5306.TW':'桂盟', | |
| '1103.TW':'嘉泥', | |
| '4906.TW':'正文', | |
| '6525.TW':'捷敏-KY', | |
| '8404.TW':'百和興業-KY', | |
| '1537.TW':'廣隆', | |
| '1711.TW':'永光', | |
| '2104.TW':'國際中橡', | |
| '2612.TW':'中航', | |
| '2436.TW':'偉詮電', | |
| '2332.TW':'友訊', | |
| '1312.TW':'國喬', | |
| '6863.TW':'永道-KY', | |
| '6923.TW':'中台', | |
| '2457.TW':'飛宏', | |
| '2340.TW':'台亞', | |
| '2034.TW':'允強', | |
| '6790.TW':'永豐實', | |
| '8163.TW':'達方', | |
| '8499.TW':'鼎炫-KY', | |
| '4771.TW':'望隼', | |
| '6215.TW':'和椿', | |
| '8341.TW':'日友', | |
| '2338.TW':'光罩', | |
| '2534.TW':'宏盛', | |
| '2908.TW':'特力', | |
| '3032.TW':'偉訓', | |
| '3015.TW':'全漢', | |
| '1720.TW':'生達', | |
| '1582.TW':'信錦', | |
| '1614.TW':'三洋電', | |
| '2429.TW':'銘旺科', | |
| '2913.TW':'農林', | |
| '2636.TW':'台驊控股', | |
| '6192.TW':'巨路', | |
| '3048.TW':'益登', | |
| '1235.TW':'興泰', | |
| '2323.TW':'中環', | |
| '4977.TW':'眾達-KY', | |
| '6464.TW':'台數科', | |
| '4306.TW':'炎洲', | |
| '3701.TW':'大眾控', | |
| '6153.TW':'嘉聯益', | |
| '6957.TW':'裕慶-KY', | |
| '1604.TW':'聲寶', | |
| '1218.TW':'泰山', | |
| '3056.TW':'富華新', | |
| '1203.TW':'味王', | |
| '1615.TW':'大山', | |
| '6914.TW':'阜爾運通', | |
| '8213.TW':'志超', | |
| '9927.TW':'泰銘', | |
| '6202.TW':'盛群', | |
| '3645.TW':'達邁', | |
| '1110.TW':'東泥', | |
| '3149.TW':'正達', | |
| '8110.TW':'華東', | |
| '6655.TW':'科定', | |
| '4571.TW':'鈞興-KY', | |
| '2365.TW':'昆盈', | |
| '2536.TW':'宏普', | |
| '9942.TW':'茂順', | |
| '4526.TW':'東台', | |
| '2489.TW':'瑞軒', | |
| '5225.TW':'東科-KY', | |
| '5525.TW':'順天', | |
| '9926.TW':'新海', | |
| '5531.TW':'鄉林', | |
| '6115.TW':'鎰勝', | |
| '1618.TW':'合機', | |
| '2102.TW':'泰豐', | |
| '8374.TW':'羅昇', | |
| '8467.TW':'波力-KY', | |
| '4164.TW':'承業醫', | |
| '3712.TW':'永崴投控', | |
| '2375.TW':'凱美', | |
| '4137.TW':'麗豐-KY', | |
| '6830.TW':'汎銓', | |
| '9924.TW':'福興', | |
| '5258.TW':'虹堡', | |
| '2013.TW':'中鋼構', | |
| '2228.TW':'劍麟', | |
| '8045.TW':'達運光電', | |
| '4739.TW':'康普', | |
| '2762.TW':'世界健身-KY', | |
| '1321.TW':'大洋', | |
| '4960.TW':'誠美材', | |
| '3622.TW':'洋華', | |
| '9943.TW':'好樂迪', | |
| '8271.TW':'宇瞻', | |
| '4142.TW':'國光生', | |
| '3003.TW':'健和興', | |
| '2423.TW':'固緯', | |
| '2387.TW':'精元', | |
| '2852.TW':'第一保', | |
| '1714.TW':'和桐', | |
| '2420.TW':'新巨', | |
| '4438.TW':'廣越', | |
| '3454.TW':'晶睿', | |
| '8261.TW':'富鼎', | |
| '4746.TW':'台耀', | |
| '6184.TW':'大豐電', | |
| '2397.TW':'友通', | |
| '3062.TW':'建漢', | |
| '1709.TW':'和益', | |
| '1201.TW':'味全', | |
| '5203.TW':'訊連', | |
| '5288.TW':'豐祥-KY', | |
| '6443.TW':'元晶', | |
| '3312.TW':'弘憶股', | |
| '8249.TW':'菱光', | |
| '8215.TW':'明基材', | |
| '6933.TW':'AMAX-KY', | |
| '6117.TW':'迎廣', | |
| '1603.TW':'華電', | |
| '1708.TW':'東鹼', | |
| '3004.TW':'豐達科', | |
| '2478.TW':'大毅', | |
| '3130.TW':'一零四', | |
| '6277.TW':'宏正', | |
| '4967.TW':'十銓', | |
| '6862.TW':'三集瑞-KY', | |
| '6585.TW':'鼎基', | |
| '3028.TW':'增你強', | |
| '2419.TW':'仲琦', | |
| '2450.TW':'神腦', | |
| '3209.TW':'全科', | |
| '1713.TW':'國化', | |
| '1786.TW':'科妍', | |
| '2107.TW':'厚生', | |
| '6958.TW':'日盛台駿', | |
| '3679.TW':'新至陞', | |
| '4119.TW':'旭富', | |
| '9918.TW':'欣天然', | |
| '2029.TW':'盛餘', | |
| '1225.TW':'福懋油', | |
| '2614.TW':'東森', | |
| '2406.TW':'國碩', | |
| '2369.TW':'菱生', | |
| '2505.TW':'國揚', | |
| '1597.TW':'直得', | |
| '2349.TW':'錸德', | |
| '4927.TW':'泰鼎-KY', | |
| '7736.TW':'虎山', | |
| '6706.TW':'惠特', | |
| '2114.TW':'鑫永銓', | |
| '2414.TW':'精技', | |
| '2433.TW':'互盛電', | |
| '1308.TW':'亞聚', | |
| '2442.TW':'新美齊', | |
| '2527.TW':'宏璟', | |
| '6281.TW':'全國電', | |
| '6120.TW':'達運', | |
| '3504.TW':'揚明光', | |
| '6672.TW':'騰輝電子-KY', | |
| '1342.TW':'八貫', | |
| '1535.TW':'中宇', | |
| '1616.TW':'億泰', | |
| '2459.TW':'敦吉', | |
| '2706.TW':'第一店', | |
| '6994.TW':'富威電力', | |
| '5608.TW':'四維航', | |
| '4994.TW':'傳奇', | |
| '6209.TW':'今國光', | |
| '1305.TW':'華夏', | |
| '2020.TW':'美亞', | |
| '2417.TW':'圓剛', | |
| '2405.TW':'輔信', | |
| '4572.TW':'駐龍', | |
| '4916.TW':'事欣科', | |
| '5292.TW':'華懋', | |
| '5519.TW':'隆大', | |
| '8473.TW':'山林水', | |
| '1737.TW':'臺鹽', | |
| '1315.TW':'達新', | |
| '1444.TW':'力麗', | |
| '9905.TW':'大華', | |
| '5471.TW':'松翰', | |
| '3535.TW':'晶彩科', | |
| '6112.TW':'邁達特', | |
| '6715.TW':'嘉基', | |
| '1437.TW':'勤益控', | |
| '2453.TW':'凌群', | |
| '2495.TW':'普安', | |
| '1108.TW':'幸福', | |
| '6756.TW':'威鋒電子', | |
| '6416.TW':'瑞祺電通', | |
| '3501.TW':'維熹', | |
| '1558.TW':'伸興', | |
| '1760.TW':'寶齡富錦', | |
| '2497.TW':'怡利電', | |
| '2816.TW':'旺旺保', | |
| '4935.TW':'茂林-KY', | |
| '5521.TW':'工信', | |
| '8105.TW':'凌巨', | |
| '9946.TW':'三發地產', | |
| '4566.TW':'時碩工業', | |
| '2514.TW':'龍邦', | |
| '2236.TW':'百達-KY', | |
| '1734.TW':'杏輝', | |
| '1810.TW':'和成', | |
| '1256.TW':'鮮活果汁-KY', | |
| '5538.TW':'東明-KY', | |
| '8482.TW':'商億-KY', | |
| '1316.TW':'上曜', | |
| '1583.TW':'程泰', | |
| '2028.TW':'威致', | |
| '3055.TW':'蔚華科', | |
| '3257.TW':'虹冠電', | |
| '4976.TW':'佳凌', | |
| '3049.TW':'精金', | |
| '2426.TW':'鼎元', | |
| '1109.TW':'信大', | |
| '1457.TW':'宜進', | |
| '2427.TW':'三商電', | |
| '3447.TW':'展達', | |
| '6887.TW':'寶綠特-KY', | |
| '9906.TW':'欣巴巴', | |
| '6477.TW':'安集', | |
| '1447.TW':'力鵬', | |
| '1515.TW':'力山', | |
| '1217.TW':'愛之味', | |
| '2012.TW':'春雨', | |
| '2008.TW':'高興昌', | |
| '6201.TW':'亞弘電', | |
| '3338.TW':'泰碩', | |
| '4942.TW':'嘉彰', | |
| '4912.TW':'聯德控股-KY', | |
| '6928.TW':'攸泰科技', | |
| '6689.TW':'伊雲谷', | |
| '2342.TW':'茂矽', | |
| '2701.TW':'萬企', | |
| '1525.TW':'江申', | |
| '8103.TW':'瀚荃', | |
| '3266.TW':'昇陽', | |
| '3530.TW':'晶相光', | |
| '3716.TW':'中化控股', | |
| '5285.TW':'界霖', | |
| '5283.TW':'禾聯碩', | |
| '5515.TW':'建國', | |
| '6625.TW':'必應', | |
| '3528.TW':'安馳', | |
| '8442.TW':'威宏-KY', | |
| '1455.TW':'集盛', | |
| '1725.TW':'元禎', | |
| '2748.TW':'雲品', | |
| '3138.TW':'耀登', | |
| '2538.TW':'基泰', | |
| '2601.TW':'益航', | |
| '1464.TW':'得力', | |
| '1472.TW':'三洋實業', | |
| '6743.TW':'安普新', | |
| '6695.TW':'芯鼎', | |
| '6657.TW':'華安', | |
| '4569.TW':'六方科-KY', | |
| '4540.TW':'全球傳動', | |
| '6838.TW':'台新藥', | |
| '1524.TW':'耿鼎', | |
| '1219.TW':'福壽', | |
| '1310.TW':'台苯', | |
| '5706.TW':'鳳凰', | |
| '4564.TW':'元翎', | |
| '4952.TW':'凌通', | |
| '1527.TW':'鑽全', | |
| '1727.TW':'中華化', | |
| '2062.TW':'橋椿', | |
| '2109.TW':'華豐', | |
| '2007.TW':'燁興', | |
| '1459.TW':'聯發', | |
| '1309.TW':'台達化', | |
| '6168.TW':'宏齊', | |
| '6224.TW':'聚鼎', | |
| '6776.TW':'展碁國際', | |
| '6885.TW':'全福生技', | |
| '6955.TW':'邦睿生技-創', | |
| '9934.TW':'成霖', | |
| '6558.TW':'興能高', | |
| '6165.TW':'浪凡', | |
| '5533.TW':'皇鼎', | |
| '4737.TW':'華廣', | |
| '3356.TW':'奇偶', | |
| '1611.TW':'中電', | |
| '2017.TW':'官田鋼', | |
| '2239.TW':'英利-KY', | |
| '2485.TW':'兆赫', | |
| '3047.TW':'訊舟', | |
| '1438.TW':'三地開發', | |
| '1414.TW':'東和', | |
| '8367.TW':'建新國際', | |
| '9931.TW':'欣高', | |
| '3717.TW':'聯嘉投控', | |
| '1460.TW':'宏遠', | |
| '1783.TW':'和康生', | |
| '3040.TW':'遠見', | |
| '2722.TW':'夏都', | |
| '2364.TW':'倫飛', | |
| '2030.TW':'彰源', | |
| '1598.TW':'岱宇', | |
| '1443.TW':'立益物流', | |
| '2254.TW':'巨鎧精密-創', | |
| '2477.TW':'美隆電', | |
| '4934.TW':'太極', | |
| '8104.TW':'錸寶', | |
| '8463.TW':'潤泰材', | |
| '6742.TW':'澤米', | |
| '4562.TW':'穎漢', | |
| '2462.TW':'良得電', | |
| '3168.TW':'眾福科', | |
| '1423.TW':'利華', | |
| '1463.TW':'強盛新', | |
| '1473.TW':'台南', | |
| '1752.TW':'南光', | |
| '3051.TW':'力特', | |
| '3024.TW':'憶聲', | |
| '2613.TW':'中櫃', | |
| '2468.TW':'華經', | |
| '2506.TW':'太設', | |
| '4439.TW':'冠星-KY', | |
| '6799.TW':'來頡', | |
| '6906.TW':'現觀科', | |
| '8011.TW':'台通', | |
| '6794.TW':'向榮生技-創', | |
| '9935.TW':'慶豐富', | |
| '2910.TW':'統領', | |
| '1733.TW':'五鼎', | |
| '1806.TW':'冠軍', | |
| '1451.TW':'年興', | |
| '1529.TW':'樂事綠能', | |
| '1435.TW':'中福', | |
| '2739.TW':'寒舍', | |
| '3031.TW':'佰鴻', | |
| '3060.TW':'銘異', | |
| '2488.TW':'漢平', | |
| '4989.TW':'榮科', | |
| '6128.TW':'上福', | |
| '6205.TW':'詮欣', | |
| '4764.TW':'雙鍵', | |
| '3557.TW':'嘉威', | |
| '3311.TW':'閎暉', | |
| '6668.TW':'中揚光', | |
| '6931.TW':'青松健康', | |
| '2484.TW':'希華', | |
| '2509.TW':'全坤建', | |
| '3046.TW':'建碁', | |
| '3038.TW':'全台', | |
| '2399.TW':'映泰', | |
| '1587.TW':'吉茂', | |
| '6951.TW':'青新-創', | |
| '9919.TW':'康那香', | |
| '4557.TW':'永新-KY', | |
| '6591.TW':'動力-KY', | |
| '6283.TW':'淳安', | |
| '8222.TW':'寶一', | |
| '1730.TW':'花仙子', | |
| '2250.TW':'IKKA-KY', | |
| '2516.TW':'新建', | |
| '2537.TW':'聯上發', | |
| '3041.TW':'揚智', | |
| '2465.TW':'麗臺', | |
| '2241.TW':'艾姆勒', | |
| '1809.TW':'中釉', | |
| '9955.TW':'佳龍', | |
| '6504.TW':'南六', | |
| '3543.TW':'州巧', | |
| '3588.TW':'通嘉', | |
| '8476.TW':'台境*', | |
| '6952.TW':'大武山', | |
| '6918.TW':'愛派司', | |
| '6754.TW':'匯僑設計', | |
| '1589.TW':'永冠-KY', | |
| '1533.TW':'車王電', | |
| '1341.TW':'富林-KY', | |
| '2705.TW':'六福', | |
| '2460.TW':'建通', | |
| '1339.TW':'昭輝', | |
| '7721.TW':'微程式', | |
| '4555.TW':'氣立', | |
| '4414.TW':'如興', | |
| '6582.TW':'申豐', | |
| '4108.TW':'懷特', | |
| '3669.TW':'圓展', | |
| '6909.TW':'創控', | |
| '1528.TW':'恩德', | |
| '2038.TW':'海光', | |
| '2425.TW':'承啟', | |
| '2906.TW':'高林', | |
| '2611.TW':'志信', | |
| '2642.TW':'宅配通', | |
| '2430.TW':'燦坤', | |
| '2302.TW':'麗正', | |
| '1817.TW':'凱撒衛', | |
| '1731.TW':'美吾華', | |
| '8481.TW':'政伸', | |
| '5244.TW':'弘凱', | |
| '6606.TW':'建德工業', | |
| '8411.TW':'福貞-KY', | |
| '8162.TW':'微矽電子-創', | |
| '8072.TW':'陞泰', | |
| '6902.TW':'GOGOLOOK', | |
| '2115.TW':'六暉-KY', | |
| '1233.TW':'天仁', | |
| '2413.TW':'環科', | |
| '2461.TW':'光群雷', | |
| '1446.TW':'宏和', | |
| '1617.TW':'榮星', | |
| '6807.TW':'峰源-KY', | |
| '6796.TW':'晉弘', | |
| '6792.TW':'詠業', | |
| '7631.TW':'聚賢研發-創', | |
| '7705.TW':'三商餐飲', | |
| '6426.TW':'統新', | |
| '6136.TW':'富爾特', | |
| '3346.TW':'麗清', | |
| '4545.TW':'銘鈺', | |
| '4588.TW':'玖鼎電力', | |
| '4949.TW':'有成精密', | |
| '4552.TW':'力達-KY', | |
| '4148.TW':'全宇生技-KY', | |
| '3591.TW':'艾笛森', | |
| '5484.TW':'慧友', | |
| '6216.TW':'居易', | |
| '2022.TW':'聚亨', | |
| '1530.TW':'亞崴', | |
| '1325.TW':'恆大', | |
| '1439.TW':'雋揚', | |
| '3052.TW':'夆典', | |
| '3027.TW':'盛達', | |
| '6834.TW':'天二科技', | |
| '6835.TW':'圓裕', | |
| '6861.TW':'睿生光電', | |
| '6936.TW':'永鴻生技', | |
| '3025.TW':'星通', | |
| '2945.TW':'三商家購', | |
| '1721.TW':'三晃', | |
| '2069.TW':'運錩', | |
| '1517.TW':'利奇', | |
| '1568.TW':'倉佑', | |
| '2471.TW':'資通', | |
| '2483.TW':'百容', | |
| '6666.TW':'羅麗芬-KY', | |
| '7732.TW':'金興精密', | |
| '6598.TW':'ABC-KY', | |
| '6552.TW':'易華電', | |
| '6423.TW':'億而得-創', | |
| '6142.TW':'友勁', | |
| '6108.TW':'競國', | |
| '3652.TW':'精聯', | |
| '6698.TW':'旭暉應材', | |
| '2415.TW':'錩新', | |
| '3058.TW':'立德', | |
| '1220.TW':'台榮', | |
| '2305.TW':'全友', | |
| '3094.TW':'聯傑', | |
| '6671.TW':'三能-KY', | |
| '3437.TW':'榮創', | |
| '4106.TW':'雃博', | |
| '4560.TW':'強信-KY', | |
| '6155.TW':'鈞寶', | |
| '5215.TW':'科嘉-KY', | |
| '4155.TW':'訊映', | |
| '4956.TW':'光鋐', | |
| '3518.TW':'柏騰', | |
| '6658.TW':'聯策', | |
| '8487.TW':'愛爾達-創', | |
| '8438.TW':'昶昕', | |
| '1236.TW':'宏亞', | |
| '1416.TW':'廣豐', | |
| '1410.TW':'南染', | |
| '1540.TW':'喬福', | |
| '1475.TW':'業旺', | |
| '1506.TW':'正道', | |
| '1468.TW':'昶和', | |
| '1541.TW':'錩泰', | |
| '1531.TW':'高林股', | |
| '2248.TW':'華勝-KY', | |
| '1906.TW':'寶隆', | |
| '2712.TW':'遠雄來', | |
| '3296.TW':'勝德', | |
| '4190.TW':'佐登-KY', | |
| '5546.TW':'永固-KY', | |
| '6133.TW':'金橋', | |
| '2616.TW':'山隆', | |
| '3054.TW':'立萬利', | |
| '3050.TW':'鈺德', | |
| '2390.TW':'云辰', | |
| '1762.TW':'中化生', | |
| '2025.TW':'千興', | |
| '1521.TW':'大億', | |
| '1470.TW':'大統新創', | |
| '1466.TW':'聚隆', | |
| '1445.TW':'大宇', | |
| '2032.TW':'新鋼', | |
| '2702.TW':'華園', | |
| '2901.TW':'欣欣', | |
| '4999.TW':'鑫禾', | |
| '8466.TW':'美吉吉-KY', | |
| '9944.TW':'新麗', | |
| '4581.TW':'光隆精密-KY', | |
| '4930.TW':'燦星網', | |
| '4720.TW':'德淵', | |
| '1626.TW':'艾美特-KY', | |
| '1454.TW':'台富', | |
| '1338.TW':'廣華-KY', | |
| '1323.TW':'永裕', | |
| '1452.TW':'宏益', | |
| '2033.TW':'佳大', | |
| '3092.TW':'鴻碩', | |
| '3021.TW':'鴻名', | |
| '3011.TW':'今皓', | |
| '2440.TW':'太空梭', | |
| '2432.TW':'倚天酷碁-創', | |
| '4426.TW':'利勤', | |
| '3419.TW':'譁裕', | |
| '5906.TW':'台南-KY', | |
| '6405.TW':'悅城', | |
| '3607.TW':'谷崧', | |
| '3002.TW':'歐格', | |
| '3164.TW':'景岳', | |
| '2024.TW':'志聯', | |
| '1453.TW':'大將', | |
| '1449.TW':'佳和', | |
| '1417.TW':'嘉裕', | |
| '1432.TW':'大魯閣', | |
| '4133.TW':'亞諾法', | |
| '6243.TW':'迅杰', | |
| '6152.TW':'百一', | |
| '9902.TW':'台火', | |
| '6771.TW':'平和環保-創', | |
| '6674.TW':'鋐寶科技', | |
| '8429.TW':'金麗-KY', | |
| '3686.TW':'達能', | |
| '1474.TW':'弘裕', | |
| '1805.TW':'寶徠', | |
| '1735.TW':'日勝化', | |
| '2482.TW':'連宇', | |
| '2438.TW':'翔耀', | |
| '2431.TW':'聯昌', | |
| '1471.TW':'首利', | |
| '1337.TW':'再生-KY', | |
| '6969.TW':'成信實業*-創', | |
| '6645.TW':'金萬林-創', | |
| '8940.TW':'新天地', | |
| '3550.TW':'聯穎', | |
| '6141.TW':'柏承', | |
| '1465.TW':'偉全', | |
| '2904.TW':'匯僑', | |
| '2496.TW':'卓越', | |
| '1467.TW':'南緯', | |
| '1340.TW':'勝悅-KY', | |
| '1732.TW':'毛寶', | |
| '6916.TW':'華凌', | |
| '6924.TW':'榮惠-KY創', | |
| '9928.TW':'中視', | |
| '9929.TW':'秋雨', | |
| '5907.TW':'大洋-KY', | |
| '4440.TW':'宜新實業', | |
| '1456.TW':'怡華', | |
| '1539.TW':'巨庭', | |
| '2444.TW':'兆勁', | |
| '2939.TW':'永邑-KY', | |
| '2491.TW':'吉祥全', | |
| '1413.TW':'宏洲', | |
| '1418.TW':'東華', | |
| '1526.TW':'日馳', | |
| '2424.TW':'隴華', | |
| '2929.TW':'淘帝-KY', | |
| '3150.TW':'鈺寶-創', | |
| '3308.TW':'聯德', | |
| '8201.TW':'無敵', | |
| '3043.TW':'科風', | |
| '1776.TW':'展宇', | |
| '2243.TW':'宏旭-KY', | |
| '1512.TW':'瑞利', | |
| '1324.TW':'地球', | |
| '8443.TW':'阿瘦', | |
| '6988.TW':'威力暘-創', | |
| '6164.TW':'華興', | |
| '6226.TW':'光鼎', | |
| '6431.TW':'光麗-KY', | |
| '6573.TW':'虹揚-KY', | |
| '6641.TW':'基士德-KY', | |
| '8488.TW':'吉源-KY', | |
| '1441.TW':'大東', | |
| '3057.TW':'喬鼎', | |
| '9912.TW':'偉聯', | |
| '3494.TW':'誠研', | |
| '4807.TW':'日成-KY', | |
| '1516.TW':'川飛', | |
| '3229.TW':'晟鈦', | |
| '2434.TW':'統懋', | |
| '3432.TW':'台端', | |
| } | |
| # 美股股票中文名稱對照表 | |
| us_stock_names = { | |
| 'AAPL': '蘋果', | |
| 'MSFT': '微軟', | |
| 'GOOGL': '谷歌', | |
| 'AMZN': '亞馬遜', | |
| 'TSLA': '特斯拉', | |
| 'META': 'Meta', | |
| 'NVDA': '輝達', | |
| 'NFLX': '網飛', | |
| 'AMD': '超微', | |
| 'INTC': '英特爾', | |
| 'ORCL': '甲骨文', | |
| 'CRM': 'Salesforce', | |
| 'ADBE': 'Adobe', | |
| 'PYPL': 'PayPal', | |
| 'UBER': 'Uber', | |
| 'SPOT': 'Spotify', | |
| 'ZOOM': 'Zoom', | |
| 'SHOP': 'Shopify', | |
| 'SQ': 'Block', | |
| 'TWTR': '推特', | |
| 'SNAP': 'Snapchat', | |
| 'PINS': 'Pinterest', | |
| 'DOCU': 'DocuSign', | |
| 'ROKU': 'Roku', | |
| 'PLTR': 'Palantir', | |
| 'COIN': 'Coinbase', | |
| 'RBLX': 'Roblox', | |
| 'RIVN': 'Rivian', | |
| 'LCID': 'Lucid', | |
| 'NIO': '蔚來', | |
| 'XPEV': '小鵬', | |
| 'LI': '理想', | |
| 'BABA': '阿里巴巴', | |
| 'JD': '京東', | |
| 'PDD': '拼多多', | |
| 'BIDU': '百度', | |
| 'TME': '騰訊音樂', | |
| 'BILI': 'B站', | |
| 'VTI': '全市場ETF', | |
| 'VOO': 'S&P500ETF', | |
| 'QQQ': '納斯達克ETF', | |
| 'SPY': 'SPDR S&P500', | |
| 'IWM': '羅素2000', | |
| 'EFA': '歐澳遠東ETF', | |
| 'EEM': '新興市場ETF', | |
| 'VNQ': '房地產ETF', | |
| 'GLD': '黃金ETF', | |
| 'TLT': '長期國債ETF', | |
| 'BTC-USD': '比特幣', | |
| 'ETH-USD': '以太幣', | |
| '^GSPC': 'S&P500指數', | |
| '^DJI': '道瓊指數', | |
| '^IXIC': '納斯達克指數', | |
| } | |
| # 首先查找台股 | |
| if symbol in tw_stock_names: | |
| return tw_stock_names[symbol] | |
| # 然後查找美股 | |
| if symbol in us_stock_names: | |
| return us_stock_names[symbol] | |
| # 如果都找不到,返回原始名稱(通常是從 yfinance 獲取的英文名稱) | |
| return symbol | |
| def create_results_table(results): | |
| """創建結果表格 - 由 Copilot 生成""" | |
| if not results: | |
| return "" | |
| # 創建表格 HTML | |
| table_html = """ | |
| <div style="overflow-x: auto; margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif;"> | |
| <thead> | |
| <tr style="background-color: #f0f0f0;"> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">中文名稱</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">股票代號</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">當前價格</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 12px;">近10日漲跌</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 12px;">90日高點突破</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">上漲機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">下跌機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">盤整機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">信心度(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">新聞</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">量能1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">籌碼1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">籌碼2</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術2</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術3</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術4</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術5</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">總結</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 12px;">預測方向</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">持股建議</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">買進建議</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">狀態</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| """ | |
| for result in results: | |
| # 判斷預測方向和顏色 | |
| if result['error_message']: | |
| direction = "❌ 錯誤" | |
| row_color = "#fff2f2" | |
| holding_advice = "N/A" | |
| buying_advice = "N/A" | |
| news_alert = "N/A" | |
| volume_alert1 = "N/A" | |
| institutional_alert1 = "N/A" | |
| institutional_alert2 = "N/A" | |
| technical_alert2 = "N/A" | |
| technical_alert3 = "N/A" | |
| technical_alert4 = "N/A" | |
| technical_alert5 = "N/A" | |
| summary_alert = "N/A" | |
| else: | |
| up_prob = float(result['up_probability']) | |
| down_prob = float(result['down_probability']) | |
| sideways_prob = float(result['sideways_probability']) | |
| confidence = float(result['confidence']) / 100.0 # 轉換為小數 | |
| if up_prob > down_prob and up_prob > sideways_prob: | |
| direction = "📈 看多" | |
| row_color = "#f0fff0" # 淡綠色 | |
| elif down_prob > up_prob and down_prob > sideways_prob: | |
| direction = "📉 看空" | |
| row_color = "#fff0f0" # 淡紅色 | |
| else: | |
| direction = "➡️ 盤整" | |
| row_color = "#f8f8f8" # 淡灰色 | |
| # 生成簡化的買賣建議 | |
| if up_prob >= 60 and confidence >= 0.3: | |
| holding_advice = "🔥 強烈持有" | |
| buying_advice = "🚀 積極買進" | |
| elif up_prob >= 45 and confidence >= 0.25: | |
| holding_advice = "✅ 建議持有" | |
| buying_advice = "💰 適度買進" | |
| elif down_prob >= 60 and confidence >= 0.3: | |
| holding_advice = "🚨 建議賣出" | |
| buying_advice = "⛔ 不建議買進" | |
| elif down_prob >= 45 and confidence >= 0.25: | |
| holding_advice = "⚠️ 謹慎持有" | |
| buying_advice = "🔍 暫緩買進" | |
| else: | |
| if confidence < 0.2: | |
| holding_advice = "🤔 觀望持有" | |
| buying_advice = "⏳ 保持觀望" | |
| else: | |
| holding_advice = "📊 區間持有" | |
| buying_advice = "🎯 等待機會" | |
| # Alert 狀態顯示 | |
| news_alert = f"{'🟢' if result.get('news_alert') == 'Y' else '🔴'} {result.get('news_alert', 'N')}" | |
| volume_alert1 = f"{'🟢' if result.get('volume_alert1') == 'Y' else '🔴'} {result.get('volume_alert1', 'N')}" | |
| institutional_alert1 = f"{'🟢' if result.get('institutional_alert1') == 'Y' else '🔴'} {result.get('institutional_alert1', 'N')}" | |
| institutional_alert2 = f"{'🟢' if result.get('institutional_alert2') == 'Y' else '🔴'} {result.get('institutional_alert2', 'N')}" | |
| technical_alert2 = f"{'🟢' if result.get('technical_alert2') == 'Y' else '🔴'} {result.get('technical_alert2', 'N')}" | |
| technical_alert3 = f"{'🟢' if result.get('technical_alert3') == 'Y' else '🔴'} {result.get('technical_alert3', 'N')}" | |
| technical_alert4 = f"{'🟢' if result.get('technical_alert4') == 'Y' else '🔴'} {result.get('technical_alert4', 'N')}" | |
| technical_alert5 = f"{'🟢' if result.get('technical_alert5') == 'Y' else '🔴'} {result.get('technical_alert5', 'N')}" | |
| summary_alert = f"🔢 {result.get('summary', '0')}" | |
| status = "✅ 成功" if not result['error_message'] else f"❌ {result['error_message'][:20]}..." | |
| # 獲取中文名稱 | |
| chinese_name = get_chinese_name(result['symbol']) | |
| # 顯示近10日趨勢 - 由 Copilot 生成 | |
| trend_display = f"{result.get('trend_10day_icon', 'N/A')} {result.get('trend_10day_desc', '')}" | |
| if result['error_message']: | |
| trend_display = "❌ N/A" | |
| # 顯示90日高點突破 - 由 Copilot 生成 | |
| high_90_display = f"{result.get('high_90_icon', 'N/A')} {result.get('high_90_desc', '')}" | |
| if result['error_message']: | |
| high_90_display = "❌ N/A" | |
| table_html += f""" | |
| <tr style="background-color: {row_color};"> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 12px; color: #2c3e50;">{chinese_name}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 11px; color: #7f8c8d;">{result['symbol']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{result['current_price']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{trend_display}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{high_90_display}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{result['up_probability']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{result['down_probability']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{result['sideways_probability']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{result['confidence']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('news_message', '')}">{news_alert}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('volume_alert1_message', '')}">{volume_alert1}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('institutional_alert1_message', '')}">{institutional_alert1}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('institutional_alert2_message', '')}">{institutional_alert2}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('technical_alert2_message', '')}">{technical_alert2}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('technical_alert3_message', '')}">{technical_alert3}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('technical_alert4_message', '')}">{technical_alert4}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{result.get('technical_alert5_message', '')}">{technical_alert5}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px; font-weight: bold; color: #e74c3c;" title="{result.get('summary_message', '')}">{summary_alert}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{direction}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;">{holding_advice}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;">{buying_advice}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-size: 11px;">{status}</td> | |
| </tr> | |
| """ | |
| table_html += """ | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="margin-top: 10px; font-size: 11px; color: #666;"> | |
| <strong>🚨 十項 Alert 說明:</strong><br> | |
| 📰 <strong>新聞:</strong> 公司重大公告(併購、BOT、合約、目標價上修)<br> | |
| 📊 <strong>量能1:</strong> 當日量 >= 2×20日平均量 | <strong>量能2:</strong> 突破價格阻力位(顯示阻力位和目標價)<br> | |
| 💰 <strong>籌碼1:</strong> 三大法人連續買超 | <strong>籌碼2:</strong> 外資當日淨買超↑<br> | |
| 📈 <strong>技術1:</strong> 價格突破20日高 | <strong>技術2:</strong> MACD交叉/OBV上升<br> | |
| 📈 <strong>技術3:</strong> 是否站上MA5 | <strong>技術4:</strong> 是否站上MA10 | <strong>技術5:</strong> 是否站上MA20<br> | |
| � <strong>90日高點突破:</strong> 🚀 突破90日高點 | 📊 距高點差距百分比<br> | |
| �🔢 <strong>總結:</strong> 所有Alert為綠燈(Y)的數量加總<br> | |
| <strong>🟢 Y = 達到條件 | 🔴 N = 未達到條件</strong><br><br> | |
| <strong>買賣建議說明:</strong><br> | |
| 🔥強烈持有/🚀積極買進: 高機率上漲且高信心度<br> | |
| ✅建議持有/💰適度買進: 中高機率上漲<br> | |
| 🚨建議賣出/⛔不建議買進: 高機率下跌且高信心度<br> | |
| ⚠️謹慎持有/🔍暫緩買進: 中高機率下跌<br> | |
| 🤔觀望持有/⏳保持觀望: 低信心度預測<br> | |
| 📊區間持有/🎯等待機會: 盤整機率較高<br> | |
| <em>註:此建議僅供參考,請結合個人情況謹慎投資 - 由 Copilot 生成</em> | |
| </div> | |
| """ | |
| return table_html | |
| def create_batch_analysis_charts(results): | |
| """創建批次分析結果圖表""" | |
| if not results: | |
| return None, None, None, None | |
| # 過濾出成功分析的結果 | |
| success_results = [r for r in results if r['error_message'] == ''] | |
| if not success_results: | |
| return None, None, None, None | |
| # 準備數據 | |
| symbols = [r['symbol'] for r in success_results] | |
| up_probs = [float(r['up_probability']) for r in success_results] | |
| down_probs = [float(r['down_probability']) for r in success_results] | |
| sideways_probs = [float(r['sideways_probability']) for r in success_results] | |
| confidence = [float(r['confidence']) for r in success_results] | |
| # 1. 機率比較柱狀圖 | |
| fig_bar = go.Figure() | |
| fig_bar.add_trace(go.Bar(name='上漲機率', x=symbols, y=up_probs, marker_color='green', opacity=0.8)) | |
| fig_bar.add_trace(go.Bar(name='下跌機率', x=symbols, y=down_probs, marker_color='red', opacity=0.8)) | |
| fig_bar.add_trace(go.Bar(name='盤整機率', x=symbols, y=sideways_probs, marker_color='gray', opacity=0.8)) | |
| fig_bar.update_layout( | |
| title='📊 股票預測機率比較', | |
| xaxis_title='股票代號', | |
| yaxis_title='機率 (%)', | |
| barmode='group', | |
| height=500, | |
| showlegend=True, | |
| xaxis_tickangle=-45 | |
| ) | |
| # 2. 信心度散佈圖 | |
| fig_scatter = go.Figure() | |
| # 根據最高機率決定顏色 | |
| colors = [] | |
| for i in range(len(success_results)): | |
| if up_probs[i] > down_probs[i] and up_probs[i] > sideways_probs[i]: | |
| colors.append('green') # 看多 | |
| elif down_probs[i] > up_probs[i] and down_probs[i] > sideways_probs[i]: | |
| colors.append('red') # 看空 | |
| else: | |
| colors.append('gray') # 盤整 | |
| fig_scatter.add_trace(go.Scatter( | |
| x=symbols, | |
| y=confidence, | |
| mode='markers+text', | |
| marker=dict( | |
| size=[max(prob) for prob in zip(up_probs, down_probs, sideways_probs)], | |
| sizemode='diameter', | |
| sizeref=2, | |
| color=colors, | |
| opacity=0.7, | |
| line=dict(width=2, color='white') | |
| ), | |
| text=[f"{conf:.1f}%" for conf in confidence], | |
| textposition="middle center", | |
| name='信心度' | |
| )) | |
| fig_scatter.update_layout( | |
| title='🎯 預測信心度分佈 (圓圈大小=最高機率)', | |
| xaxis_title='股票代號', | |
| yaxis_title='信心度 (%)', | |
| height=500, | |
| xaxis_tickangle=-45 | |
| ) | |
| # 3. 綜合評分雷達圖 (取前6支股票) | |
| radar_data = success_results[:6] # 限制顯示數量避免過於擁擠 | |
| fig_radar = go.Figure() | |
| categories = ['上漲機率', '信心度', '綜合評分'] | |
| for i, result in enumerate(radar_data): | |
| # 計算綜合評分 (上漲機率 * 信心度 / 100) | |
| composite_score = float(result['up_probability']) * float(result['confidence']) / 100 | |
| values = [ | |
| float(result['up_probability']), | |
| float(result['confidence']), | |
| composite_score | |
| ] | |
| fig_radar.add_trace(go.Scatterpolar( | |
| r=values + [values[0]], # 閉合雷達圖 | |
| theta=categories + [categories[0]], | |
| fill='toself', | |
| name=result['symbol'], | |
| opacity=0.6 | |
| )) | |
| fig_radar.update_layout( | |
| polar=dict( | |
| radialaxis=dict( | |
| visible=True, | |
| range=[0, 100] | |
| ) | |
| ), | |
| title='📈 股票綜合評分雷達圖 (前6支)', | |
| height=500, | |
| showlegend=True | |
| ) | |
| # 4. 機率分佈餅圖統計 | |
| # 統計各種預測傾向的數量 | |
| bullish_count = sum(1 for r in success_results if float(r['up_probability']) > max(float(r['down_probability']), float(r['sideways_probability']))) | |
| bearish_count = sum(1 for r in success_results if float(r['down_probability']) > max(float(r['up_probability']), float(r['sideways_probability']))) | |
| neutral_count = len(success_results) - bullish_count - bearish_count | |
| fig_pie = go.Figure(data=[go.Pie( | |
| labels=['看多股票', '看空股票', '盤整股票'], | |
| values=[bullish_count, bearish_count, neutral_count], | |
| marker_colors=['green', 'red', 'gray'], | |
| textinfo='label+percent+value', | |
| hovertemplate='<b>%{label}</b><br>數量: %{value}<br>比例: %{percent}<extra></extra>' | |
| )]) | |
| fig_pie.update_layout( | |
| title='🥧 整體市場情緒分佈', | |
| height=400 | |
| ) | |
| return fig_bar, fig_scatter, fig_radar, fig_pie | |
| def batch_analyze_stocks(stock_list_input): | |
| """批次分析股票清單""" | |
| # 檢查輸入是否為空 | |
| if not stock_list_input or not stock_list_input.strip(): | |
| return "❌ 請輸入股票代碼!可用逗號、空格或換行分隔多個股票代碼。", "", None, None, None, None, "" | |
| try: | |
| # 解析股票清單(支援多種分隔符) | |
| import re | |
| stock_symbols = re.split(r'[,\s\n]+', stock_list_input.strip()) | |
| stock_symbols = [symbol.strip().upper() for symbol in stock_symbols if symbol.strip()] | |
| if not stock_symbols: | |
| return "❌ 未找到有效的股票代碼!", "", None, None, None, None, "" | |
| # 準備結果列表 | |
| results = [] | |
| progress_messages = [] | |
| progress_messages.append(f"📊 開始批次分析 {len(stock_symbols)} 支股票...") | |
| # 分析每支股票 | |
| for i, symbol in enumerate(stock_symbols, 1): | |
| progress_messages.append(f"\n🔍 正在分析 ({i}/{len(stock_symbols)}): {symbol}") | |
| try: | |
| # 獲取股票數據 | |
| result = analyzer.fetch_stock_data(symbol.upper()) | |
| if len(result) == 3: | |
| success, message, stock_name = result | |
| else: | |
| success, message = result | |
| stock_name = symbol | |
| if not success: | |
| # 記錄錯誤 | |
| results.append({ | |
| 'symbol': symbol, | |
| 'name': stock_name or symbol, | |
| 'current_price': 'N/A', | |
| 'up_probability': 'ERROR', | |
| 'down_probability': 'ERROR', | |
| 'sideways_probability': 'ERROR', | |
| 'confidence': 'ERROR', | |
| 'trend_10day_icon': 'N/A', | |
| 'trend_10day_desc': '數據不足', | |
| 'high_90_icon': 'N/A', | |
| 'high_90_desc': '數據不足', | |
| 'news_alert': 'N/A', | |
| 'volume_alert1': 'N/A', | |
| 'institutional_alert1': 'N/A', | |
| 'institutional_alert2': 'N/A', | |
| 'technical_alert2': 'N/A', | |
| 'technical_alert3': 'N/A', | |
| 'technical_alert4': 'N/A', | |
| 'technical_alert5': 'N/A', | |
| 'summary': 'N/A', | |
| 'news_message': '', | |
| 'volume_alert1_message': '', | |
| 'institutional_alert1_message': '', | |
| 'institutional_alert2_message': '', | |
| 'technical_alert2_message': '', | |
| 'technical_alert3_message': '', | |
| 'technical_alert4_message': '', | |
| 'technical_alert5_message': '', | |
| 'summary_message': '', | |
| 'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| 'error_message': message | |
| }) | |
| progress_messages.append(f"❌ {symbol}: {message}") | |
| continue | |
| # 計算技術指標 | |
| df = analyzer.calculate_technical_indicators() | |
| if df is None or len(df) < 30: | |
| results.append({ | |
| 'symbol': symbol, | |
| 'name': stock_name or symbol, | |
| 'current_price': 'N/A', | |
| 'up_probability': 'ERROR', | |
| 'down_probability': 'ERROR', | |
| 'sideways_probability': 'ERROR', | |
| 'confidence': 'ERROR', | |
| 'trend_10day_icon': 'N/A', | |
| 'trend_10day_desc': '數據不足', | |
| 'high_90_icon': 'N/A', | |
| 'high_90_desc': '數據不足', | |
| 'news_alert': 'N/A', | |
| 'volume_alert1': 'N/A', | |
| 'institutional_alert1': 'N/A', | |
| 'institutional_alert2': 'N/A', | |
| 'technical_alert2': 'N/A', | |
| 'technical_alert3': 'N/A', | |
| 'technical_alert4': 'N/A', | |
| 'technical_alert5': 'N/A', | |
| 'summary': 'N/A', | |
| 'news_message': '', | |
| 'volume_alert1_message': '', | |
| 'volume_alert2_message': '', | |
| 'institutional_alert1_message': '', | |
| 'institutional_alert2_message': '', | |
| 'technical_alert1_message': '', | |
| 'technical_alert2_message': '', | |
| 'technical_alert3_message': '', | |
| 'technical_alert4_message': '', | |
| 'technical_alert5_message': '', | |
| 'summary_message': '', | |
| 'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| 'error_message': '數據不足,無法分析' | |
| }) | |
| progress_messages.append(f"❌ {symbol}: 數據不足") | |
| continue | |
| # 獲取新聞情感 | |
| news_sentiment = analyzer.get_news_sentiment(symbol) | |
| sentiment_summary = analyzer.analyze_sentiment_summary(news_sentiment) | |
| # 計算預測機率 | |
| recent_data = df.tail(20) | |
| technical_signals = [] | |
| # 簡化的技術信號計算 | |
| latest = df.iloc[-1] | |
| if latest['Close'] > latest['MA20']: | |
| technical_signals.append("價格在20日均線之上") | |
| else: | |
| technical_signals.append("價格在20日均線之下") | |
| probabilities = analyzer.calculate_prediction_probabilities( | |
| technical_signals, sentiment_summary, recent_data | |
| ) | |
| # 獲取股票資訊 | |
| stock_info = analyzer.get_stock_info(symbol) | |
| # 檢查四項 alert | |
| alerts = analyzer.check_alerts(recent_data, symbol) | |
| # 計算近10日趨勢 - 由 Copilot 生成 | |
| trend_icon, trend_desc = analyzer.calculate_10day_trend(df) | |
| # 計算90日高點突破情況 - 由 Copilot 生成 | |
| high_90_icon, high_90_desc = analyzer.calculate_90day_high_breakthrough(df) | |
| # 記錄成功結果 | |
| results.append({ | |
| 'symbol': symbol, | |
| 'name': stock_info['name'], | |
| 'current_price': f"{latest['Close']:.2f}" if latest['Close'] else 'N/A', | |
| 'up_probability': f"{probabilities['up']:.1f}", | |
| 'down_probability': f"{probabilities['down']:.1f}", | |
| 'sideways_probability': f"{probabilities['sideways']:.1f}", | |
| 'confidence': f"{probabilities['confidence']*100:.1f}", | |
| 'trend_10day_icon': trend_icon, | |
| 'trend_10day_desc': trend_desc, | |
| 'high_90_icon': high_90_icon, | |
| 'high_90_desc': high_90_desc, | |
| 'news_alert': alerts['news_alert']['status'], | |
| 'volume_alert1': alerts['volume_alert1']['status'], | |
| 'institutional_alert1': alerts['institutional_alert1']['status'], | |
| 'institutional_alert2': alerts['institutional_alert2']['status'], | |
| 'technical_alert2': alerts['technical_alert2']['status'], | |
| 'technical_alert3': alerts['technical_alert3']['status'], | |
| 'technical_alert4': alerts['technical_alert4']['status'], | |
| 'technical_alert5': alerts['technical_alert5']['status'], | |
| 'summary': alerts['summary']['status'], | |
| 'news_message': alerts['news_alert']['message'], | |
| 'volume_alert1_message': alerts['volume_alert1']['message'], | |
| 'institutional_alert1_message': alerts['institutional_alert1']['message'], | |
| 'institutional_alert2_message': alerts['institutional_alert2']['message'], | |
| 'technical_alert2_message': alerts['technical_alert2']['message'], | |
| 'technical_alert3_message': alerts['technical_alert3']['message'], | |
| 'technical_alert4_message': alerts['technical_alert4']['message'], | |
| 'technical_alert5_message': alerts['technical_alert5']['message'], | |
| 'summary_message': alerts['summary']['message'], | |
| 'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| 'error_message': '' | |
| }) | |
| progress_messages.append(f"✅ {symbol}: 分析完成") | |
| except Exception as e: | |
| # 處理未預期的錯誤 | |
| results.append({ | |
| 'symbol': symbol, | |
| 'name': symbol, | |
| 'current_price': 'N/A', | |
| 'up_probability': 'ERROR', | |
| 'down_probability': 'ERROR', | |
| 'sideways_probability': 'ERROR', | |
| 'confidence': 'ERROR', | |
| 'trend_10day_icon': 'N/A', | |
| 'trend_10day_desc': '數據不足', | |
| 'high_90_icon': 'N/A', | |
| 'high_90_desc': '數據不足', | |
| 'news_alert': 'N/A', | |
| 'volume_alert1': 'N/A', | |
| 'institutional_alert1': 'N/A', | |
| 'institutional_alert2': 'N/A', | |
| 'technical_alert2': 'N/A', | |
| 'technical_alert3': 'N/A', | |
| 'technical_alert4': 'N/A', | |
| 'technical_alert5': 'N/A', | |
| 'summary': 'N/A', | |
| 'news_message': '', | |
| 'volume_alert1_message': '', | |
| 'institutional_alert1_message': '', | |
| 'institutional_alert2_message': '', | |
| 'technical_alert2_message': '', | |
| 'technical_alert3_message': '', | |
| 'technical_alert4_message': '', | |
| 'technical_alert5_message': '', | |
| 'summary_message': '', | |
| 'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| 'error_message': f'未預期錯誤: {str(e)}' | |
| }) | |
| progress_messages.append(f"❌ {symbol}: 未預期錯誤") | |
| # 統計結果 | |
| success_count = len([r for r in results if r['error_message'] == '']) | |
| error_count = len(results) - success_count | |
| summary_message = f""" | |
| 📈 批次分析完成! | |
| 📊 **分析統計:** | |
| - 總計股票數:{len(stock_symbols)} | |
| - 成功分析:{success_count} | |
| - 分析失敗:{error_count} | |
| 📊 **圖表已生成:** | |
| - 📊 機率比較柱狀圖 | |
| - 🎯 信心度散佈圖 | |
| - 📈 綜合評分雷達圖 | |
| - 🥧 市場情緒餅圖 | |
| 🎯 **排序規則:按(上漲機率×信心度)降序排列** - 由 Copilot 生成 | |
| 🎯 **請查看下方圖表和表格進行投資決策分析!** | |
| """ | |
| progress_log = "\n".join(progress_messages) | |
| # 按(上漲機率最高,信心度最高)排序結果 - 由 Copilot 生成 | |
| try: | |
| # 僅對成功分析的股票進行排序 | |
| valid_results = [r for r in results if r['error_message'] == ''] | |
| error_results = [r for r in results if r['error_message'] != ''] | |
| # 對有效結果按上漲機率×信心度排序(降序) | |
| def calculate_sort_key(result): | |
| try: | |
| up_prob = float(result['up_probability']) / 100.0 | |
| confidence = float(result['confidence']) / 100.0 | |
| return up_prob * confidence | |
| except: | |
| return 0 | |
| valid_results.sort(key=calculate_sort_key, reverse=True) | |
| # 重組結果:排序後的有效結果 + 錯誤結果 | |
| results = valid_results + error_results | |
| except Exception as e: | |
| print(f"排序過程中發生錯誤: {e}") | |
| # 創建圖表和結果表格 | |
| chart_bar, chart_scatter, chart_radar, chart_pie = create_batch_analysis_charts(results) | |
| results_table = create_results_table(results) | |
| return summary_message, progress_log, chart_bar, chart_scatter, chart_radar, chart_pie, results_table | |
| except Exception as e: | |
| return f"❌ 批次分析過程中發生錯誤:{str(e)}", "", None, None, None, None, "" | |
| def smart_recommend_stocks(stock_list_input): | |
| """智能推薦股票 - 挑選信心度最高的上漲和下跌股票 - 由 Copilot 生成""" | |
| # 檢查輸入是否為空 | |
| if not stock_list_input or not stock_list_input.strip(): | |
| return "❌ 請輸入股票代碼!可用逗號、空格或換行分隔多個股票代碼。", "", "" | |
| try: | |
| # 解析股票清單(支援多種分隔符) | |
| import re | |
| stock_symbols = re.split(r'[,\s\n]+', stock_list_input.strip()) | |
| stock_symbols = [symbol.strip().upper() for symbol in stock_symbols if symbol.strip()] | |
| if not stock_symbols: | |
| return "❌ 未找到有效的股票代碼!", "", "" | |
| # 準備結果列表 | |
| results = [] | |
| progress_messages = [] | |
| progress_messages.append(f"🤖 開始智能推薦分析 {len(stock_symbols)} 支股票...") | |
| # 分析每支股票 | |
| for i, symbol in enumerate(stock_symbols, 1): | |
| progress_messages.append(f"\n🔍 正在分析 ({i}/{len(stock_symbols)}): {symbol}") | |
| try: | |
| # 獲取股票數據 | |
| result = analyzer.fetch_stock_data(symbol.upper()) | |
| if len(result) == 3: | |
| success, message, stock_name = result | |
| else: | |
| success, message = result | |
| stock_name = symbol | |
| if not success: | |
| progress_messages.append(f"❌ {symbol}: {message}") | |
| continue | |
| # 計算技術指標 | |
| df = analyzer.calculate_technical_indicators() | |
| if df is None or len(df) < 30: | |
| progress_messages.append(f"❌ {symbol}: 數據不足") | |
| continue | |
| # 獲取新聞情感 | |
| news_sentiment = analyzer.get_news_sentiment(symbol) | |
| sentiment_summary = analyzer.analyze_sentiment_summary(news_sentiment) | |
| # 計算預測機率 | |
| recent_data = df.tail(20) | |
| technical_signals = [] | |
| # 簡化的技術信號計算 | |
| latest = df.iloc[-1] | |
| if latest['Close'] > latest['MA20']: | |
| technical_signals.append("價格在20日均線之上") | |
| else: | |
| technical_signals.append("價格在20日均線之下") | |
| probabilities = analyzer.calculate_prediction_probabilities( | |
| technical_signals, sentiment_summary, recent_data | |
| ) | |
| # 獲取股票資訊 | |
| stock_info = analyzer.get_stock_info(symbol) | |
| # 檢查 Alert | |
| alerts = analyzer.check_alerts(recent_data, symbol) | |
| # 計算近10日趨勢 | |
| trend_icon, trend_desc = analyzer.calculate_10day_trend(df) | |
| # 計算90日高點突破情況 - 由 Copilot 生成 | |
| high_90_icon, high_90_desc = analyzer.calculate_90day_high_breakthrough(df) | |
| # 計算綜合評分(機率 × 信心度) | |
| recommendation_score = max(probabilities['up'], probabilities['down']) * probabilities['confidence'] | |
| # 記錄成功結果 | |
| results.append({ | |
| 'symbol': symbol, | |
| 'name': stock_info['name'], | |
| 'current_price': f"{latest['Close']:.2f}" if latest['Close'] else 'N/A', | |
| 'up_probability': probabilities['up'], | |
| 'down_probability': probabilities['down'], | |
| 'sideways_probability': probabilities['sideways'], | |
| 'confidence': probabilities['confidence'], | |
| 'recommendation_score': recommendation_score, | |
| 'alerts': alerts, | |
| 'trend_icon': trend_icon, | |
| 'trend_desc': trend_desc, | |
| 'high_90_icon': high_90_icon, | |
| 'high_90_desc': high_90_desc, | |
| 'analysis_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
| }) | |
| progress_messages.append(f"✅ {symbol}: 分析完成") | |
| except Exception as e: | |
| progress_messages.append(f"❌ {symbol}: 未預期錯誤") | |
| if not results: | |
| return "❌ 所有股票分析都失敗了,請檢查股票代碼。", "\n".join(progress_messages), "" | |
| # 按上漲信心度和下跌信心度分別排序,取最高的10檔 - 由 Copilot 生成 | |
| all_stocks = results.copy() | |
| # 計算上漲信心度和下跌信心度 | |
| for stock in all_stocks: | |
| # 上漲信心度 = 上漲機率 × 總信心度 (當上漲機率大於下跌機率時) | |
| if stock['up_probability'] >= stock['down_probability']: | |
| stock['up_confidence_score'] = stock['up_probability'] * stock['confidence'] | |
| else: | |
| stock['up_confidence_score'] = 0 | |
| # 下跌信心度 = 下跌機率 × 總信心度 (當下跌機率大於上漲機率時) | |
| if stock['down_probability'] >= stock['up_probability']: | |
| stock['down_confidence_score'] = stock['down_probability'] * stock['confidence'] | |
| else: | |
| stock['down_confidence_score'] = 0 | |
| # 按上漲信心度排序,取前10檔 | |
| up_sorted_stocks = [stock for stock in all_stocks if stock['up_confidence_score'] > 0] | |
| up_sorted_stocks.sort(key=lambda x: x['up_confidence_score'], reverse=True) | |
| top_10_up_confidence = up_sorted_stocks[:10] | |
| # 按下跌信心度排序,取前10檔 | |
| down_sorted_stocks = [stock for stock in all_stocks if stock['down_confidence_score'] > 0] | |
| down_sorted_stocks.sort(key=lambda x: x['down_confidence_score'], reverse=True) | |
| top_10_down_confidence = down_sorted_stocks[:10] | |
| progress_messages.append(f"\n🎯 推薦完成!") | |
| progress_messages.append(f"🚀 上漲信心度最高:{len(top_10_up_confidence)} 支") | |
| progress_messages.append(f"📉 下跌信心度最高:{len(top_10_down_confidence)} 支") | |
| progress_log = "\n".join(progress_messages) | |
| # 生成推薦報告 | |
| recommendation_html = create_recommendation_table(top_10_up_confidence, top_10_down_confidence) | |
| summary_message = f""" | |
| 🤖 **智能推薦完成!** | |
| 📊 **分析統計:** | |
| - 總計分析:{len(stock_symbols)} 支股票 | |
| - 成功分析:{len(results)} 支股票 | |
| - 上漲信心度最高:{len(top_10_up_confidence)} 支股票 | |
| - 下跌信心度最高:{len(top_10_down_confidence)} 支股票 | |
| 🎯 **推薦標準:** | |
| - 上漲信心度 = 上漲機率 × 信心度(當上漲機率≥下跌機率時) | |
| - 下跌信心度 = 下跌機率 × 信心度(當下跌機率≥上漲機率時) | |
| - 包含近10日趨勢(漲跌)分析 | |
| - 特別標示:量能1、籌碼1、技術2~4皆Y的股票 | |
| 💡 **下方顯示詳細推薦結果和Alert狀態!** | |
| """ | |
| return summary_message, progress_log, recommendation_html | |
| except Exception as e: | |
| return f"❌ 智能推薦過程中發生錯誤:{str(e)}", "", "" | |
| def create_recommendation_table(up_confidence_stocks, down_confidence_stocks): | |
| """創建推薦結果表格 - 按上漲信心度和下跌信心度排序 - 由 Copilot 生成""" | |
| def check_special_alert(alerts): | |
| """檢查是否量能1、籌碼1、技術2~4皆Y - 由 Copilot 生成""" | |
| return (alerts['volume_alert1']['status'] == 'Y' and | |
| alerts['institutional_alert1']['status'] == 'Y' and | |
| alerts['technical_alert2']['status'] == 'Y' and | |
| alerts['technical_alert3']['status'] == 'Y' and | |
| alerts['technical_alert4']['status'] == 'Y') | |
| def generate_prediction_reason(stock): | |
| """生成預測主要原因 - 由 Copilot 生成""" | |
| alerts = stock['alerts'] | |
| up_prob = stock['up_probability'] | |
| down_prob = stock['down_probability'] | |
| confidence = stock['confidence'] | |
| score = stock.get('recommendation_score', 0) | |
| # 統計Y的數量 | |
| y_count = sum(1 for key in ['volume_alert1', 'institutional_alert1', 'technical_alert2', | |
| 'technical_alert3', 'technical_alert4'] | |
| if alerts[key]['status'] == 'Y') | |
| reasons = [] | |
| # 根據綜合評分高低分析原因 | |
| if score >= 50: # 高評分 | |
| if up_prob > down_prob: | |
| reasons.append(f"看多信號強({up_prob:.0f}%>{ down_prob:.0f}%)") | |
| if confidence >= 0.7: | |
| reasons.append(f"高信心度({confidence*100:.0f}%)") | |
| if y_count >= 4: | |
| reasons.append(f"多項指標達標({y_count}/5)") | |
| elif y_count >= 2: | |
| reasons.append(f"部分指標達標({y_count}/5)") | |
| elif score <= 30: # 低評分 | |
| if down_prob > up_prob: | |
| reasons.append(f"看空信號({down_prob:.0f}%>{ up_prob:.0f}%)") | |
| if confidence <= 0.4: | |
| reasons.append(f"信心度偏低({confidence*100:.0f}%)") | |
| if y_count <= 1: | |
| reasons.append(f"指標不佳({y_count}/5)") | |
| else: | |
| reasons.append(f"指標混合({y_count}/5)") | |
| else: # 中等評分 | |
| if abs(up_prob - down_prob) < 10: | |
| reasons.append("上下機率接近") | |
| if up_prob > down_prob: | |
| reasons.append(f"微偏看多({up_prob:.0f}%)") | |
| else: | |
| reasons.append(f"微偏看空({down_prob:.0f}%)") | |
| reasons.append(f"中等信心度({confidence*100:.0f}%)") | |
| # 特別情況 | |
| if check_special_alert(alerts): | |
| reasons.append("⭐全指標達標") | |
| # 趨勢影響 | |
| trend_icon = stock.get('trend_icon', '') | |
| if '📈' in trend_icon or 'UP' in trend_icon: | |
| reasons.append("近期走勢上升") | |
| elif '📉' in trend_icon or 'DOWN' in trend_icon: | |
| reasons.append("近期走勢下降") | |
| return ";".join(reasons[:3]) # 最多顯示3個原因 | |
| html = """ | |
| <div style="margin: 20px 0;"> | |
| <h3 style="color: #2c3e50; margin-bottom: 15px;">🤖 AI 智能推薦結果</h3> | |
| """ | |
| # 上漲信心度最高表格 | |
| if up_confidence_stocks: | |
| html += """ | |
| <h4 style="color: #27ae60; margin: 20px 0 10px 0;">🚀 上漲信心度最高 (Top 10)</h4> | |
| <div style="overflow-x: auto;"> | |
| <table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; margin-bottom: 20px;"> | |
| <thead> | |
| <tr style="background-color: #e8f5e8;"> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">排名</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">中文名稱</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">股票代號</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">當前價格</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 12px;">近10日趨勢</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">90日高點突破</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">上漲機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">下跌機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">信心度(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">上漲信心度</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 10px;">主要原因</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">量能1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">籌碼1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術2</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術3</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術4</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| """ | |
| for i, stock in enumerate(up_confidence_stocks, 1): | |
| alerts = stock['alerts'] | |
| chinese_name = get_chinese_name(stock['symbol']) | |
| # 檢查特殊標示 | |
| is_special = check_special_alert(alerts) | |
| row_style = "background-color: #fff9c4; border: 2px solid #f39c12;" if is_special else "background-color: #f0fff0;" | |
| special_marker = "⭐" if is_special else "" | |
| # Alert 狀態顯示 | |
| alert_displays = {} | |
| for alert_key in ['volume_alert1', 'institutional_alert1', 'technical_alert2', | |
| 'technical_alert3', 'technical_alert4']: | |
| status = alerts[alert_key]['status'] | |
| alert_displays[alert_key] = f"{'🟢' if status == 'Y' else '🔴'} {status}" | |
| # 計算上漲信心度評分和生成主要原因 | |
| confidence_score = stock.get('up_confidence_score', 0) | |
| main_reason = generate_prediction_reason(stock) | |
| html += f""" | |
| <tr style="{row_style}"> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-weight: bold; color: #27ae60;">#{i} {special_marker}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 12px; color: #2c3e50;">{chinese_name}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 11px; color: #7f8c8d;">{stock['symbol']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{stock['current_price']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{stock['trend_icon']} {stock['trend_desc']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{stock['high_90_icon']} {stock['high_90_desc']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #27ae60;">{stock['up_probability']:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #e74c3c;">{stock['down_probability']:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #3498db;">{stock['confidence']*100:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #e67e22;">{confidence_score:.1f}</td> | |
| <td style="border: 1px solid #ddd; padding: 4px; text-align: left; font-size: 9px; color: #34495e;" title="{main_reason}">{main_reason}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['volume_alert1']['message']}">{alert_displays['volume_alert1']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['institutional_alert1']['message']}">{alert_displays['institutional_alert1']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert2']['message']}">{alert_displays['technical_alert2']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert3']['message']}">{alert_displays['technical_alert3']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert4']['message']}">{alert_displays['technical_alert4']}</td> | |
| </tr>""" | |
| html += """ | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| # 下跌信心度最高表格 | |
| if down_confidence_stocks: | |
| html += """ | |
| <h4 style="color: #e74c3c; margin: 20px 0 10px 0;">📉 下跌信心度最高 (Top 10)</h4> | |
| <div style="overflow-x: auto;"> | |
| <table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; margin-bottom: 20px;"> | |
| <thead> | |
| <tr style="background-color: #fde8e8;"> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">排名</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">中文名稱</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 12px;">股票代號</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">當前價格</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 12px;">近10日趨勢</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">90日高點突破</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">上漲機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">下跌機率(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 11px;">信心度(%)</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: right; font-size: 12px;">下跌信心度</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 10px;">主要原因</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">量能1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">籌碼1</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術2</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術3</th> | |
| <th style="border: 1px solid #ddd; padding: 8px; text-align: center; font-size: 11px;">技術4</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| """ | |
| for i, stock in enumerate(down_confidence_stocks, 1): | |
| alerts = stock['alerts'] | |
| chinese_name = get_chinese_name(stock['symbol']) | |
| # 檢查特殊標示 | |
| is_special = check_special_alert(alerts) | |
| row_style = "background-color: #fff9c4; border: 2px solid #f39c12;" if is_special else "background-color: #fff0f0;" | |
| special_marker = "⭐" if is_special else "" | |
| # Alert 狀態顯示 | |
| alert_displays = {} | |
| for alert_key in ['volume_alert1', 'institutional_alert1', 'technical_alert2', | |
| 'technical_alert3', 'technical_alert4']: | |
| status = alerts[alert_key]['status'] | |
| alert_displays[alert_key] = f"{'🟢' if status == 'Y' else '🔴'} {status}" | |
| # 計算下跌信心度評分和生成主要原因 | |
| confidence_score = stock.get('down_confidence_score', 0) | |
| main_reason = generate_prediction_reason(stock) | |
| html += f""" | |
| <tr style="{row_style}"> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-weight: bold; color: #e74c3c;">#{i} {special_marker}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 12px; color: #2c3e50;">{chinese_name}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; font-weight: bold; font-size: 11px; color: #7f8c8d;">{stock['symbol']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px;">{stock['current_price']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 11px;">{stock['trend_icon']} {stock['trend_desc']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{stock['high_90_desc']}">{stock['high_90_icon']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #27ae60;">{stock['up_probability']:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #e74c3c;">{stock['down_probability']:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #3498db;">{stock['confidence']*100:.1f}%</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 11px; font-weight: bold; color: #e67e22;">{confidence_score:.1f}</td> | |
| <td style="border: 1px solid #ddd; padding: 4px; text-align: left; font-size: 9px; color: #34495e;" title="{main_reason}">{main_reason}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['volume_alert1']['message']}">{alert_displays['volume_alert1']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['institutional_alert1']['message']}">{alert_displays['institutional_alert1']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert2']['message']}">{alert_displays['technical_alert2']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert3']['message']}">{alert_displays['technical_alert3']}</td> | |
| <td style="border: 1px solid #ddd; padding: 6px; text-align: center; font-size: 10px;" title="{alerts['technical_alert4']['message']}">{alert_displays['technical_alert4']}</td> | |
| </tr>""" | |
| html += """ | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| html += """ | |
| <div style="margin-top: 15px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;"> | |
| <p style="font-size: 11px; color: #666; margin: 0;"> | |
| <strong>📊 智能推薦說明:</strong><br> | |
| • <strong>上漲機率</strong>:AI預測股價上漲的可能性百分比<br> | |
| • <strong>下跌機率</strong>:AI預測股價下跌的可能性百分比<br> | |
| • <strong>信心度</strong>:AI對此預測的信心程度,數值越高越可信<br> | |
| • <strong>信心度評分</strong>:上漲信心度 = 上漲機率 × 信心度;下跌信心度 = 下跌機率 × 信心度<br> | |
| • <strong>主要原因</strong>:解釋評分高低的關鍵因素和預測依據<br> | |
| • <strong>近10日趨勢</strong>:📈↗ 上漲 | 📉↘ 下跌 | ➡️↔ 震盪<br> | |
| • <strong>90日高點突破</strong>:🚀 突破90日高點 | 📊 距高點差距百分比<br> | |
| • <strong>特別標示 ⭐</strong>:量能1、籌碼1、技術2~4皆為Y的股票<br> | |
| • <strong>Alert 燈號</strong>:🟢 = 達到條件 (Y) | 🔴 = 未達條件 (N)<br> | |
| <em>註:此推薦僅供參考,請結合個人投資策略謹慎決策 - 由 Copilot 生成</em> | |
| </p> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| # 創建 Gradio 界面 | |
| with gr.Blocks(title="AI 股票分析師", theme=gr.themes.Soft()) as app: | |
| gr.Markdown( | |
| """ | |
| # 📈 AI 股票分析師 | |
| ### 🤖 使用 Hugging Face 模型進行智能股票分析 | |
| **✨ 核心功能:** | |
| - 📊 **完整技術指標**:MA、RSI、MACD、布林通道分析 | |
| - 🧠 **AI 情感分析**:使用 FinBERT 模型分析市場情緒 | |
| - 🎯 **機率預測**:提供上漲/下跌/盤整機率百分比 | |
| - 🚨 **十項 Alert 提醒**:新聞、量能×2、籌碼×2、技術×5、總結 細分警示 | |
| - 💼 **買賣建議**:針對持股/未持股提供具體投資建議 | |
| - 📈 **智能策略**:根據信心度給出個性化投資策略 | |
| - 🎚️ **倉位建議**:提供合理的倉位配置建議 | |
| - 🖼️ **互動圖表**:動態視覺化技術指標走勢 | |
| - 📁 **批次分析**:一次分析多支股票並匯出詳細報告 | |
| **🚀 使用方法:** 單支分析輸入股票代碼,批次分析直接在文本框中輸入多個股票代碼即可! | |
| **💡 新功能亮點:** 現在提供差異化的買賣建議 - 持有股票時建議觀望/賣出/持有,未持有時建議加碼買進/觀望! | |
| """ | |
| ) | |
| # 建立分頁 | |
| with gr.Tabs(): | |
| with gr.TabItem("🎯 單支股票分析"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| stock_input = gr.Textbox( | |
| label="股票代碼", | |
| placeholder="例如:AAPL, TSLA, 2330.TW", | |
| value="2330.TW" | |
| ) | |
| analyze_btn = gr.Button("開始分析", variant="primary", size="lg") | |
| status_output = gr.Textbox( | |
| label="分析狀態", | |
| lines=2, | |
| interactive=False | |
| ) | |
| with gr.Column(scale=2): | |
| chart_output = gr.Plot(label="股價走勢圖") | |
| prediction_output = gr.Markdown(label="AI 分析報告") | |
| # 事件綁定 | |
| analyze_btn.click( | |
| fn=analyze_stock, | |
| inputs=[stock_input], | |
| outputs=[chart_output, status_output, prediction_output] | |
| ) | |
| # 範例按鈕 | |
| gr.Examples( | |
| examples=[ | |
| ["AAPL"], | |
| ["TSLA"], | |
| ["2330.TW"], | |
| ["MSFT"], | |
| ["GOOGL"] | |
| ], | |
| inputs=[stock_input] | |
| ) | |
| with gr.TabItem("📊 批次股票分析"): | |
| gr.Markdown( | |
| """ | |
| ### 📁 批次分析功能 | |
| **📋 使用步驟:** | |
| 1. 在下方文本框中輸入股票代碼 | |
| 2. 支援多種分隔方式:逗號、空格或換行 | |
| 3. 例如:`AAPL, TSLA, 2330.TW` 或 `AAPL MSFT GOOGL` | |
| 4. 點擊「開始批次分析」按鈕查看結果 | |
| **📈 輸出內容:** | |
| - 📊 機率比較柱狀圖:直觀對比各股票預測機率 | |
| - 🎯 信心度散佈圖:顯示預測可靠性分佈 | |
| - 📈 綜合評分雷達圖:多維度股票評分比較 | |
| - 🥧 市場情緒餅圖:整體多空情緒統計 | |
| - 📈 **近10日趨勢**:顯示股價短期漲跌情況 - 由 Copilot 生成 | |
| - 🎯 **智能排序**:按(上漲機率×信心度)降序排列 - 由 Copilot 生成 | |
| - 🚨 **十項 Alert 提醒**:新聞、量能×2、籌碼×2、技術×5、總結等全面警示 | |
| - �💼 **買賣建議表格**:針對持股與非持股提供具體建議 | |
| - 📊 **詳細分析表格**:包含所有機率、信心度與投資建議 | |
| - 即時進度顯示和完整錯誤處理 | |
| **💡 買賣建議特色:** | |
| - 🏠 **持股建議**:持有、賣出、觀望等具體行動 | |
| - 💰 **買進建議**:積極買進、適度買進、暫緩等建議 | |
| - 🎚️ **倉位建議**:根據信心度提供合理倉位配置 | |
| - ⚠️ **風險警示**:超買超賣等特殊情況提醒 | |
| """ | |
| ) | |
| # 股票代碼輸入區域 | |
| stock_list_input = gr.Textbox( | |
| label="📝 輸入股票代碼", | |
| placeholder="例如:AAPL, TSLA, 2330.TW, MSFT, GOOGL\n或用空格、換行分隔", | |
| lines=3, | |
| value="AAPL, TSLA, 2330.TW, MSFT, GOOGL", | |
| info="支援逗號、空格或換行分隔多個股票代碼" | |
| ) | |
| # 範例按鈕 | |
| gr.Examples( | |
| examples=[ | |
| ["AAPL, MSFT, GOOGL, AMZN, TSLA"], | |
| ["2330.TW, 2317.TW, 2454.TW, 3711.TW, 2382.TW"], | |
| ["JPM, BAC, WFC, C, GS"], | |
| ["JNJ, PFE, ABBV, UNH, CVS"], | |
| ["BTC-USD, ETH-USD, ^GSPC, ^DJI, ^IXIC"] | |
| ], | |
| inputs=[stock_list_input], | |
| label="💡 快速範例" | |
| ) | |
| with gr.Row(): | |
| batch_analyze_btn = gr.Button( | |
| "🚀 開始批次分析", | |
| variant="primary", | |
| size="lg", | |
| scale=1 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| batch_summary = gr.Markdown(label="📊 分析摘要") | |
| with gr.Column(scale=1): | |
| batch_progress = gr.Textbox( | |
| label="📋 分析進度", | |
| lines=10, | |
| interactive=False, | |
| max_lines=15 | |
| ) | |
| # 圖表顯示區域 | |
| gr.Markdown("## 📈 視覺化分析結果") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| chart_probability = gr.Plot(label="📊 股票預測機率比較") | |
| with gr.Column(scale=1): | |
| chart_confidence = gr.Plot(label="🎯 預測信心度分佈") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| chart_radar = gr.Plot(label="📈 綜合評分雷達圖") | |
| with gr.Column(scale=1): | |
| chart_sentiment = gr.Plot(label="🥧 整體市場情緒分佈") | |
| # 詳細結果表格 | |
| gr.Markdown("## 📋 詳細分析結果") | |
| results_table = gr.HTML(label="分析結果表格") | |
| # 批次分析事件綁定 | |
| batch_analyze_btn.click( | |
| fn=batch_analyze_stocks, | |
| inputs=[stock_list_input], | |
| outputs=[ | |
| batch_summary, | |
| batch_progress, | |
| chart_probability, | |
| chart_confidence, | |
| chart_radar, | |
| chart_sentiment, | |
| results_table | |
| ] | |
| ) | |
| with gr.TabItem("🤖 智能推薦"): | |
| gr.Markdown( | |
| """ | |
| ### 🤖 AI 智能推薦功能 | |
| **🎯 推薦原理:** | |
| - 基於 **綜合評分** = 預測機率 × 信心度 | |
| - 自動篩選出上漲和下跌趨勢最明確的股票 | |
| - 按評分高低排序,推薦最有潜力的前5名股票 | |
| **📊 推薦內容:** | |
| - 📈 **看多推薦**:上漲機率最高且信心度最強的股票 | |
| - 📉 **看空推薦**:下跌機率最高且信心度最強的股票 | |
| - 🚨 **完整Alert**:顯示所有8項Alert燈號狀態 | |
| - 💯 **綜合評分**:客觀量化的推薦強度指標 | |
| **💡 使用方式:** | |
| 1. 在下方輸入想要分析的股票代碼清單 | |
| 2. 系統會自動分析所有股票 | |
| 3. 智能挑選出最值得關注的看多/看空標的 | |
| 4. 提供詳細的機率、信心度和Alert狀態 | |
| **⚡ 特色優勢:** | |
| - 🎯 **精準篩選**:只顯示趨勢明確的高潛力股票 | |
| - 📊 **量化排序**:客觀的綜合評分排名系統 | |
| - 🚨 **全面警示**:8項Alert完整風險評估 | |
| - 💡 **簡化決策**:直接獲得最值得關注的標的 | |
| """ | |
| ) | |
| # 股票代碼輸入區域 | |
| smart_stock_input = gr.Textbox( | |
| label="📝 輸入分析股票代碼", | |
| placeholder="例如:AAPL, TSLA, 2330.TW, MSFT, GOOGL, AMZN, NVDA, META\n或用空格、換行分隔", | |
| lines=4, | |
| value="AAPL, TSLA, 2330.TW, MSFT, GOOGL, AMZN, NVDA, META, 2317.TW, 2454.TW", | |
| info="建議輸入10-20支股票以獲得更好的推薦效果" | |
| ) | |
| # 範例按鈕 | |
| gr.Examples( | |
| examples=[ | |
| ["AAPL, MSFT, GOOGL, AMZN, TSLA, NVDA, META, NFLX, AMD, INTC"], | |
| ["2330.TW, 2317.TW, 2454.TW, 2382.TW, 2303.TW, 2881.TW, 2883.TW, 2886.TW, 2891.TW, 2892.TW"], | |
| ["JPM, BAC, WFC, C, GS, MS, BLK, AXP, COF, SCHW"], | |
| ["JNJ, PFE, ABBV, UNH, CVS, MRK, BMY, GILD, AMGN, LLY"], | |
| ["BTC-USD, ETH-USD, ^GSPC, ^DJI, ^IXIC, GLD, TLT, VTI, QQQ, SPY"], | |
| ["BABA, JD, PDD, BIDU, NIO, XPEV, LI, TME, BILI, IQ"] | |
| ], | |
| inputs=[smart_stock_input], | |
| label="💡 快速範例(不同類型股票組合)" | |
| ) | |
| with gr.Row(): | |
| smart_recommend_btn = gr.Button( | |
| "🤖 開始智能推薦", | |
| variant="primary", | |
| size="lg", | |
| scale=1 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| smart_summary = gr.Markdown(label="📊 推薦摘要") | |
| with gr.Column(scale=1): | |
| smart_progress = gr.Textbox( | |
| label="📋 分析進度", | |
| lines=10, | |
| interactive=False, | |
| max_lines=15 | |
| ) | |
| # 推薦結果顯示區域 | |
| gr.Markdown("## 🏆 智能推薦結果") | |
| smart_results_table = gr.HTML(label="智能推薦表格") | |
| # 智能推薦事件綁定 | |
| smart_recommend_btn.click( | |
| fn=smart_recommend_stocks, | |
| inputs=[smart_stock_input], | |
| outputs=[ | |
| smart_summary, | |
| smart_progress, | |
| smart_results_table | |
| ] | |
| ) | |
| # 啟動應用 | |
| if __name__ == "__main__": | |
| print("正在啟動 AI 股票分析師...") | |
| # 簡化的啟動邏輯 | |
| try: | |
| if IS_HUGGINGFACE_SPACE: | |
| # Hugging Face Spaces 環境 - 使用預設配置 | |
| print("在 Hugging Face Spaces 中啟動...") | |
| app.launch() | |
| else: | |
| # 本地環境 - 嘗試多個端口 | |
| print("在本地環境中啟動...") | |
| ports_to_try = [7860, 7861, 7862, 7863, 7864, 7865] | |
| launched = False | |
| for port in ports_to_try: | |
| try: | |
| print(f"嘗試端口 {port}...") | |
| app.launch( | |
| share=True, | |
| server_name="0.0.0.0", | |
| server_port=port, | |
| show_error=True, | |
| quiet=False | |
| ) | |
| launched = True | |
| break | |
| except OSError as e: | |
| if "port" in str(e).lower(): | |
| print(f"端口 {port} 不可用,嘗試下一個...") | |
| continue | |
| else: | |
| raise e | |
| if not launched: | |
| print("所有預設端口都被佔用,使用隨機端口...") | |
| app.launch( | |
| share=True, | |
| server_name="0.0.0.0", | |
| server_port=0, # 0 表示自動分配端口 | |
| show_error=True | |
| ) | |
| except Exception as e: | |
| print(f"啟動失敗: {e}") | |
| print("請檢查端口使用情況或嘗試重新啟動") | |
| raise e | |