forked from lab/TPM
Add LLM investment advice feature with OpenAI integration\n\n- Created llm_service.py with comprehensive LLM advisor\n- Added config_openai.py for API configuration\n- Created prompts/investment_advice.py with prompt templates\n- Updated requirements.txt with OpenAI dependency\n- Modified main.py with /api/llm_advice endpoint\n- Enhanced result_view.html with LLM advice section\n- Added CSS styling for better UI\n- Created LLM_SETUP.md setup guide\n- Added test_llm_service.py for testing\n- Implemented caching, retry logic, and error handling\n- Features: strategy analysis, risk assessment, market insights
parent
fd39ea0287
commit
45e9ea7ec2
8 changed files with 957 additions and 0 deletions
@ -0,0 +1,116 @@ |
||||
# LLM 投資建議功能設置指南 |
||||
|
||||
## 功能介紹 |
||||
|
||||
LLM 投資建議功能使用 OpenAI GPT 模型為投資組合提供專業的投資分析和建議,包括: |
||||
- 策略績效評估 |
||||
- 風險分析 |
||||
- 市場適配性評估 |
||||
- 具體的改進建議 |
||||
|
||||
## 設置步驟 |
||||
|
||||
### 1. 獲取 OpenAI API 金鑰 |
||||
|
||||
1. 前往 [OpenAI API](https://platform.openai.com/api-keys) 頁面 |
||||
2. 點擊 "Create new secret key" |
||||
3. 複製生成的 API 金鑰 |
||||
|
||||
### 2. 配置 API 金鑰 |
||||
|
||||
您可以通過以下兩種方式之一設置 API 金鑰: |
||||
|
||||
#### 方法一:環境變數(推薦) |
||||
```bash |
||||
export OPENAI_API_KEY="your-api-key-here" |
||||
``` |
||||
|
||||
#### 方法二:修改配置檔案 |
||||
編輯 `config_openai.py` 檔案: |
||||
```python |
||||
OPENAI_CONFIG = { |
||||
'api_key': 'your-actual-api-key-here', |
||||
# ... 其他配置 |
||||
} |
||||
``` |
||||
|
||||
### 3. 安裝依賴 |
||||
|
||||
```bash |
||||
pip install -r requirements.txt |
||||
``` |
||||
|
||||
### 4. 啟動服務 |
||||
|
||||
```bash |
||||
docker compose up -d flask |
||||
``` |
||||
|
||||
### 5. 測試功能 |
||||
|
||||
1. 訪問任意策略詳情頁面 |
||||
2. 查看「🤖 LLM 投資建議」區塊 |
||||
3. 系統會自動生成 AI 投資建議 |
||||
|
||||
## 配置選項 |
||||
|
||||
### OpenAI 模型選擇 |
||||
|
||||
在 `config_openai.py` 中可以調整: |
||||
|
||||
```python |
||||
OPENAI_CONFIG = { |
||||
'model': 'gpt-4', # 選擇 gpt-3.5-turbo 可降低成本 |
||||
'max_tokens': 2000, # 最大回應長度 |
||||
'temperature': 0.7, # 創意程度 (0-1) |
||||
} |
||||
``` |
||||
|
||||
### 快取設定 |
||||
|
||||
```python |
||||
CACHE_CONFIG = { |
||||
'enabled': True, # 啟用快取 |
||||
'ttl': 3600, # 快取時間(秒) |
||||
'max_size': 100, # 最大快取項目數 |
||||
} |
||||
``` |
||||
|
||||
## 費用估算 |
||||
|
||||
- **GPT-4**: 約 $0.03/1K tokens |
||||
- **GPT-3.5-turbo**: 約 $0.002/1K tokens |
||||
|
||||
每個投資建議約使用 1000-2000 tokens,建議選擇合適的模型以控制成本。 |
||||
|
||||
## 故障排除 |
||||
|
||||
### 常見問題 |
||||
|
||||
1. **API 金鑰錯誤** |
||||
- 檢查 API 金鑰是否正確設置 |
||||
- 確認金鑰沒有額外的空格 |
||||
|
||||
2. **連接到服務失敗** |
||||
- 檢查 Flask 服務是否正常運行 |
||||
- 確認防火牆設定允許內部通訊 |
||||
|
||||
3. **生成建議失敗** |
||||
- 查看後端日誌確認錯誤原因 |
||||
- 檢查 OpenAI API 額度是否充足 |
||||
|
||||
### 檢查日誌 |
||||
|
||||
```bash |
||||
docker compose logs flask |
||||
``` |
||||
|
||||
## 進階配置 |
||||
|
||||
如需自訂 Prompt 模板,請編輯 `prompts/investment_advice.py` 檔案中的模板函數。 |
||||
|
||||
## 安全性注意事項 |
||||
|
||||
- 請勿將 API 金鑰提交到版本控制系統 |
||||
- 考慮使用環境變數而非硬編碼 |
||||
- 定期輪換 API 金鑰 |
||||
@ -0,0 +1,30 @@ |
||||
""" |
||||
OpenAI API 配置 |
||||
|
||||
請設置您的OpenAI API金鑰 |
||||
""" |
||||
|
||||
import os |
||||
|
||||
# OpenAI API 配置 |
||||
OPENAI_CONFIG = { |
||||
'api_key': os.getenv('OPENAI_API_KEY', 'your-api-key-here'), |
||||
'model': 'gpt-4', # 可選擇 gpt-3.5-turbo 以降低成本 |
||||
'max_tokens': 2000, |
||||
'temperature': 0.7, |
||||
'timeout': 30, # 請求超時時間(秒) |
||||
} |
||||
|
||||
# API 調用限制 |
||||
RATE_LIMITS = { |
||||
'requests_per_minute': 60, # 每分鐘最大請求數 |
||||
'max_retries': 3, # 最大重試次數 |
||||
'retry_delay': 2, # 重試間隔(秒) |
||||
} |
||||
|
||||
# 快取設定 |
||||
CACHE_CONFIG = { |
||||
'enabled': True, |
||||
'ttl': 3600, # 快取時間(秒) |
||||
'max_size': 100, # 最大快取項目數 |
||||
} |
||||
@ -0,0 +1,248 @@ |
||||
""" |
||||
LLM Investment Advisor Service |
||||
|
||||
提供投資策略的AI分析服務,包含: |
||||
- 投資建議生成 |
||||
- 風險評估 |
||||
- 市場洞察 |
||||
- Prompt工程管理 |
||||
""" |
||||
|
||||
import os |
||||
import json |
||||
import time |
||||
import logging |
||||
from typing import Dict, Any, Optional, Tuple |
||||
from functools import lru_cache |
||||
import time |
||||
|
||||
import openai |
||||
from openai import OpenAI |
||||
from config_openai import OPENAI_CONFIG, RATE_LIMITS |
||||
|
||||
# 設定日誌 |
||||
logging.basicConfig(level=logging.INFO) |
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class PromptManager: |
||||
"""管理不同的Prompt模板""" |
||||
|
||||
def __init__(self): |
||||
self.system_prompt = self._get_system_prompt() |
||||
|
||||
def _get_system_prompt(self) -> str: |
||||
"""系統提示詞""" |
||||
return """你是一位經驗豐富的專業投資顧問,擁有超過15年的投資經驗和深厚的金融知識。 |
||||
請基於提供的投資組合數據,提供專業、客觀且實用的投資建議。 |
||||
|
||||
請從以下幾個面向進行分析: |
||||
1. 整體表現評估(年化報酬、風險指標) |
||||
2. 風險收益特性分析(夏普比率、最大回落) |
||||
3. 市場環境適配性評估 |
||||
4. 具體的改進建議和操作建議 |
||||
5. 風險管理和再平衡建議 |
||||
|
||||
請確保你的回答: |
||||
- 專業且易懂,避免過度技術術語 |
||||
- 基於數據事實,客觀分析 |
||||
- 提供可操作的具體建議 |
||||
- 考慮台灣市場的特殊性(如果適用)""" |
||||
|
||||
def build_strategy_context(self, strategy_data: Dict[str, Any]) -> str: |
||||
"""將策略資料轉換為結構化context""" |
||||
return f""" |
||||
策略基本資訊: |
||||
- 策略編號:{strategy_data.get('id', 'N/A')} |
||||
- 策略名稱:{strategy_data.get('name', 'N/A')} |
||||
- 投資目標:{strategy_data.get('role', 'N/A')} |
||||
- 建立時間:{strategy_data.get('date', 'N/A')} |
||||
- 建立者:{strategy_data.get('username', 'N/A')} |
||||
|
||||
績效指標: |
||||
- 年化報酬率:{strategy_data.get('annual_ret', 0):.2%} |
||||
- 年化波動率:{strategy_data.get('vol', 0):.2%} |
||||
- 年化夏普比率:{strategy_data.get('annual_sr', 0):.2f} |
||||
- 最大回落(MDD):{strategy_data.get('mdd', 0):.2%} |
||||
- Alpha值:{strategy_data.get('alpha', 0):.4f} |
||||
- Beta值:{strategy_data.get('beta', 0):.4f} |
||||
- VaR (10%):{strategy_data.get('var10', 0):.2%} |
||||
- R-squared:{strategy_data.get('r2', 0):.4f} |
||||
- Gamma值:{strategy_data.get('gamma', 0):.4f} |
||||
|
||||
投資組合配置: |
||||
- 包含資產:{', '.join(strategy_data.get('assets', []))} |
||||
- 市場類型:{'台灣市場' if strategy_data.get('tw', True) else '美國市場'} |
||||
""" |
||||
|
||||
def get_investment_advice_prompt(self, strategy_data: Dict[str, Any]) -> str: |
||||
"""生成投資建議的完整Prompt""" |
||||
context = self.build_strategy_context(strategy_data) |
||||
|
||||
return f"""{self.system_prompt} |
||||
|
||||
{context} |
||||
|
||||
請提供詳細的投資建議分析,包含: |
||||
1. 整體表現評估:該策略的強項和弱點 |
||||
2. 風險評估:當前風險水平的評價和建議 |
||||
3. 市場適配性:該策略在當前市場環境下的適配程度 |
||||
4. 改進建議:具體的可操作改進建議 |
||||
5. 未來展望:未來3-6個月的投資建議 |
||||
|
||||
請用繁體中文回答,結構清晰,建議具體可行。""" |
||||
|
||||
|
||||
class LLMInvestmentAdvisor: |
||||
"""LLM投資顧問主類""" |
||||
|
||||
def __init__(self, api_key: Optional[str] = None): |
||||
"""初始化LLM服務""" |
||||
self.api_key = api_key or OPENAI_CONFIG['api_key'] |
||||
if not self.api_key or self.api_key == 'your-api-key-here': |
||||
raise ValueError("OpenAI API key is required. Please set OPENAI_API_KEY environment variable.") |
||||
|
||||
self.client = OpenAI( |
||||
api_key=self.api_key, |
||||
timeout=OPENAI_CONFIG.get('timeout', 30) |
||||
) |
||||
self.prompt_manager = PromptManager() |
||||
self.model = OPENAI_CONFIG.get('model', 'gpt-4') |
||||
self.max_tokens = OPENAI_CONFIG.get('max_tokens', 2000) |
||||
self.temperature = OPENAI_CONFIG.get('temperature', 0.7) |
||||
|
||||
# 快取設定 |
||||
self.cache: Dict[str, Tuple[str, float]] = {} |
||||
self.cache_timeout = 3600 # 1小時快取 |
||||
|
||||
def _is_cache_valid(self, cache_time: float) -> bool: |
||||
"""檢查快取是否有效""" |
||||
return time.time() - cache_time < self.cache_timeout |
||||
|
||||
@lru_cache(maxsize=100) |
||||
def generate_advice(self, strategy_id: str, strategy_data: Dict[str, Any]) -> str: |
||||
"""生成投資建議 |
||||
|
||||
Args: |
||||
strategy_id: 策略ID |
||||
strategy_data: 策略資料字典 |
||||
|
||||
Returns: |
||||
str: 投資建議文本 |
||||
""" |
||||
cache_key = f"advice_{strategy_id}_{hash(str(strategy_data))}" |
||||
|
||||
# 檢查快取 |
||||
if cache_key in self.cache: |
||||
advice, cache_time = self.cache[cache_key] |
||||
if self._is_cache_valid(cache_time): |
||||
logger.info(f"Returning cached advice for strategy {strategy_id}") |
||||
return advice |
||||
|
||||
try: |
||||
# 構建prompt |
||||
prompt = self.prompt_manager.get_investment_advice_prompt(strategy_data) |
||||
|
||||
# 調用OpenAI API |
||||
response = self._call_openai_with_retry(prompt) |
||||
|
||||
# 快取結果 |
||||
self.cache[cache_key] = (response, time.time()) |
||||
|
||||
logger.info(f"Generated new advice for strategy {strategy_id}") |
||||
return response |
||||
|
||||
except Exception as e: |
||||
logger.error(f"Error generating advice for strategy {strategy_id}: {str(e)}") |
||||
return self._get_fallback_advice(strategy_data) |
||||
|
||||
def _call_openai_with_retry(self, prompt: str) -> str: |
||||
"""調用OpenAI API,包含重試機制 |
||||
|
||||
Args: |
||||
prompt: 完整的prompt |
||||
|
||||
Returns: |
||||
str: API回應內容 |
||||
""" |
||||
max_retries = RATE_LIMITS.get('max_retries', 3) |
||||
retry_delay = RATE_LIMITS.get('retry_delay', 2) |
||||
|
||||
for attempt in range(max_retries): |
||||
try: |
||||
response = self.client.chat.completions.create( |
||||
model=self.model, |
||||
messages=[ |
||||
{"role": "system", "content": "你是一位專業的投資顧問。"}, |
||||
{"role": "user", "content": prompt} |
||||
], |
||||
max_tokens=self.max_tokens, |
||||
temperature=self.temperature, |
||||
top_p=0.9 |
||||
) |
||||
|
||||
return response.choices[0].message.content.strip() |
||||
|
||||
except openai.RateLimitError as e: |
||||
if attempt < max_retries - 1: |
||||
# 指數退避:1s, 2s, 4s |
||||
wait_time = retry_delay ** attempt |
||||
logger.warning(f"Rate limit exceeded, retrying in {wait_time}s... (attempt {attempt + 1}/{max_retries})") |
||||
time.sleep(wait_time) |
||||
continue |
||||
else: |
||||
logger.error(f"Rate limit exceeded after {max_retries} attempts") |
||||
raise e |
||||
|
||||
except openai.APIError as e: |
||||
if attempt < max_retries - 1: |
||||
wait_time = retry_delay ** attempt |
||||
logger.warning(f"API error, retrying in {wait_time}s... (attempt {attempt + 1}/{max_retries}): {str(e)}") |
||||
time.sleep(wait_time) |
||||
continue |
||||
else: |
||||
logger.error(f"API error after {max_retries} attempts: {str(e)}") |
||||
raise e |
||||
|
||||
except Exception as e: |
||||
logger.error(f"Unexpected error calling OpenAI: {str(e)}") |
||||
raise e |
||||
|
||||
def _get_fallback_advice(self, strategy_data: Dict[str, Any]) -> str: |
||||
"""獲取fallback投資建議""" |
||||
annual_ret = strategy_data.get('annual_ret', 0) |
||||
vol = strategy_data.get('vol', 0) |
||||
sharpe = strategy_data.get('annual_sr', 0) |
||||
|
||||
return f""" |
||||
基於您的投資策略數據,我提供以下初步分析: |
||||
|
||||
📊 **表現評估**: |
||||
- 年化報酬率:{annual_ret:.2%} - {'表現良好' if annual_ret > 0.1 else '有改進空間'} |
||||
- 年化波動率:{vol:.2%} - {'風險適中' if vol < 0.2 else '風險較高'} |
||||
- 夏普比率:{sharpe:.2f} - {'風險調整後報酬優良' if sharpe > 1 else '有改進空間'} |
||||
|
||||
💡 **初步建議**: |
||||
1. 持續監控市場變化 |
||||
2. 定期檢視投資組合配置 |
||||
3. 考慮分散投資降低風險 |
||||
|
||||
*注意:此為預設建議,如需詳細分析請稍後再試。* |
||||
""" |
||||
|
||||
def clear_cache(self): |
||||
"""清除快取""" |
||||
self.cache.clear() |
||||
self.generate_advice.cache_clear() |
||||
logger.info("LLM advice cache cleared") |
||||
|
||||
|
||||
# 創建全域實例 |
||||
llm_advisor = None |
||||
|
||||
def get_llm_advisor() -> LLMInvestmentAdvisor: |
||||
"""獲取LLM顧問實例""" |
||||
global llm_advisor |
||||
if llm_advisor is None: |
||||
llm_advisor = LLMInvestmentAdvisor() |
||||
return llm_advisor |
||||
@ -0,0 +1,159 @@ |
||||
""" |
||||
投資建議相關的Prompt模板 |
||||
|
||||
包含: |
||||
- 基本投資建議模板 |
||||
- 進階分析模板 |
||||
- 不同市場環境的模板 |
||||
""" |
||||
|
||||
from typing import Dict, Any |
||||
|
||||
|
||||
def get_basic_investment_prompt(strategy_data: Dict[str, Any]) -> str: |
||||
"""基本投資建議Prompt""" |
||||
return f""" |
||||
請基於以下投資策略數據,提供專業的投資建議: |
||||
|
||||
策略概況: |
||||
- 年化報酬率:{strategy_data.get('annual_ret', 0):.2%} |
||||
- 年化波動率:{strategy_data.get('vol', 0):.2%} |
||||
- 夏普比率:{strategy_data.get('annual_sr', 0):.2f} |
||||
- 最大回落:{strategy_data.get('mdd', 0):.2%} |
||||
- 投資目標:{strategy_data.get('role', 'N/A')} |
||||
|
||||
投資組合包含:{', '.join(strategy_data.get('assets', []))} |
||||
|
||||
請提供: |
||||
1. 整體表現評估 |
||||
2. 風險收益分析 |
||||
3. 改進建議 |
||||
""" |
||||
|
||||
|
||||
def get_comprehensive_analysis_prompt(strategy_data: Dict[str, Any]) -> str: |
||||
"""全面分析Prompt""" |
||||
return f""" |
||||
作為專業投資顧問,請對以下投資策略進行全面分析並提供建議: |
||||
|
||||
【策略基本資訊】 |
||||
- 策略名稱:{strategy_data.get('name', 'N/A')} |
||||
- 投資目標:{strategy_data.get('role', 'N/A')} |
||||
- 市場類型:{'台灣市場' if strategy_data.get('tw', True) else '美國市場'} |
||||
|
||||
【關鍵績效指標】 |
||||
- 年化報酬率:{strategy_data.get('annual_ret', 0):.2%} |
||||
- 年化波動率:{strategy_data.get('vol', 0):.2%} |
||||
- 年化夏普比率:{strategy_data.get('annual_sr', 0):.2f} |
||||
- 最大回落(MDD):{strategy_data.get('mdd', 0):.2%} |
||||
- Alpha值:{strategy_data.get('alpha', 0):.4f} |
||||
- Beta值:{strategy_data.get('beta', 0):.4f} |
||||
- VaR (95%):{strategy_data.get('var10', 0):.2%} |
||||
- R-squared:{strategy_data.get('r2', 0):.4f} |
||||
|
||||
【投資組合配置】 |
||||
持有資產:{', '.join(strategy_data.get('assets', []))} |
||||
權重配置:{strategy_data.get('weight', {{}}).get('columns', [])} |
||||
|
||||
【分析要求】 |
||||
請從以下面向提供詳細分析和建議: |
||||
|
||||
1. **績效評估**: |
||||
- 與市場基準比較(台灣加權指數或S&P 500) |
||||
- 歷史表現趨勢分析 |
||||
- 相對表現評價 |
||||
|
||||
2. **風險分析**: |
||||
- 整體風險水平評估 |
||||
- 主要風險來源識別 |
||||
- 風險調整後報酬分析 |
||||
|
||||
3. **市場適配性**: |
||||
- 當前市場環境適配程度 |
||||
- 經濟週期位置評估 |
||||
- 市場波動性影響分析 |
||||
|
||||
4. **投資建議**: |
||||
- 資產配置優化建議 |
||||
- 風險管理改進方案 |
||||
- 定期調整和再平衡建議 |
||||
|
||||
5. **未來展望**: |
||||
- 未來3-6個月投資展望 |
||||
- 潛在風險預警 |
||||
- 操作建議和時機點 |
||||
|
||||
請用專業、客觀且易懂的語言回答,使用繁體中文。 |
||||
""" |
||||
|
||||
|
||||
def get_risk_focused_prompt(strategy_data: Dict[str, Any]) -> str: |
||||
"""風險導向分析Prompt""" |
||||
return f""" |
||||
請重點分析以下投資策略的風險特性,並提供風險管理建議: |
||||
|
||||
【風險指標】 |
||||
- 年化波動率:{strategy_data.get('vol', 0):.2%} |
||||
- 最大回落:{strategy_data.get('mdd', 0):.2%} |
||||
- Beta值:{strategy_data.get('beta', 0):.4f} |
||||
- VaR (10%):{strategy_data.get('var10', 0):.2%} |
||||
- 夏普比率:{strategy_data.get('annual_sr', 0):.2f} |
||||
|
||||
【風險分析要求】 |
||||
1. 評估當前風險水平(低/中/高) |
||||
2. 識別主要風險來源 |
||||
3. 分析風險與報酬的配比 |
||||
4. 提供風險降低策略 |
||||
5. 建議風險監控指標 |
||||
|
||||
請提供具體、可操作的風險管理建議。 |
||||
""" |
||||
|
||||
|
||||
def get_market_context_prompt(strategy_data: Dict[str, Any], market_condition: str = "normal") -> str: |
||||
"""市場環境特定Prompt""" |
||||
market_contexts = { |
||||
"bull": "目前市場處於牛市環境", |
||||
"bear": "目前市場處於熊市環境", |
||||
"volatile": "目前市場波動劇烈", |
||||
"normal": "目前市場環境正常" |
||||
} |
||||
|
||||
context = market_contexts.get(market_condition, market_contexts["normal"]) |
||||
|
||||
return f""" |
||||
{context},請針對以下投資策略提供相應的投資建議: |
||||
|
||||
【策略資訊】 |
||||
- 年化報酬率:{strategy_data.get('annual_ret', 0):.2%} |
||||
- 年化波動率:{strategy_data.get('vol', 0):.2%} |
||||
- 投資目標:{strategy_data.get('role', 'N/A')} |
||||
|
||||
【市場適配建議】 |
||||
根據當前市場環境,請分析: |
||||
1. 該策略在當前環境下的適配程度 |
||||
2. 可能的調整建議 |
||||
3. 風險控制措施 |
||||
4. 時機選擇建議 |
||||
|
||||
請提供針對當前市場環境的具體操作建議。 |
||||
""" |
||||
|
||||
|
||||
def build_custom_prompt(strategy_data: Dict[str, Any], analysis_type: str, **kwargs) -> str: |
||||
"""自訂Prompt生成器 |
||||
|
||||
Args: |
||||
strategy_data: 策略資料 |
||||
analysis_type: 分析類型 ('basic', 'comprehensive', 'risk', 'market') |
||||
**kwargs: 額外參數 |
||||
""" |
||||
templates = { |
||||
'basic': get_basic_investment_prompt, |
||||
'comprehensive': get_comprehensive_analysis_prompt, |
||||
'risk': get_risk_focused_prompt, |
||||
'market': lambda data: get_market_context_prompt(data, kwargs.get('condition', 'normal')) |
||||
} |
||||
|
||||
template_func = templates.get(analysis_type, get_basic_investment_prompt) |
||||
return template_func(strategy_data) |
||||
Loading…
Reference in new issue