This commit is contained in:
parent
c4430805e2
commit
8d3c8dc2a6
|
|
@ -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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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` 用於特定的財務報表。"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 表格,以整理報告中的要點。"""
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 表格,以整理報告中的要點。""",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 表格,以整理報告中的要點。""",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 生成回應
|
||||
|
|
|
|||
|
|
@ -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 生成決策
|
||||
|
|
|
|||
|
|
@ -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 生成回應
|
||||
|
|
|
|||
|
|
@ -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 生成回應
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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字):關鍵監控指標與調整觸發條件
|
||||
|
||||
**注意**:
|
||||
- 決策必須明確(買/賣/持)。
|
||||
- 數字要精確。
|
||||
- 務實可執行。
|
||||
**撰寫原則**:
|
||||
- 決策明確,參數具體
|
||||
- 可執行性強,避免模糊表述
|
||||
- 風險控制完善
|
||||
|
||||
請以「最終交易提案:**買入/持有/賣出**」結束回應!"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# 新增此供應商的結果
|
||||
|
|
|
|||
|
|
@ -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__}")
|
||||
|
|
@ -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, "要獲取分析和報告的技術指標"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue