投資組合大擂台 Ver. 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

630 lines
22 KiB

"""
投資建議 Prompt V2 - Context Engineering + Prompt Engineering
核心改進:
1. Context Engineering: 加入用戶背景、市場環境、評分標準
2. Prompt Engineering: 強制一致性邏輯、嚴格輸出格式
3. 前端整合: 支援 Markdown 渲染
"""
from typing import Dict, Any
from datetime import datetime
# ===== Context Engineering =====
def get_market_context(tw: bool = True) -> Dict[str, Any]:
"""
獲取市場環境背景(從資料庫計算實際數據)
整合:
- 資料庫:從 stock_price_tw (0050.TW) 和 stock_price (SPY) 計算
- 快取:1小時 TTL 避免重複計算
- Fallback:資料庫查詢失敗時使用靜態資料
"""
try:
# 嘗試使用資料庫計算的實際市場數據
from market_benchmark import get_market_benchmark
benchmark = get_market_benchmark()
context = benchmark.get_market_context(tw)
# 如果是 fallback,記錄警告
if context.get('is_fallback'):
import logging
logging.warning(f"Using fallback market data for {'TW' if tw else 'US'}")
return context
except ImportError:
# market_benchmark 模組不存在,使用靜態資料
import logging
logging.warning("market_benchmark module not found, using static data")
return _get_static_market_context(tw)
except Exception as e:
# 其他錯誤,使用靜態資料
import logging
logging.error(f"Error loading market context: {e}, using static data")
return _get_static_market_context(tw)
def _get_static_market_context(tw: bool) -> Dict[str, Any]:
"""靜態市場資料(Fallback)"""
if tw:
return {
"market_name": "台灣加權指數(0050.TW)",
"ytd_return": 0.18,
"avg_5y_return": 0.09,
"volatility": 0.15,
"sentiment": "neutral",
"is_fallback": True
}
else:
return {
"market_name": "S&P 500(SPY)",
"ytd_return": 0.22,
"avg_5y_return": 0.12,
"volatility": 0.14,
"sentiment": "bull",
"is_fallback": True
}
def get_investor_profile(risk_tolerance: str = "moderate") -> Dict[str, Any]:
"""
投資人背景設定 (未來可從用戶資料庫讀取)
Args:
risk_tolerance: conservative/moderate/aggressive
"""
profiles = {
"conservative": {
"label": "保守型",
"max_acceptable_mdd": -0.15, # 最大可接受回落
"target_sharpe": 1.5, # 目標夏普比率
"description": "重視資本保全,追求穩定收益"
},
"moderate": {
"label": "穩健型",
"max_acceptable_mdd": -0.25,
"target_sharpe": 1.0,
"description": "平衡風險與報酬"
},
"aggressive": {
"label": "積極型",
"max_acceptable_mdd": -0.40,
"target_sharpe": 0.8,
"description": "追求高報酬,願意承擔較高風險"
}
}
return profiles.get(risk_tolerance, profiles["moderate"])
def get_evaluation_criteria() -> Dict[str, Any]:
"""
明確的評分標準 (解決評價不一致問題)
"""
return {
"sharpe_ratio": {
"excellent": 2.0, # >= 2.0
"good": 1.5, # >= 1.5
"acceptable": 1.0, # >= 1.0
"poor": 0.5, # >= 0.5
"very_poor": 0.0 # < 0.5
},
"annual_return": {
"tw": {"excellent": 0.15, "good": 0.10, "acceptable": 0.08, "poor": 0.05},
"us": {"excellent": 0.18, "good": 0.12, "acceptable": 0.10, "poor": 0.06}
},
"mdd": {
"low_risk": -0.15, # < -15% 算低風險
"medium_risk": -0.25, # -15% ~ -25%
"high_risk": -0.40, # -25% ~ -40%
"extreme_risk": -0.40 # > -40%
},
"beta": {
"defensive": 0.8, # < 0.8
"balanced": 1.2, # 0.8 ~ 1.2
"aggressive": 1.2 # > 1.2
}
}
# ===== Prompt Engineering =====
def get_context_aware_prompt(strategy_data: Dict[str, Any],
risk_tolerance: str = "moderate",
use_cot: bool = True) -> str:
"""
Context-Aware Prompt with Strict Evaluation Logic
Args:
strategy_data: 策略數據
risk_tolerance: 風險承受度 (conservative/moderate/aggressive)
use_cot: 是否使用 Chain-of-Thought
"""
# 提取數據
annual_ret = strategy_data.get('annual_ret', 0)
vol = strategy_data.get('vol', 0)
sharpe = strategy_data.get('annual_sr', 0)
mdd = strategy_data.get('mdd', 0)
alpha = strategy_data.get('alpha', 0)
beta = strategy_data.get('beta', 0)
var10 = strategy_data.get('var10', 0)
r2 = strategy_data.get('r2', 0)
assets = strategy_data.get('assets', [])
name = strategy_data.get('name', 'N/A')
role = strategy_data.get('role', 'N/A')
tw = strategy_data.get('tw', True)
# Context Engineering
market_ctx = get_market_context(tw)
investor_profile = get_investor_profile(risk_tolerance)
criteria = get_evaluation_criteria()
# 構建評分邏輯
evaluation_logic = f"""
## 評分標準與邏輯 (CRITICAL: 必須嚴格遵守)
### 1. 夏普比率評級
```python
if sharpe >= {criteria['sharpe_ratio']['excellent']}:
rating = "優秀"
description = "風險調整後報酬表現卓越"
elif sharpe >= {criteria['sharpe_ratio']['good']}:
rating = "良好"
description = "風險調整後報酬表現良好"
elif sharpe >= {criteria['sharpe_ratio']['acceptable']}:
rating = "可接受"
description = "風險調整後報酬尚可"
elif sharpe >= {criteria['sharpe_ratio']['poor']}:
rating = "待改善"
description = "風險調整後報酬偏低"
else:
rating = "不佳"
description = "風險調整後報酬表現不佳"
```
**本策略夏普比率: {sharpe:.2f}** → 請依上述邏輯給予評級,並保持一致性。
### 2. 報酬率評級 (相對於市場基準)
- 市場基準: {market_ctx['market_name']} 近5年平均 {market_ctx['avg_5y_return']:.1%}
- 本策略: {annual_ret:.2%}
- 相對表現: {((annual_ret / market_ctx['avg_5y_return']) - 1) * 100:+.1f}%
### 3. 風險評級
- 最大回落: {mdd:.2%}
- 投資人可接受範圍: {investor_profile['max_acceptable_mdd']:.1%}
- 超出程度: {((abs(mdd) / abs(investor_profile['max_acceptable_mdd'])) - 1) * 100:+.1f}%
### 4. 市場相關性
- Beta: {beta:.2f}
- 類型: {"防禦型 (< 0.8)" if beta < criteria['beta']['defensive'] else "均衡型 (0.8-1.2)" if beta < criteria['beta']['balanced'] else "進攻型 (> 1.2)"}
- 預期波動: 市場變動 1%,策略預期變動 {beta:.2f}%
### 5. 超額收益能力
- Alpha: {alpha:.4f}
- 解釋: {"創造超額收益 ✓" if alpha > 0.01 else "與市場同步 ≈" if alpha >= -0.01 else "落後市場 ✗"}
"""
# 一致性邏輯檢查
consistency_check = f"""
## 一致性邏輯檢查 (MUST FOLLOW)
**禁止自相矛盾**:
1. 如果夏普比率評級為「優秀」,整體評價不能說「表現一般」
2. 如果報酬率遠超市場,風險評價不能忽略這個亮點
3. 如果 MDD 超出可接受範圍,必須在風險警示中明確指出
✅ **評價優先順序**:
1. 首先看風險調整後報酬 (夏普比率) → 這是最重要的指標
2. 其次看絕對報酬 vs 市場基準
3. 最後看風險承受度匹配性
📊 **本策略快速判讀**:
- 夏普 {sharpe:.2f}: {"優秀 ✓" if sharpe >= 2.0 else "良好 ✓" if sharpe >= 1.5 else "可接受 ○" if sharpe >= 1.0 else "待改善 ✗"}
- 報酬 {annual_ret:.2%} vs 市場 {market_ctx['avg_5y_return']:.1%}: {"超越 ✓" if annual_ret > market_ctx['avg_5y_return'] * 1.2 else "接近 ○" if annual_ret > market_ctx['avg_5y_return'] * 0.8 else "落後 ✗"}
- 風險 {mdd:.2%}: {"可接受 ✓" if abs(mdd) <= abs(investor_profile['max_acceptable_mdd']) else "偏高 ⚠"}
"""
# CoT 推理步驟
cot_steps = """
## 推理步驟 (Chain-of-Thought)
請按以下順序思考並輸出:
**步驟 1: 指標解讀**
- 先單獨解讀每個指標的絕對值
- 再與市場基準比較
- 最後與投資人預期比較
**步驟 2: 綜合評分**
- 根據評分標準,給出各維度評級
- 注意保持一致性(不要有矛盾)
- 找出最突出的優勢和劣勢
**步驟 3: 情境分析**
- 假設投入 100 萬資金
- 最好情況能賺多少?(基於年化報酬)
- 最壞情況會虧多少?(基於 MDD)
- 承受 1 單位風險能換多少報酬?(基於夏普)
**步驟 4: 投資建議**
- 這個策略適合哪種投資人?
- 在什麼市場環境下表現最好?
- 需要做哪些調整才能更好?
""" if use_cot else ""
# 嚴格禁止事項
strict_prohibitions = f"""
## ⛔ 嚴格禁止事項 (CRITICAL - MUST FOLLOW)
1. **禁止自己假設或生成市場數據**
- ❌ 禁止說「假設台灣加權指數同期間年化報酬率為 12%...」
- ❌ 禁止說「為了更精確的比較,我們需要...」
- ✅ **必須使用已提供的市場基準數據**: {market_ctx['market_name']} 近5年平均 {market_ctx['avg_5y_return']:.1%}
2. **禁止模糊或矛盾的評價**
- ❌ 禁止說「優秀的投資策略...風險調整後報酬表現一般」
- ❌ 禁止說「良好但待改善」
- ✅ 必須根據夏普比率給出**明確且一致**的評級
3. **禁止使用模板化語言**
- ❌ 禁止說「策略基本資訊回顧」「歷史表現趨勢分析」等空泛標題
- ✅ 必須根據**實際數據**給出具體分析
4. **市場基準說明**
- 台灣市場: 使用 **0050.TW (元大台灣50)** 作為市場代理指標
- 理由: 追蹤台灣市值前50大公司,涵蓋約70%市值,流動性高
- 美國市場: 使用 **SPY (SPDR S&P 500 ETF)** 作為市場代理指標
- **直接使用提供的數據,不要質疑或假設其他數據**
"""
# 輸出格式要求(專業深度版)
output_format = """
## 輸出格式要求 (STRICT)
**目標對象**:專業投資人、進階使用者
**字數控制**:1500-2000 字
**特色**:顯示完整推理過程、技術性分析、數學計算
請用以下 Markdown 格式輸出:
```markdown
## 📊 策略總評
[一句話總結:綜合評級 + 核心特性] (不超過 30 字)
---
## 🎯 核心指標分析
### 風險調整後報酬
**夏普比率 {sharpe:.2f}** → [優秀/良好/可接受/待改善]
```
推理過程:
- 業界標準: ≥2.0 優秀, ≥1.5 良好, ≥1.0 可接受
- 本策略: {sharpe:.2f}
- 意義: 每承受 1% 風險,獲得 {sharpe:.2f}% 超額報酬
→ 結論: [具體評價]
```
### 報酬表現
**年化報酬 {annual_ret:.2%}** vs **市場基準 {market_ctx['avg_5y_return']:.1%}**
```
推理過程:
- 絕對報酬: {annual_ret:.2%}
- 相對市場: {((annual_ret / market_ctx['avg_5y_return']) - 1) * 100:+.1f}%
- 情境假設: 投入 100 萬,一年後預期變為 {100 * (1 + annual_ret):.1f} 萬
→ 結論: [超越/接近/落後]市場表現
```
### 風險評估
**最大回落 {mdd:.2%}** | **波動率 {vol:.2%}**
```
推理過程:
- 歷史最大虧損: {mdd:.2%}
- 投資人可接受範圍: {investor_profile['max_acceptable_mdd']:.1%}
- 情境假設: 投入 100 萬,最壞情況虧損 {abs(100 * mdd):.1f} 萬
→ 結論: 風險水平 [低/中/高],[適合/不適合] {investor_profile['label']}投資人
```
---
## 💡 投資建議
### ✅ 策略亮點
1. [最突出的優勢,用數據支持]
2. [第二個優勢]
### ⚠ 風險提示
1. [最主要的風險,量化說明]
2. [次要風險]
### 🎯 適合對象
- **投資人類型**: [保守/穩健/積極]
- **投資期限**: [短期/中期/長期]
- **市場環境**: [牛市/震盪/熊市]
### 🔧 優化建議
[1-2 個具體的改進方向]
---
## 📈 未來展望
[基於當前市場環境 {market_ctx['sentiment']},給出 3-6 個月展望]
---
*本分析基於歷史數據回測,過往績效不代表未來表現。建議搭配即時市場分析與專業投資顧問。*
```
**重要**:
1. 禁止使用模糊詞彙(「優秀但一般」「良好卻待改善」)
2. 每個評價必須有數據支持
3. 推理過程要顯示計算邏輯
4. 結論必須與推理過程一致
"""
# 組合完整 Prompt
full_prompt = f"""
你是一位資深投資顧問(CFA, 20+ 年經驗)。請基於以下背景與數據,提供專業投資分析。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 投資人背景
- 風險承受度:**{investor_profile['label']}** ({investor_profile['description']})
- 目標夏普比率:≥ {investor_profile['target_sharpe']:.1f}
- 可接受最大回落:{investor_profile['max_acceptable_mdd']:.1%}
## 市場環境
- 市場:{market_ctx['market_name']}
- 今年表現:{market_ctx['ytd_return']:.1%}
- 近5年平均:{market_ctx['avg_5y_return']:.1%}
- 市場情緒:{market_ctx['sentiment']}
## 策略資訊
- 名稱:{name}
- 目標:{role}
- 持股:{', '.join(assets[:5])}{"..." if len(assets) > 5 else ""} (共 {len(assets)} 檔)
## 績效數據
| 指標 | 數值 |
|------|------|
| 年化報酬率 | {annual_ret:.1%} |
| 年化波動率 | {vol:.1%} |
| 夏普比率 | {sharpe:.1f} |
| 最大回落 (MDD) | {mdd:.2%} |
| Alpha | {alpha:.1f} |
| Beta | {beta:.2f} |
| VaR (95%) | {var10:.2%} |
| R² | {r2:.2%} |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{strict_prohibitions}
{evaluation_logic}
{consistency_check}
{cot_steps}
{output_format}
現在請開始分析,記住:**數據驅動,邏輯一致,評價明確,禁止假設市場數據**。
"""
return full_prompt
def get_simplified_context_prompt(strategy_data: Dict[str, Any]) -> str:
"""
教育性詳細版 Context-Aware Prompt(為普羅大眾設計,預設模式)
目標對象:投資新手、一般散戶
特色:
- 白話文解釋每個指標的意義
- 舉例說明(投入100萬的情境)
- 說明為什麼這個數字重要
- 提供具體優化建議
- 字數控制在 1000-1500 字
適用於:use_cot=False(預設模式,不顯示推理步驟,但內容詳盡)
"""
annual_ret = strategy_data.get('annual_ret', 0)
sharpe = strategy_data.get('annual_sr', 0)
vol = strategy_data.get('vol', 0)
mdd = strategy_data.get('mdd', 0)
beta = strategy_data.get('beta', 0)
alpha = strategy_data.get('alpha', 0)
var10 = strategy_data.get('var10', 0)
r2 = strategy_data.get('r2', 0)
tw = strategy_data.get('tw', True)
assets = strategy_data.get('assets', [])
name = strategy_data.get('name', 'N/A')
market_ctx = get_market_context(tw)
criteria = get_evaluation_criteria()
# 評分邏輯
sharpe_rating = "優秀" if sharpe >= 2.0 else "良好" if sharpe >= 1.5 else "可接受" if sharpe >= 1.0 else "待改善"
return_vs_market = ((annual_ret / market_ctx['avg_5y_return']) - 1) * 100
return f"""
你是一位資深投資教育講師,專門為**投資新手和普羅大眾**解釋投資策略。
你的任務是:
1. **用白話文**解釋每個績效指標的意義
2. **說明為什麼**這個指標重要
3. **給予具體的優化建議**
4. **避免使用過多專業術語**,如果必須使用,請解釋清楚
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 策略基本資訊
- **策略名稱**: {name}
- **投資市場**: {'🇹🇼 台灣股市' if tw else '🇺🇸 美國股市'}
- **持股標的**: {', '.join(assets[:10])}{"..." if len(assets) > 10 else ""} (共 {len(assets)} 檔)
- **對比基準**: {market_ctx['market_name']} (近5年平均 {market_ctx['avg_5y_return']:.1%})
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 📊 核心績效對比表(你的策略 vs 市場)
| 指標 | 你的策略 | 市場基準 | 差異 | 評價 |
|------|---------|---------|------|------|
| 📈 年化報酬率 | {annual_ret:.2%} | {market_ctx['avg_5y_return']:.1%} | {return_vs_market:+.1f}% | {'🚀 大勝' if return_vs_market > 50 else '✅ 超越' if return_vs_market > 0 else '❌ 落後'} |
| ⭐ 夏普比率 | {sharpe:.2f} | ~1.0 | {(sharpe - 1.0):+.2f} | {sharpe_rating} |
| 📊 波動率 | {vol:.2%} | {market_ctx.get('volatility', 0.20):.1%} | {(vol - market_ctx.get('volatility', 0.20)) * 100:+.1f}% | {' 較高' if vol > market_ctx.get('volatility', 0.20) * 1.2 else '✅ 適中'} |
| ⚠ 最大回落 | {mdd:.2%} | ~-30% | {'更深' if abs(mdd) > 0.30 else '相近'} | {'❌ 風險高' if abs(mdd) > 0.30 else '✅ 可控'} |
## 🎯 主動管理能力
| 指標 | 數值 | 說明 |
|------|------|------|
| 🎯 Alpha (超額報酬) | {alpha:.4f} | {'✅ 打敗市場' if alpha > 0.01 else '≈ 跟隨市場' if alpha >= -0.01 else '❌ 落後市場'} |
| ⚖ Beta (市場相關性) | {beta:.2f} | {'🔥 進攻型 (Beta > 1.2)' if beta > 1.2 else ' 均衡型 (Beta 0.8-1.2)' if beta > 0.8 else '🛡 防禦型 (Beta < 0.8)'} |
| 📐 R² (可解釋度) | {r2:.2%} | 策略中 {r2:.1%} 的波動來自市場影響 |
| 📉 VaR (風險值) | {var10:.2%} | 95%信心水準下的最大單日損失 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## ⛔ 重要指示
1. **禁止假設數據** - 市場基準 {market_ctx['avg_5y_return']:.1%} 已提供,直接使用
2. **評級必須一致** - 夏普 {sharpe:.2f} = 「{sharpe_rating}
3. **用白話文** - 避免過多專業術語
4. **解釋每個指標** - 說明它的意義和為什麼重要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 📝 輸出格式(教育性詳細版)
請用 1200-1600 字,包含以下內容:
```markdown
## 📊 一句話總評
[根據夏普比率和報酬率,給一個清晰的總評,30字內]
---
## 📋 你選了哪些股票?
{', '.join(assets[:15])}{"..." if len(assets) > 15 else ""}(共 {len(assets)} 檔)
**組合特性:**
- **產業分布**: [分析持股集中在哪些產業,例如:科技股佔50%、金融股佔30%]
- **風險分散度**: [評估是否分散,例如:持股{len(assets)}檔,{'分散良好' if len(assets) >= 8 else '偏集中' if len(assets) >= 5 else '非常集中'}]
- **代表性個股**: [列出前3-5檔重要持股,簡述其特色]
**💡 持股建議:**
[根據持股數量和類型,給予是否需要調整的建議]
---
## 📊 績效表現分析(vs 市場基準)
### 1 賺錢能力:年化報酬率 {annual_ret:.2%}
**白話文解釋:**
假設你投入 100 萬,一年後預期變成 **{100 * (1 + annual_ret):.1f} 萬**({'' if annual_ret > 0 else ''} {abs(100 * annual_ret):.1f} 萬)。
**vs 市場表現:**
- 📈 你的策略:{annual_ret:.2%}
- 📊 市場平均:{market_ctx['avg_5y_return']:.1%}{market_ctx['market_name']}
- 🎯 相對表現:{return_vs_market:+.1f}%
**評價:** {'🚀 遠超市場表現!' if return_vs_market > 50 else '✅ 打敗市場' if return_vs_market > 20 else '✓ 略勝市場' if return_vs_market > 0 else '❌ 落後市場'}
**為什麼重要?**
[解釋報酬率是投資最直觀的指標,但不能只看報酬率,還要考慮風險]
---
### 2 投資CP值:夏普比率 {sharpe:.2f}{sharpe_rating}
**白話文解釋:**
「CP值」就是夏普比率!每承受 1 元風險,你能賺 {sharpe:.2f} 元報酬。
**評級:**
- ⭐⭐⭐ 優秀 (≥ 2.0) - CP值超高!
- ⭐⭐ 良好 (≥ 1.5) - CP值不錯
- ⭐ 可接受 (≥ 1.0) - CP值及格 ← {'你在這裡' if 1.0 <= sharpe < 1.5 else ''}
- 待改善 (< 1.0) - CP值偏低
**為什麼重要?**
[解釋夏普比率是同時考慮報酬和風險的指標,比單看報酬率更全面]
---
### 3 最大虧損風險:最大回落 {mdd:.2%}
**白話文解釋:**
投入 100 萬,最慘的時候會虧到剩 **{100 * (1 + mdd):.1f} 萬**(虧損 {abs(100 * mdd):.1f} 萬)。
**vs 市場風險:**
- 你的策略:{mdd:.2%}
- 一般市場:約 -30%
- 評價:{'❌ 風險較高' if abs(mdd) > 0.30 else '✅ 風險可控'}
**心理測試:**
[問投資人是否能接受這個程度的虧損]
---
### 4 主動選股價值:Alpha {alpha:.4f} & Beta {beta:.2f}
**Alpha 超額報酬:** {'✅ +{alpha:.2%} 打敗市場' if alpha > 0.01 else '≈ 跟隨市場' if alpha >= -0.01 else '❌ 落後市場'}
**Beta 市場連動:** {'🔥 進攻型' if beta > 1.2 else ' 均衡型' if beta > 0.8 else '🛡 防禦型'}
**白話文解釋:**
- Alpha:扣除市場影響後,你的選股能力創造的額外報酬
- Beta:當市場漲 1%,你的策略預期漲 {beta:.2f}%
**為什麼重要?**
[解釋主動選股的價值,以及不同市場環境下的策略表現]
---
## 🎯 給投資人的建議
### ✅ 這個策略的優勢
[列出2-3個具體優點,用數據支持]
### ⚠ 需要注意的風險
[列出2-3個具體風險,量化說明]
### 🔧 如何優化這個策略?
[給3個具體的優化建議]
1. [建議1:例如分散持股、調整權重等]
2. [建議2:例如設定停損點]
3. [建議3:例如定期再平衡]
### 💼 適合什麼樣的投資人?
- **風險承受度**: [保守/穩健/積極]
- **投資期限**: [建議持有多久]
- **市場環境**: [適合牛市/震盪/熊市]
- **資金用途**: [閒錢/退休金/教育基金]
---
## 📈 總結
[用2-3句話總結這個策略的核心特點,並給出明確的投資建議]
---
*💡 小提醒:過去績效不代表未來表現,投資一定有風險,建議搭配專業顧問意見。*
```
**輸出要求**:
1. **字數**: 1000-1500 字(教育性內容,不能太少)
2. **語氣**: 親切、易懂,像在跟朋友聊天
3. **避免**: 「策略基本資訊回顧」等空泛標題
4. **必須**: 解釋每個指標的意義和重要性
5. **必須**: 給出具體的優化建議
"""