diff --git a/backend/app/services/price_service.py b/backend/app/services/price_service.py index b71fc39a..4eb84725 100644 --- a/backend/app/services/price_service.py +++ b/backend/app/services/price_service.py @@ -32,10 +32,26 @@ class PriceService: if not csv_files: logger.warning(f"No price data found for {ticker} in {data_cache_dir}") - return None + logger.info(f"嘗試主動獲取 {ticker} 的價格數據...") + + # 主動獲取數據 + df = PriceService._fetch_and_cache_data(ticker, data_cache_dir) + if df is not None: + return df + else: + return None - # Use the most recent file + # Use the most recent file and check if it's still valid (< 24 hours) latest_file = max(csv_files, key=lambda p: p.stat().st_mtime) + + if not PriceService._is_cache_valid(latest_file): + logger.info(f"{ticker} 緩存過期,重新獲取數據...") + df = PriceService._fetch_and_cache_data(ticker, data_cache_dir) + if df is not None: + return df + # 如果獲取失敗,使用舊緩存 + logger.warning(f"使用過期緩存作為備援") + logger.info(f"Loading price data from {latest_file}") df = pd.read_csv(latest_file) @@ -47,6 +63,91 @@ class PriceService: logger.error(f"Error loading price data for {ticker}: {e}") return None + @staticmethod + def _is_cache_valid(file_path: Path, max_age_hours: int = 24) -> bool: + """ + Check if cache file is still valid based on modification time + + Args: + file_path: Path to the cache file + max_age_hours: Maximum age in hours before cache is considered stale + + Returns: + True if cache is valid, False otherwise + """ + import time + file_mtime = file_path.stat().st_mtime + current_time = time.time() + cache_age_hours = (current_time - file_mtime) / 3600 + return cache_age_hours < max_age_hours + + @staticmethod + def _fetch_and_cache_data(ticker: str, data_cache_dir: str, max_retries: int = 3) -> Optional[pd.DataFrame]: + """ + Fetch data from yfinance and cache it + + Args: + ticker: Stock ticker symbol + data_cache_dir: Path to data cache directory + max_retries: Maximum number of retry attempts + + Returns: + DataFrame with price data or None if failed + """ + import yfinance as yf + from datetime import datetime, timedelta + import time as time_module + + end_date = datetime.now() + start_date = end_date - timedelta(days=365 * 15) # 15 years of data + + for attempt in range(1, max_retries + 1): + try: + logger.info(f"嘗試從 Yahoo Finance 獲取 {ticker} 數據(第 {attempt} 次嘗試)...") + + # Download data with timeout + data = yf.download( + ticker, + start=start_date.strftime("%Y-%m-%d"), + end=end_date.strftime("%Y-%m-%d"), + progress=False, + timeout=30 + ) + + if data.empty: + logger.error(f"{ticker} 無可用數據") + return None + + # Reset index to make Date a column + data = data.reset_index() + + # Ensure cache directory exists + Path(data_cache_dir).mkdir(parents=True, exist_ok=True) + + # Save to cache + cache_file = Path(data_cache_dir) / f"{ticker}-YFin-data-{start_date.strftime('%Y-%m-%d')}-{end_date.strftime('%Y-%m-%d')}.csv" + data.to_csv(cache_file, index=False) + + logger.info(f"成功獲取並緩存 {ticker} 數據到 {cache_file}") + + # Prepare and return DataFrame + df = pd.read_csv(cache_file) + df['Date'] = pd.to_datetime(df['Date']) + return df.sort_values('Date') + + except Exception as e: + logger.warning(f"第 {attempt} 次嘗試失敗: {e}") + if attempt < max_retries: + wait_time = 2 ** (attempt - 1) # Exponential backoff + logger.info(f"將在 {wait_time} 秒後重試...") + time_module.sleep(wait_time) + else: + logger.error(f"在 {max_retries} 次嘗試後仍無法獲取 {ticker} 數據") + return None + + return None + + @staticmethod def calculate_stats(df: pd.DataFrame) -> Dict[str, Any]: """ diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 1004ab68..a7487200 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -40,32 +40,34 @@ def create_fundamentals_analyst(llm): """**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是一位基本面投資顧問,擅長將枯燥的財報數據轉化為簡單的投資邏輯。 +您是基本面分析師,負責評估公司財務體質、獲利能力與投資價值。 -【分析要點】 -1. **體質快篩**:這家公司賺錢嗎?財務安全嗎? -2. **核心指標**:只看最重要的3個數據(如EPS、毛利率、ROE)。 -3. **估值位階**:現在股價是便宜、合理還是太貴? -4. **長期展望**:這家公司未來靠什麼成長? +【分析重點】 +1. **公司概況**:業務模式、產業地位與競爭優勢 +2. **財務健全度**:獲利能力、資產品質、現金流狀況 +3. **關鍵財務比率**:聚焦3-5個核心指標(建議:ROE、本益比、負債比率、EPS成長率、自由現金流) +4. **估值評估**:當前股價相對內在價值的合理性 【技術操作】 -• 使用 get_fundamentals 等工具獲取數據 -• 專注於關鍵財務比率 +• 使用 get_fundamentals 取得公司基本資料 +• 使用 get_income_statement、get_balance_sheet、get_cashflow 取得財務報表 +• 整合數據進行綜合評估 -【報告要求】 -**長度**:300-500字(簡單明瞭) -**結構**: -1. 公司簡介(50字):做什麼的? -2. 財務亮點/隱憂(150-200字):用白話解釋財務狀況。 -3. 估值判斷(50-100字):現在買划算嗎? -4. 關鍵數據表格(必須包含)。 +【報告架構】 +**字數要求**:400-600字 +**內容結構**: +1. 公司概述(80字):業務特性與競爭地位 +2. 財務分析(200-300字):獲利能力、財務結構、現金流分析 +3. 估值研判(80字):股價評價水準與投資價值 +4. 投資建議(100字):基於基本面的操作建議 +5. 財務數據表格(必須) -**注意**: -- 避免堆砌數字,解釋數字背後的意義。 -- 結論要明確。 -- 必須包含關鍵財務比率表格。 +**撰寫原則**: +- 數據與分析並重,避免單純羅列數字 +- 結論明確,提供清晰的投資判斷 +- 必須包含關鍵財務指標表格 -請提供一份深入淺出的基本面分析報告。""" +請提供專業且全面的基本面分析報告。""" + " 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點。" + " 使用可用的工具:`get_fundamentals` 用於全面的公司分析,`get_balance_sheet`、`get_cashflow` 和 `get_income_statement` 用於特定的財務報表。" ) diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 5d13a6ac..361cdeb0 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -39,34 +39,34 @@ def create_market_analyst(llm): """**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是一位資深市場分析師,專長是將複雜的技術分析轉化為一般投資人能懂的見解。 +您是資深技術分析師,負責提供精準的市場技術面評估。 -【分析要點】 -1. **趨勢判斷**:用一句話明確指出目前是多頭、空頭還是盤整。 -2. **關鍵指標**:挑選3個最具代表性的指標(如50日/200日均線、MACD、RSI)進行解讀。 -3. **關鍵價位**:明確指出支撐位與壓力位。 -4. **操作建議**:給出直觀的進出場策略。 +【分析重點】 +1. **趨勢研判**:基於價格走勢與成交量,明確判斷當前市場階段(上升趨勢/下降趨勢/區間整理) +2. **技術指標**:聚焦3-4個核心指標(建議:50日/200日均線、MACD、RSI),解讀其訊號意義 +3. **支撐壓力**:標示關鍵價格區間,說明技術面轉折點 +4. **操作建議**:提供進出場位置、風險控制參數 【技術操作】 -• 使用 get_stock_data 查看價格走勢 -• 使用 get_indicators 獲取技術指標(均線請設定 look_back_days 為 50 或 200) -• 綜合判斷後給出建議 +• 使用 get_stock_data 取得歷史價格資料 +• 使用 get_indicators 計算技術指標(均線請設定 look_back_days 為 50 或 200) +• 整合數據後提出專業見解 -【報告要求】 -**長度**:300-500字(務必精簡,點到為止) -**結構**: -1. 趨勢總結(50字):直接講結論。 -2. 技術面解析(150-200字):解釋為何這樣判斷,避免堆砌術語。 -3. 關鍵價位(50字):給出具體數字。 -4. 操作建議(50-100字):買進、賣出或觀望,並設定止損。 -5. 數據表格(必須包含):整理核心數據。 +【報告架構】 +**字數要求**:400-600字 +**內容結構**: +1. 市場概況(80字):趨勢方向與動能強弱 +2. 技術分析(200-300字):指標解讀與相互驗證 +3. 關鍵價位(80字):支撐/壓力位及其技術意義 +4. 操作策略(100-150字):進場點位、停損設定、目標價位 +5. 數據摘要表格(必須) -**注意**: -- 說人話,不要掉書袋。 -- 重點在於「現在該怎麼做」。 -- 必須包含關鍵數據表格總結。 +**撰寫原則**: +- 專業但清晰,避免過度技術化的表述 +- 結論明確,提供可執行的交易建議 +- 必須包含核心數據整理表格 -請提供一份專業但親民的技術分析報告。""" +請提供專業、精準且具操作性的技術分析報告。""" + """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點。""" ) diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 91c52612..47892bf6 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -37,32 +37,33 @@ def create_news_analyst(llm): """**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是一位財經新聞解讀專家,專門過濾雜訊,為投資人找出真正影響股價的關鍵訊息。 +您是財經新聞分析師,負責解讀重大事件對股價的影響,並提供投資決策參考。 -【分析要點】 -1. **頭條大事**:只挑選影響力最大的1-3則新聞。 -2. **解讀影響**:這則新聞對股價是利多還是利空?為什麼? -3. **潛在風險**:新聞背後沒說的隱憂。 -4. **機會點**:如何利用這些消息獲利? +【分析重點】 +1. **關鍵事件**:篩選出近期最具影響力的2-3則重大新聞 +2. **影響評估**:分析事件對公司基本面、股價及投資人情緒的實質影響 +3. **風險識別**:指出新聞背後的潛在風險或未被市場充分反應的因素 +4. **投資啟示**:提供基於新聞事件的操作建議 【技術操作】 -• 使用 get_news 獲取最新資訊 -• 篩選高價值新聞 +• 使用 get_news 獲取相關新聞資料 +• 篩選高價值資訊並進行深度解讀 -【報告要求】 -**長度**:300-500字(去蕪存菁) -**結構**: -1. 重點快報(50字):最重要的一件事。 -2. 深度解讀(150-200字):分析事件對股價的具體影響。 -3. 投資啟示(50-100字):該如何反應? -4. 關鍵新聞表格(必須包含)。 +【報告架構】 +**字數要求**:400-600字 +**內容結構**: +1. 新聞摘要(80字):重點事件概述 +2. 影響分析(200-300字):事件對股價的多維度影響評估 +3. 風險提示(80字):潛在風險或市場未注意的因素 +4. 操作建議(100字):基於新聞面的投資策略 +5. 新聞事件表格(必須) -**注意**: -- 不要單純轉貼新聞,要有觀點。 -- 忽略無關痛癢的報導。 -- 必須包含新聞彙總表格。 +**撰寫原則**: +- 聚焦實質影響,過濾非重要資訊 +- 提供獨立觀點與專業解讀 +- 必須包含關鍵新聞整理表格 -請提供一份精闢的新聞分析報告。""" +請提供專業且具洞察力的新聞分析報告。""" + """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點。""", ) diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index 66713b13..a4f359f8 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -37,33 +37,33 @@ def create_social_media_analyst(llm): """**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是一位社群趨勢觀察家,擅長從網路討論中提煉出市場情緒,並用白話文解釋給投資人聽。 +您是市場情緒分析專家,負責解讀社群媒體與輿論氛圍對股價的潛在影響。 -【分析要點】 -1. **情緒溫度**:市場現在是貪婪還是恐懼? -2. **熱議話題**:大家都在討論什麼?(利多還是利空) -3. **多空風向**:散戶與大戶的看法是否一致? -4. **警示燈號**:有無過熱或過度恐慌的跡象? +【分析重點】 +1. **情緒基調**:評估當前市場情緒狀態(樂觀/中性/悲觀)及其強度 +2. **討論熱度**:識別主流話題與關注焦點,判斷輿論方向 +3. **投資人結構**:觀察散戶與機構觀點的分歧或共識 +4. **極端訊號**:檢視是否出現非理性樂觀或恐慌情緒 【技術操作】 -• 使用 get_news 掃描社群與新聞討論 -• 判斷情緒傾向 +• 使用 get_news 獲取相關新聞與社群討論資料 +• 分析輿情傾向與討論熱度 -【報告要求】 -**長度**:250-400字(精準扼要) -**結構**: -1. 情緒總結(50字):一句話概括市場氣氛。 -2. 熱點分析(100-150字):主要討論焦點。 -3. 風險提示(50字):情緒是否極端? -4. 投資啟示(50-100字):逆勢操作還是順勢而為? -5. 情緒指標表格(必須包含)。 +【報告架構】 +**字數要求**:350-500字 +**內容結構**: +1. 情緒概要(60字):市場氛圍與情緒指標 +2. 輿情分析(150-200字):主要討論議題與觀點分布 +3. 關鍵洞察(80字):情緒極值或轉折訊號 +4. 投資含義(60-100字):情緒面對操作策略的啟示 +5. 情緒數據表格(必須) -**注意**: -- 用詞生動但客觀。 -- 不要流水帳,只抓重點。 -- 必須包含情緒量化表格。 +**撰寫原則**: +- 客觀分析,避免主觀臆測 +- 聚焦有價值的情緒訊號 +- 必須包含情緒量化數據表格 -請提供一份直觀且有洞見的市場情緒報告。""" +請提供專業且具洞察力的市場情緒分析報告。""" + """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點。""", ) diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index 73fd3273..8aa29a06 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -78,31 +78,32 @@ def create_research_manager(llm, memory): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是投資決策主筆,負責彙整多方觀點,拍板定案。 +您是投資決策經理,負責評估多空辯論並做出最終投資決策。 【職責】 -1. **聽取辯論**:誰說得比較有道理? -2. **做出裁決**:現在到底是該買還是該賣? -3. **擬定戰略**:給交易員一個明確的方向。 +1. **評估論證**:客觀權衡看漲與看跌方的論據強度 +2. **做出決策**:基於證據明確判斷買入/賣出/持有 +3. **制定計畫**:提供交易員可執行的操作指引 【可用資訊】 - 過去反思:"{past_memory_str}" - 辯論歷史:{history} 【輸出要求】 -**長度**:300-450字(決策明確) -**結構**: -1. 決策摘要(50字):買入、賣出還是持有? -2. 觀點評析(100-150字):為什麼採納某方的意見? -3. 核心理由(100字):支持決策的關鍵證據。 -4. 給交易員的指令(50-100字):目標價、停損點、倉位控制。 +**字數要求**:350-500字 +**內容結構**: +1. 決策摘要(70字):明確的買入/賣出/持有決策與核心理由 +2. 論證評估(140字):雙方最強論點與分歧點 +3. 決策依據(150字):選擇此立場的關鍵證據與邏輯 +4. 操作指引(100字):部位規模、目標價位、停損設定 +5. 風險提示(50字):主要風險與監控重點 -**注意**: -- 不要模稜兩可。 -- 必須給出具體數字。 -- 決策要有邏輯支撐。 +**撰寫原則**: +- 決策明確,避免模稜兩可 +- 提供具體量化的操作參數 +- 邏輯清晰,證據充分 -請提供一份明確且可執行的投資決策!""" +請提供專業且可執行的投資決策報告。""" # 呼叫 LLM 生成回應 diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index cba48e27..4123ac6d 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -82,13 +82,13 @@ def create_risk_manager(llm, memory): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是風險控管總監,負責為投資決策把關,確保不會翻船。 +您是風險管理經理,負責評估投資計畫的風險並做出最終風控決策。 【職責】 -1. **審視辯論**:激進派太衝?保守派太縮? -2. **識別地雷**:最大的風險在哪裡? -3. **最終裁決**:這個交易能做嗎?怎麼做才安全? -4. **風控設定**:設定最後一道防線。 +1. **評估辯論**:綜合積極、中立、保守三方的風險觀點 +2. **識別風險**:系統性評估市場、財務、營運等多維度風險 +3. **最終決策**:基於風險調整後的買入/賣出/持有決策 +4. **風控設定**:建立明確的風險管理框架與參數 【可用資訊】 - 過去反思:"{past_memory_str}" @@ -96,19 +96,20 @@ def create_risk_manager(llm, memory): - 辯論歷史:{history} 【輸出要求】 -**長度**:300-450字(嚴謹把關) -**結構**: -1. 風控結論(50字):通過、駁回或有條件通過? -2. 風險評估(100-150字):總結各方觀點。 -3. 最終決策(100字):買/賣/持及建議倉位。 -4. 風控措施(50-100字):強制止損點、加減碼原則。 +**字數要求**:350-500字 +**內容結構**: +1. 風控結論(70字):風險評級與最終決策 +2. 論證評估(140字):三方風險觀點的綜合評估 +3. 風險分析(150字):主要風險因素與量化評估 +4. 最終決策(100字):經風險調整的操作建議與部位規模 +5. 風控措施(50字):停損、監控指標、應急預案 -**注意**: -- 安全第一。 -- 指令要明確,不能含糊。 -- 必須包含具體的風控參數。 +**撰寫原則**: +- 決策明確,風控參數具體 +- 保守謹慎,但避免過度保守 +- 提供完整的風險管理框架 -請提供一份全面且可執行的風險管理方案!""" +請提供專業且全面的風險管理決策報告。""" # 呼叫 LLM 生成決策 diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index 678db4d8..588b39c9 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -88,13 +88,13 @@ def create_bear_researcher(llm, memory): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是謹慎投資研究員,專注於揭示潛在的下跌風險。 +您是看跌方研究員,負責提出賣出論據,強調投資風險與下跌壓力。 【分析重點】 -1. **成長隱憂**:營收是否開始放緩? -2. **競爭威脅**:護城河是否被侵蝕? -3. **財務地雷**:現金流或債務有無問題? -4. **負面因子**:有無潛在的利空消息? +1. **成長疑慮**:檢視營收成長減速、市場飽和或競爭加劇跡象 +2. **競爭劣勢**:評估護城河侵蝕、市佔率流失或定價能力弱化 +3. **財務問題**:識別現金流惡化、債務風險或獲利品質下降 +4. **負面催化**:指出可能觸發股價下跌的事件或結構性問題 【可用資源】 - 市場分析:{market_research_report} @@ -106,19 +106,19 @@ def create_bear_researcher(llm, memory): - 過往經驗:{past_memory_str} 【輸出要求】 -**長度**:200-350字(一針見血) -**結構**: -1. 核心警示(50字):一句話點出最大風險。 -2. 風險詳解(100-150字):為什麼這個風險很嚴重? -3. 反駁多方(50-100字):指出看漲觀點的盲點。 -4. 投資建議(50字):建議賣出或觀望。 +**字數要求**:300-450字 +**內容結構**: +1. 核心警示(70字):清晰陳述看跌理由 +2. 風險論證(150-200字):數據支撐的風險分析 +3. 回應質疑(80字):針對看漲觀點的反駁 +4. 投資建議(50字):明確的操作建議 -**注意**: -- 保持冷靜客觀。 -- 不要為了反對而反對,要有理有據。 -- 強調風險大於機會。 +**撰寫原則**: +- 論據扎實,以數據與事實為基礎 +- 直接指出對方論點的漏洞 +- 強調風險大於機會 -請提供一份警示性的看跌報告! +請提供有說服力的看跌分析報告。 """ # 呼叫 LLM 生成回應 diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index 54119f11..ae9c2bde 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -93,13 +93,13 @@ def create_bull_researcher(llm, memory): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是樂觀投資研究員,專注於發掘股票的爆發潛力。 +您是看漲方研究員,負責提出買進論據,強調投資價值與上漲潛力。 【分析重點】 -1. **成長引擎**:這家公司靠什麼賺大錢? -2. **護城河**:為什麼別人贏不了它? -3. **催化劑**:近期有什麼利多消息? -4. **估值優勢**:為什麼現在買很划算? +1. **成長動能**:評估營收、盈餘成長的持續性與加速跡象 +2. **競爭優勢**:分析護城河、市場地位與定價能力 +3. **催化因子**:識別可能推升股價的近期事件或結構性改變 +4. **估值優勢**:說明當前價格相對價值的吸引力 【可用資料】 - 市場分析:{market_research_report} @@ -111,19 +111,19 @@ def create_bull_researcher(llm, memory): - 過往經驗:{past_memory_str} 【輸出要求】 -**長度**:200-350字(精簡有力) -**結構**: -1. 核心觀點(50字):一句話告訴大家為什麼要買。 -2. 亮點分析(100-150字):詳述最大的利多。 -3. 反駁空方(50-100字):針對看跌觀點進行回擊。 -4. 投資建議(50字):堅定看多。 +**字數要求**:300-450字 +**內容結構**: +1. 核心論點(70字):清晰陳述看漲理由 +2. 成長論證(150-200字):數據支撐的成長邏輯 +3. 回應質疑(80字):針對看跌觀點的反駁 +4. 投資建議(50字):明確的操作建議 -**注意**: -- 用數據說話,但不要枯燥。 -- 展現信心,但要有邏輯。 -- 直接回應對方的質疑。 +**撰寫原則**: +- 論據扎實,以數據與事實為基礎 +- 直接回應對方論點,避免迴避問題 +- 承認風險但強調機會更具吸引力 -請提供一份令人信服的看漲報告! +請提供有說服力的看漲分析報告。 """ # 呼叫 LLM 生成回應 diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index 1b753c22..ff6a272f 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -68,13 +68,13 @@ def create_risky_debator(llm): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是積極型策略師,追求高風險高報酬的機會。 +您是積極型風險策略師,主張追求高報酬機會,評估上檔潛力。 【論證重點】 -1. **獲利空間**:如果看對了,能賺多少? -2. **爆發點**:什麼事件會讓股價噴出? -3. **動能**:現在是不是主升段? -4. **反駁保守**:太保守會錯失什麼大行情? +1. **上檔空間**:量化分析最佳情境下的報酬潛力 +2. **催化事件**:識別可能帶動股價突破的關鍵因素 +3. **成長加速**:評估營收或盈餘成長提速的可能性 +4. **保守迷思**:指出過度保守可能錯失的機會成本 【可用資訊】 - 交易員計畫:{trader_decision} @@ -83,19 +83,19 @@ def create_risky_debator(llm): - 對手觀點:{current_safe_response}, {current_neutral_response} 【輸出要求】 -**長度**:200-350字(充滿熱情) -**結構**: -1. 核心主張(50字):為什麼現在必須進場? -2. 機會分析(100-150字):描繪獲利藍圖。 -3. 回應質疑(50-100字):風險是可控的。 -4. 操作建議(50字):積極買進。 +**字數要求**:300-450字 +**內容結構**: +1. 核心主張(70字):清晰陳述積極策略的理由 +2. 機會分析(150-200字):上檔潛力的論證 +3. 回應質疑(80字):反駁保守派的擔憂 +4. 操作建議(50字):明確的部位建議 -**注意**: -- 強調「富貴險中求」。 -- 挑戰保守派的思維。 -- 展現對高報酬的渴望。 +**撰寫原則**: +- 量化評估,避免空泛樂觀 +- 直接回應風險疑慮 +- 強調機會大於風險的論據 -請提供一份積極進取的投資論證!""" +請提供專業且具說服力的積極策略分析。""" # 呼叫 LLM 生成回應 response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index 5e10350a..e0784c9b 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -69,13 +69,13 @@ def create_safe_debator(llm): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是穩健型策略師,首要任務是保護本金。 +您是保守型風險策略師,優先考量資本保全,評估下檔風險。 【論證重點】 -1. **下檔風險**:最慘會賠多少? -2. **隱形地雷**:大家忽略了什麼危險? -3. **價格偏離**:現在股價是不是太貴了? -4. **回應激進**:指出激進派的盲點。 +1. **下檔風險**:量化分析最壞情境下的潛在損失 +2. **隱藏風險**:識別市場尚未充分反應的威脅因素 +3. **估值疑慮**:評估股價相對基本面的偏離程度 +4. **激進盲點**:指出過度樂觀忽略的風險因子 【可用資訊】 - 交易員計畫:{trader_decision} @@ -84,19 +84,19 @@ def create_safe_debator(llm): - 對手觀點:{current_risky_response}, {current_neutral_response} 【輸出要求】 -**長度**:200-350字(謹慎小心) -**結構**: -1. 核心警告(50字):為什麼現在很危險? -2. 風險盤點(100-150字):列出具體威脅。 -3. 潑冷水(50-100字):反駁過度樂觀的看法。 -4. 操作建議(50字):保守為上,現金為王。 +**字數要求**:300-450字 +**內容結構**: +1. 核心警示(70字):清晰陳述保守建議的理由 +2. 風險盤點(150-200字):下檔風險的詳細分析 +3. 回應質疑(80字):反駁積極派的論點 +4. 操作建議(50字):明確的風控建議 -**注意**: -- 寧可少賺,不可大賠。 -- 強調安全邊際。 -- 建議防禦性策略。 +**撰寫原則**: +- 量化評估,避免過度悲觀 +- 直接回應機會論述 +- 強調風險管理的重要性 -請提供一份穩健保守的投資論證!""" +請提供專業且具說服力的保守策略分析。""" # 呼叫 LLM 生成回應 response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index 02de83d7..11e50e44 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -68,13 +68,13 @@ def create_neutral_debator(llm): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是平衡型策略師,客觀評估風險與報酬的性價比。 +您是平衡型風險策略師,客觀評估風險與報酬,提供折衷方案。 【論證重點】 -1. **客觀權衡**:不偏多也不偏空,只看數據。 -2. **情境分析**:什麼情況下該買?什麼情況下該賣? -3. **策略優化**:有沒有比單純買進或賣出更好的做法?(如分批、對沖) -4. **調解分歧**:整合激進與保守的觀點。 +1. **平衡視角**:客觀權衡上檔機會與下檔風險 +2. **情境分析**:評估不同市場情境下的策略適用性 +3. **風險調整**:建議部位規模與風險對沖措施 +4. **整合觀點**:綜合積極與保守的合理之處 【可用資訊】 - 交易員計畫:{trader_decision} @@ -83,19 +83,19 @@ def create_neutral_debator(llm): - 對手觀點:{current_risky_response}, {current_safe_response} 【輸出要求】 -**長度**:200-350字(中立客觀) -**結構**: -1. 核心觀點(50字):持平而論。 -2. 損益分析(100-150字):分析勝率與賠率。 -3. 評論對手(50-100字):指出雙方都沒看到的盲點。 -4. 操作建議(50字):穩健的折衷方案。 +**字數要求**:300-450字 +**內容結構**: +1. 核心觀點(70字):平衡策略的理由 +2. 風險報酬評估(150-200字):客觀分析損益比 +3. 評論雙方(80字):指出積極與保守派的合理與盲點 +4. 操作建議(50字):具體的折衷方案 -**注意**: -- 尋求最佳平衡點。 -- 不要當牆頭草,要有自己的判斷。 -- 提供務實的建議。 +**撰寫原則**: +- 客觀中立,避免偏頗 +- 提供可執行的平衡策略 +- 強調風險管理與機會把握的平衡 -請提供一份平衡且客觀的投資論證!""" +請提供專業且客觀的平衡策略分析。""" # 呼叫 LLM 生成回應 response = llm.invoke(prompt) diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 444df138..03a683d2 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -87,33 +87,33 @@ def create_trader(llm, memory): prompt = f"""**重要:您必須使用繁體中文(Traditional Chinese)回覆所有內容。** 【專業身份】 -您是交易執行專家,負責將分析轉化為精確的下單指令。 +您是交易執行專家,負責將投資決策轉化為具體可執行的交易計畫。 【職責】 -1. **綜合研判**:結合研究與風控的意見。 -2. **擬定指令**:什麼價格買?買多少?什麼時候跑? -3. **執行紀律**:嚴格遵守交易計畫。 +1. **整合決策**:綜合研究團隊與風控團隊的建議 +2. **制定計畫**:明確買入/賣出/持有的執行細節 +3. **風險管理**:設定清晰的進出場與停損參數 【可用資訊】 - 投資計畫:{investment_plan_truncated} - 過去反思:{past_memory_str} 【輸出要求】 -**長度**:300-450字(精確執行) -**結構**: -1. 最終決策(50字):買入、賣出或持有。 -2. 綜合分析(100字):為什麼這樣決定? -3. 交易計畫(150字): - - 進場價位/區間 - - 資金比例(%) - - 目標價(獲利點) - - 停損價(停損點) -4. 監控重點(50字):接下來要盯什麼? +**字數要求**:350-500字 +**內容結構**: +1. 執行摘要(70字):最終決策與核心理由 +2. 決策整合(100字):研究與風控觀點的平衡 +3. 交易計畫(180字): + - 進場策略:價位區間與時機 + - 部位規模:資金配置比例 + - 目標價位:獲利了結點 + - 停損設定:風險控制線 +4. 監控機制(50字):關鍵監控指標與調整觸發條件 -**注意**: -- 決策必須明確(買/賣/持)。 -- 數字要精確。 -- 務實可執行。 +**撰寫原則**: +- 決策明確,參數具體 +- 可執行性強,避免模糊表述 +- 風險控制完善 請以「最終交易提案:**買入/持有/賣出**」結束回應!""" diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index c0dcdd05..9b9aeaff 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -200,6 +200,8 @@ def route_to_vendor(method: str, *args, **kwargs): for impl_func, vendor_name in vendor_methods: try: print(f"調試:正在從供應商 '{vendor_name}' 調用 {impl_func.__name__}...") + + # 執行函數(已由各供應商內部處理timeout) result = impl_func(*args, **kwargs) vendor_results.append(result) print(f"成功:來自供應商 '{vendor_name}' 的 {impl_func.__name__} 成功完成") @@ -211,8 +213,9 @@ def route_to_vendor(method: str, *args, **kwargs): # 繼續到下一個供應商進行備援 continue except Exception as e: - # 記錄錯誤但繼續其他實現 - print(f"失敗:來自供應商 '{vendor_name}' 的 {impl_func.__name__} 失敗:{e}") + # 記錄詳細錯誤但繼續其他實現 + error_type = type(e).__name__ + print(f"失敗:來自供應商 '{vendor_name}' 的 {impl_func.__name__} 失敗 ({error_type}): {e}") continue # 新增此供應商的結果 diff --git a/tradingagents/dataflows/retry_utils.py b/tradingagents/dataflows/retry_utils.py new file mode 100644 index 00000000..2cb94066 --- /dev/null +++ b/tradingagents/dataflows/retry_utils.py @@ -0,0 +1,84 @@ +""" +重試工具模組,提供統一的重試機制和錯誤處理 +""" +import time +import logging +from functools import wraps +from typing import Callable, Type, Tuple + +logger = logging.getLogger(__name__) + + +def retry( + max_attempts: int = 3, + backoff: float = 2.0, + exceptions: Tuple[Type[Exception], ...] = (Exception,), + on_retry: Callable = None +): + """ + 重試裝飾器,支援指數退避 + + Args: + max_attempts: 最大重試次數(包含首次嘗試) + backoff: 退避基數(每次重試等待時間 = backoff ^ attempt) + exceptions: 需要重試的例外類型元組 + on_retry: 重試時的回調函數 + + Returns: + 裝飾後的函數 + + Example: + @retry(max_attempts=3, backoff=2.0) + def fetch_data(): + return api.get_data() + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs): + last_exception = None + + for attempt in range(1, max_attempts + 1): + try: + return func(*args, **kwargs) + + except exceptions as e: + last_exception = e + + if attempt == max_attempts: + # 最後一次嘗試失敗 + logger.error( + f"{func.__name__} 在 {max_attempts} 次嘗試後失敗: {e}" + ) + raise + + # 計算退避時間 + wait_time = backoff ** (attempt - 1) + + logger.warning( + f"{func.__name__} 第 {attempt} 次嘗試失敗: {e}. " + f"將在 {wait_time:.1f} 秒後重試..." + ) + + # 執行回調 + if on_retry: + on_retry(attempt, e) + + # 等待後重試 + time.sleep(wait_time) + + # 理論上不會到這裡,但為了類型安全 + raise last_exception + + return wrapper + return decorator + + +def log_retry_attempt(attempt: int, exception: Exception) -> None: + """ + 記錄重試嘗試的標準回調函數 + + Args: + attempt: 當前嘗試次數 + exception: 遇到的例外 + """ + logger.info(f"重試回調 - 第 {attempt} 次嘗試因以下原因失敗: {type(exception).__name__}") diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 19601f92..fbb29b28 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -3,8 +3,15 @@ from datetime import datetime from dateutil.relativedelta import relativedelta import yfinance as yf import os +import time +import logging from .stockstats_utils import StockstatsUtils +from .retry_utils import retry +logger = logging.getLogger(__name__) + + +@retry(max_attempts=3, backoff=2.0, exceptions=(Exception,)) def get_YFin_data_online( symbol: Annotated[str, "公司的股票代碼"], start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"], @@ -28,8 +35,11 @@ def get_YFin_data_online( # 建立股票代碼物件 ticker = yf.Ticker(symbol.upper()) - # 獲取指定日期範圍的歷史數據 - data = ticker.history(start=start_date, end=end_date) + # 獲取指定日期範圍的歷史數據(添加 timeout) + try: + data = ticker.history(start=start_date, end=end_date, timeout=30) + except Exception as e: + raise Exception(f"從 Yahoo Finance 獲取 {symbol} 數據失敗: {e}") # 檢查數據是否為空 if data.empty: @@ -57,6 +67,7 @@ def get_YFin_data_online( return header + csv_string + def get_stock_stats_indicators_window( symbol: Annotated[str, "公司的股票代碼"], indicator: Annotated[str, "要獲取分析和報告的技術指標"], @@ -254,20 +265,50 @@ def _get_stock_stats_bulk( f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", ) + # 檢查緩存是否存在且有效(24小時內) + cache_valid = False if os.path.exists(data_file): + file_mtime = os.path.getmtime(data_file) + current_time = time.time() + cache_age_hours = (current_time - file_mtime) / 3600 + + if cache_age_hours < 24: + cache_valid = True + logger.info(f"{symbol} 緩存有效(年齡:{cache_age_hours:.1f} 小時)") + else: + logger.info(f"{symbol} 緩存過期(年齡:{cache_age_hours:.1f} 小時),將重新下載") + + if cache_valid: data = pd.read_csv(data_file) data["Date"] = pd.to_datetime(data["Date"]) else: - data = yf.download( - symbol, - start=start_date_str, - end=end_date_str, - multi_level_index=False, - progress=False, - auto_adjust=True, - ) - data = data.reset_index() - data.to_csv(data_file, index=False) + # 使用重試機制下載數據 + @retry(max_attempts=3, backoff=2.0) + def download_data(): + return yf.download( + symbol, + start=start_date_str, + end=end_date_str, + multi_level_index=False, + progress=False, + auto_adjust=True, + timeout=30 + ) + + try: + data = download_data() + data = data.reset_index() + data.to_csv(data_file, index=False) + logger.info(f"成功下載並緩存 {symbol} 數據到 {data_file}") + except Exception as e: + logger.error(f"下載 {symbol} 數據失敗: {e}") + # 如果下載失敗但有舊緩存,使用舊緩存 + if os.path.exists(data_file): + logger.warning(f"使用過期緩存作為備援") + data = pd.read_csv(data_file) + data["Date"] = pd.to_datetime(data["Date"]) + else: + raise df = wrap(data) df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") @@ -290,6 +331,7 @@ def _get_stock_stats_bulk( return result_dict + def get_stockstats_indicator( symbol: Annotated[str, "公司的股票代碼"], indicator: Annotated[str, "要獲取分析和報告的技術指標"],