This commit is contained in:
MarkLo 2025-11-25 20:48:39 +08:00
parent c4430805e2
commit 8d3c8dc2a6
16 changed files with 463 additions and 228 deletions

View File

@ -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]:
"""

View File

@ -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_statementget_balance_sheetget_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` 用於特定的財務報表。"
)

View File

@ -39,34 +39,34 @@ def create_market_analyst(llm):
"""**重要您必須使用繁體中文Traditional Chinese回覆所有內容。**
專業身份
您是一位資深市場分析師專長是將複雜的技術分析轉化為一般投資人能懂的見解
您是資深技術分析師負責提供精準的市場技術面評估
分析
1. **趨勢判斷**用一句話明確指出目前是多頭空頭還是盤整
2. **關鍵指標**挑選3個最具代表性的指標如50/200日均線MACDRSI進行解讀
3. **關鍵價位**明確指出支撐位與壓力位
4. **操作建議**給出直觀的進出場策略
分析
1. **趨勢研判**基於價格走勢與成交量明確判斷當前市場階段上升趨勢/下降趨勢/區間整理
2. **技術指標**聚焦3-4個核心指標建議50/200日均線MACDRSI解讀其訊號意義
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 表格,以整理報告中的要點。"""
)

View File

@ -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 表格,以整理報告中的要點。""",
)

View File

@ -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 表格,以整理報告中的要點。""",
)

View File

@ -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 生成回應

View File

@ -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 生成決策

View File

@ -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 生成回應

View File

@ -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 生成回應

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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關鍵監控指標與調整觸發條件
**注意**
- 決策必須明確//
- 數字要精確
- 務實可執行
**撰寫原則**
- 決策明確參數具體
- 可執行性強避免模糊表述
- 風險控制完善
請以最終交易提案**買入/持有/賣出**結束回應"""

View File

@ -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
# 新增此供應商的結果

View File

@ -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__}")

View File

@ -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, "要獲取分析和報告的技術指標"],