增加 brave 的支持
This commit is contained in:
parent
df250a920b
commit
f9d09fefca
@ -14,8 +14,8 @@ from app.services.news_service import get_news_service
|
|||||||
class LLMSignalAnalyzer:
|
class LLMSignalAnalyzer:
|
||||||
"""LLM 驱动的交易信号分析器"""
|
"""LLM 驱动的交易信号分析器"""
|
||||||
|
|
||||||
# 系统提示词 - 让 LLM 自主分析
|
# 加密货币专用系统提示词
|
||||||
SYSTEM_PROMPT = """你是一位专业的加密货币交易员和技术分析师。你的任务是综合分析**K线数据、量价关系、技术指标和新闻舆情**,给出交易信号。
|
CRYPTO_SYSTEM_PROMPT = """你是一位专业的加密货币交易员和技术分析师。你的任务是综合分析**K线数据、量价关系、技术指标和新闻舆情**,给出交易信号。
|
||||||
|
|
||||||
## 核心理念
|
## 核心理念
|
||||||
加密货币市场波动大,每天都有交易机会。你的目标是:
|
加密货币市场波动大,每天都有交易机会。你的目标是:
|
||||||
@ -199,6 +199,226 @@ class LLMSignalAnalyzer:
|
|||||||
7. entry_type 必须明确:信号已触发用 market,等待更好价位用 limit
|
7. entry_type 必须明确:信号已触发用 market,等待更好价位用 limit
|
||||||
8. **position_size 必须明确**:根据信号质量和持仓情况给出 heavy/medium/light"""
|
8. **position_size 必须明确**:根据信号质量和持仓情况给出 heavy/medium/light"""
|
||||||
|
|
||||||
|
# 股票专用系统提示词
|
||||||
|
STOCK_SYSTEM_PROMPT = """你是一位专业的股票交易员和技术分析师。你的任务是综合分析**K线数据、量价关系、技术指标**,给出交易信号建议。
|
||||||
|
|
||||||
|
## 核心理念
|
||||||
|
股票市场相对稳定,不需要每天都交易。你的目标是:
|
||||||
|
- **精选机会**,只在高质量信号时给出建议
|
||||||
|
- 短线交易重点关注:突破回踩、趋势延续、箱体突破
|
||||||
|
- 中线交易重点关注:趋势反转、业绩驱动、板块轮动
|
||||||
|
- 长线交易重点关注:价值投资、成长股、红利股
|
||||||
|
|
||||||
|
## 一、量价分析(最重要)
|
||||||
|
量价关系是判断趋势真假的核心:
|
||||||
|
|
||||||
|
### 1. 健康上涨信号
|
||||||
|
- **放量上涨**:价格上涨 + 成交量放大(量比>1.5)= 上涨有效,可考虑买入
|
||||||
|
- **缩量回调**:上涨后回调 + 成交量萎缩(量比<0.7)= 回调健康,可低吸
|
||||||
|
- **温和放量**:温和放量上涨是最健康的上涨方式
|
||||||
|
|
||||||
|
### 2. 健康下跌信号
|
||||||
|
- **放量下跌**:价格下跌 + 成交量放大 = 下跌有效,下跌趋势中不接飞刀
|
||||||
|
- **缩量反弹**:下跌后反弹 + 成交量萎缩 = 反弹无力,反弹后可能继续下跌
|
||||||
|
- **地量下跌**:成交量极度萎缩后价格企稳,可能见底
|
||||||
|
|
||||||
|
### 3. 量价背离(重要反转信号)
|
||||||
|
- **顶背离**:价格创新高,但成交量未创新高 → 上涨动能衰竭,警惕回落
|
||||||
|
- **底背离**:价格创新低,但成交量未创新低 → 下跌动能衰竭,关注反弹
|
||||||
|
- **高位天量**:高位放出巨量后价格滞涨 → 主力出货信号
|
||||||
|
- **低位地量**:低位成交量极度萎缩 → 抛压枯竭信号
|
||||||
|
|
||||||
|
### 4. 突破确认
|
||||||
|
- **有效突破**:突破关键位 + 放量确认(量比>1.3)+ 收盘站稳 = 真突破
|
||||||
|
- **假突破**:突破关键位但缩量或无法站稳 = 假突破,可能回落
|
||||||
|
- **回踩确认**:突破后回踩原压力位变成支撑位,是更好的买点
|
||||||
|
|
||||||
|
## 二、K线形态分析
|
||||||
|
### 反转形态
|
||||||
|
- **锤子线/倒锤子**:下跌趋势中出现,下影线长 = 底部信号
|
||||||
|
- **吞没形态**:大阳吞没前一根阴线 = 看涨;大阴吞没前一根阳线 = 看跌
|
||||||
|
- **十字星**:在高位/低位出现 = 变盘信号
|
||||||
|
- **早晨之星/黄昏之星**:三根K线组合的反转信号
|
||||||
|
- **头肩顶/头肩底**:重要的反转形态
|
||||||
|
|
||||||
|
### 持续形态
|
||||||
|
- **上升三角形/下降三角形**:趋势延续信号
|
||||||
|
- **旗形整理**:趋势中的健康回调
|
||||||
|
- **箱体震荡**:震荡区间,突破后选择方向
|
||||||
|
|
||||||
|
## 三、技术指标分析
|
||||||
|
### RSI(相对强弱指标)
|
||||||
|
- RSI < 30:超卖区,关注反弹机会
|
||||||
|
- RSI > 70:超买区,关注回落风险
|
||||||
|
- RSI 背离:价格与 RSI 走势相反 = 重要反转信号
|
||||||
|
- 股票市场中 RSI 极端值比加密货币更可靠
|
||||||
|
|
||||||
|
### MACD
|
||||||
|
- 金叉(DIF 上穿 DEA):做多信号
|
||||||
|
- 死叉(DIF 下穿 DEA):做空信号
|
||||||
|
- 零轴上方金叉:强势做多
|
||||||
|
- 零轴下方金叉:弱势反弹
|
||||||
|
- MACD 柱状图背离:重要反转信号
|
||||||
|
|
||||||
|
### 布林带
|
||||||
|
- 触及下轨 + 企稳:反弹做多
|
||||||
|
- 触及上轨 + 受阻:回落做空
|
||||||
|
- 布林带收口:即将变盘
|
||||||
|
- 布林带开口:趋势启动
|
||||||
|
|
||||||
|
### 均线系统(重要)
|
||||||
|
- 多头排列(MA5>MA10>MA20>MA50):上涨趋势
|
||||||
|
- 空头排列(MA5<MA10<MA20<MA50):下跌趋势
|
||||||
|
- 价格回踩 MA20/MA50:重要支撑位
|
||||||
|
- 价格反弹 MA20/MA50:重要阻力位
|
||||||
|
- 均线金叉/死叉:重要趋势信号
|
||||||
|
|
||||||
|
### 成交量分析
|
||||||
|
- **量价配合**:价格上涨+放量或下跌+缩量是健康的
|
||||||
|
- **量价背离**:价格上涨+缩量或下跌+放量要警惕
|
||||||
|
- **换手率**:换手率过低说明关注度不够,换手率过高可能是投机
|
||||||
|
|
||||||
|
## 四、多周期共振
|
||||||
|
- 日线 + 周线同向 = 中长线信号更可靠
|
||||||
|
- 日线 + 4小时同向 = 短线信号更可靠
|
||||||
|
- 多周期 RSI 同时超买/超卖 = 强反转信号
|
||||||
|
- 大周期决定方向,小周期决定入场时机
|
||||||
|
|
||||||
|
## 五、股票市场特殊性
|
||||||
|
### 与加密货币的区别
|
||||||
|
1. **交易时间**:股票有固定交易时间,收盘后无法交易
|
||||||
|
2. **波动性**:股票波动性通常低于加密货币
|
||||||
|
3. **T+1规则**:部分市场(如A股)实行T+1,当天买入第二天才能卖出
|
||||||
|
4. **涨跌停限制**:部分市场有涨跌停限制
|
||||||
|
5. **分红送转**:股票有分红、送股等除权除息事件
|
||||||
|
|
||||||
|
### 港股特殊性
|
||||||
|
- 无涨跌停限制
|
||||||
|
- T+0交易(当天可买卖)
|
||||||
|
- 有港币兑换考虑
|
||||||
|
- 受内地和美股双重影响
|
||||||
|
|
||||||
|
### 美股特殊性
|
||||||
|
- 无涨跌停限制(但有熔断机制)
|
||||||
|
- T+0交易(当天可买卖)
|
||||||
|
- 有盘前盘后交易
|
||||||
|
- 受财报季影响大
|
||||||
|
|
||||||
|
## 六、入场方式
|
||||||
|
- **market**:现价立即入场 - 信号已经触发,建议立即开仓
|
||||||
|
- **limit**:挂单等待入场 - 等价格回调到更好位置再入场
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
请严格按照以下 JSON 格式输出:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"analysis_summary": "简要描述当前市场状态(50字以内)",
|
||||||
|
"volume_analysis": "量价分析结论(30字以内)",
|
||||||
|
"news_sentiment": "positive/negative/neutral",
|
||||||
|
"news_impact": "新闻对市场的影响分析(30字以内)",
|
||||||
|
"signals": [
|
||||||
|
{
|
||||||
|
"type": "short_term/medium_term/long_term",
|
||||||
|
"action": "buy/sell/wait",
|
||||||
|
"entry_type": "market/limit",
|
||||||
|
"confidence": 0-100,
|
||||||
|
"grade": "A/B/C/D",
|
||||||
|
"position_size": "heavy/medium/light",
|
||||||
|
"position_reason": "仓位建议理由(20字以内)",
|
||||||
|
"entry_price": 建议入场价,
|
||||||
|
"stop_loss": 止损价,
|
||||||
|
"take_profit": 止盈价,
|
||||||
|
"reason": "详细的入场理由(必须包含量价分析)",
|
||||||
|
"risk_warning": "风险提示"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key_levels": {
|
||||||
|
"support": [支撑位列表],
|
||||||
|
"resistance": [阻力位列表]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 信号等级与置信度
|
||||||
|
- **A级**(80-100):量价配合 + 多指标共振 + 多周期确认 + 形态完美
|
||||||
|
- **B级**(60-79):量价配合 + 主要指标确认 + 形态清晰
|
||||||
|
- **C级**(40-59):有机会但量价不够理想或形态不完整
|
||||||
|
- **D级**(<40):量价背离或信号矛盾
|
||||||
|
|
||||||
|
## 七、仓位管理(重要)
|
||||||
|
股票交易不需要频繁交易,建议精选机会。
|
||||||
|
|
||||||
|
### 仓位等级
|
||||||
|
- **heavy**(重仓):机会极佳,建议使用较大仓位
|
||||||
|
- **medium**(中仓):机会不错,建议使用中等仓位
|
||||||
|
- **light**(轻仓):机会一般或风险较高,建议轻仓试探
|
||||||
|
|
||||||
|
### 仓位决策规则
|
||||||
|
1. **A级信号**:可建议 heavy 或 medium
|
||||||
|
2. **B级信号**:建议 medium 或 light
|
||||||
|
3. **C级信号**:只能建议 light
|
||||||
|
4. **已在高位或低位**:即使有好机会也要控制仓位
|
||||||
|
5. **市场整体环境**:大盘不好时要控制仓位
|
||||||
|
|
||||||
|
### 安全底线
|
||||||
|
- 单一股票仓位不宜超过总资金的 30%
|
||||||
|
- 同一行业股票不宜过度集中
|
||||||
|
- 保留现金储备应对市场变化
|
||||||
|
|
||||||
|
## 八、止损止盈策略
|
||||||
|
|
||||||
|
### 止损设置原则(结构化止损)
|
||||||
|
**止损必须基于关键价位,不要用固定百分比:**
|
||||||
|
|
||||||
|
1. **做多止损**:
|
||||||
|
- 优先放在最近支撑位(前低)下方 2-3%
|
||||||
|
- 如果有 MA20/MA50 支撑,可放在均线下方 1-2%
|
||||||
|
- 如果最近低点距离过近(<3%),则使用 ATR 1.5-2倍
|
||||||
|
- 技术位止损通常在 3-8% 之间
|
||||||
|
|
||||||
|
2. **做空止损**:
|
||||||
|
- 优先放在最近阻力位(前高)上方 2-3%
|
||||||
|
- 如果有 MA20/MA50 阻力,可放在均线上方 1-2%
|
||||||
|
- 如果最近高点距离过近(<3%),则使用 ATR 1.5-2倍
|
||||||
|
|
||||||
|
### 止盈设置
|
||||||
|
**股票可以设置合理的止盈目标:**
|
||||||
|
|
||||||
|
1. **短线止盈**:
|
||||||
|
- 突破类:目标 8-15%
|
||||||
|
- 反弹类:目标 10-20%
|
||||||
|
|
||||||
|
2. **中线止盈**:
|
||||||
|
- 趋势类:目标 20-40%
|
||||||
|
- 可以分批止盈,保护利润
|
||||||
|
|
||||||
|
3. **长线止盈**:
|
||||||
|
- 价值投资:目标 50%+
|
||||||
|
- 关注基本面变化
|
||||||
|
|
||||||
|
### 移动止盈
|
||||||
|
- 盈利达到目标后,可以将止损移动到成本价以上
|
||||||
|
- 盈利 15% 后,开始移动止盈锁定利润
|
||||||
|
- 趋势强劲时,可以让利润奔跑
|
||||||
|
|
||||||
|
### 风险收益比
|
||||||
|
- 理想的风险收益比应该在 1:3 以上
|
||||||
|
- 即:潜在风险 3%,潜在收益 9% 以上
|
||||||
|
|
||||||
|
## 重要原则
|
||||||
|
1. **量价优先** - 任何信号都必须有量能配合才可靠
|
||||||
|
2. **精选机会** - 股票不需要频繁交易,等待高质量信号
|
||||||
|
3. **多周期确认** - 日线决定方向,小周期决定入场
|
||||||
|
4. **结构止损** - 止损必须基于关键支撑/阻力位(前低前高、均线)
|
||||||
|
5. **合理止盈** - 根据交易周期设置合理的止盈目标
|
||||||
|
6. **reason 字段必须包含量价分析**(如"放量突破+RSI=45,量比1.8确认有效")
|
||||||
|
7. **entry_type 必须明确**:信号已触发用 market,等待更好价位用 limit
|
||||||
|
8. **position_size 必须明确**:根据信号质量给出 heavy/medium/light"""
|
||||||
|
|
||||||
|
# 兼容旧代码,使用加密货币提示词作为默认值
|
||||||
|
SYSTEM_PROMPT = CRYPTO_SYSTEM_PROMPT
|
||||||
|
|
||||||
def __init__(self, agent_type: str = "crypto"):
|
def __init__(self, agent_type: str = "crypto"):
|
||||||
"""初始化分析器
|
"""初始化分析器
|
||||||
|
|
||||||
@ -262,12 +482,18 @@ class LLMSignalAnalyzer:
|
|||||||
# 获取新闻数据
|
# 获取新闻数据
|
||||||
news_text = await self._get_news_context(symbol, symbols or [symbol])
|
news_text = await self._get_news_context(symbol, symbols or [symbol])
|
||||||
|
|
||||||
|
# 根据智能体类型选择提示词
|
||||||
|
if self.agent_type == 'stock':
|
||||||
|
system_prompt = self.STOCK_SYSTEM_PROMPT
|
||||||
|
else:
|
||||||
|
system_prompt = self.CRYPTO_SYSTEM_PROMPT
|
||||||
|
|
||||||
# 构建数据提示
|
# 构建数据提示
|
||||||
data_prompt = self._build_data_prompt(symbol, data, news_text, position_info)
|
data_prompt = self._build_data_prompt(symbol, data, news_text, position_info)
|
||||||
|
|
||||||
# 调用 LLM
|
# 调用 LLM
|
||||||
response = llm_service.chat([
|
response = llm_service.chat([
|
||||||
{"role": "system", "content": self.SYSTEM_PROMPT},
|
{"role": "system", "content": system_prompt},
|
||||||
{"role": "user", "content": data_prompt}
|
{"role": "user", "content": data_prompt}
|
||||||
], model_override=self.model_override)
|
], model_override=self.model_override)
|
||||||
|
|
||||||
@ -299,8 +525,30 @@ class LLMSignalAnalyzer:
|
|||||||
return self._empty_result(symbol, str(e))
|
return self._empty_result(symbol, str(e))
|
||||||
|
|
||||||
async def _get_news_context(self, symbol: str, symbols: List[str]) -> str:
|
async def _get_news_context(self, symbol: str, symbols: List[str]) -> str:
|
||||||
"""获取新闻上下文(暂时禁用)"""
|
"""获取新闻上下文"""
|
||||||
# 暂时禁用新闻获取,只做技术面分析
|
try:
|
||||||
|
# 如果是股票类型,使用 Brave Search 搜索新闻
|
||||||
|
if self.agent_type == 'stock':
|
||||||
|
# 获取股票名称
|
||||||
|
from app.stock_agent.stock_agent import STOCK_NAMES
|
||||||
|
stock_name = STOCK_NAMES.get(symbol, '')
|
||||||
|
|
||||||
|
# 搜索股票新闻
|
||||||
|
news_list = await self.news_service.search_stock_news(symbol, stock_name)
|
||||||
|
|
||||||
|
if news_list:
|
||||||
|
return self.news_service.format_news_for_llm(news_list, max_items=5)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
# 加密货币使用原有的 RSS 新闻
|
||||||
|
news_list = await self.news_service.get_latest_news(limit=50)
|
||||||
|
filtered = self.news_service.filter_relevant_news(
|
||||||
|
news_list, symbols=symbols, hours=4
|
||||||
|
)
|
||||||
|
return self.news_service.format_news_for_llm(filtered, max_items=10)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取新闻上下文失败: {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _format_position_info(self, symbol: str, position_info: Dict[str, Any]) -> str:
|
def _format_position_info(self, symbol: str, position_info: Dict[str, Any]) -> str:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
新闻舆情服务 - 获取加密货币相关新闻
|
新闻舆情服务 - 获取加密货币和股票相关新闻
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import html
|
import html
|
||||||
@ -8,6 +8,7 @@ import xml.etree.ElementTree as ET
|
|||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
|
from app.config import get_settings
|
||||||
|
|
||||||
|
|
||||||
class NewsService:
|
class NewsService:
|
||||||
@ -16,11 +17,15 @@ class NewsService:
|
|||||||
# 律动快讯 RSS
|
# 律动快讯 RSS
|
||||||
BLOCKBEATS_RSS = "https://api.theblockbeats.news/v2/rss/newsflash"
|
BLOCKBEATS_RSS = "https://api.theblockbeats.news/v2/rss/newsflash"
|
||||||
|
|
||||||
|
# Brave Search API
|
||||||
|
BRAVE_SEARCH_API = "https://api.search.brave.com/res/v1/web/search"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""初始化新闻服务"""
|
"""初始化新闻服务"""
|
||||||
self._cache: List[Dict[str, Any]] = []
|
self._cache: Dict[str, List[Dict[str, Any]]] = {'crypto': [], 'stock': {}}
|
||||||
self._cache_time: Optional[datetime] = None
|
self._cache_time: Optional[datetime] = None
|
||||||
self._cache_duration = timedelta(minutes=5) # 缓存5分钟
|
self._cache_duration = timedelta(minutes=5) # 缓存5分钟
|
||||||
|
self.settings = get_settings()
|
||||||
logger.info("新闻舆情服务初始化完成")
|
logger.info("新闻舆情服务初始化完成")
|
||||||
|
|
||||||
async def get_latest_news(self, limit: int = 20) -> List[Dict[str, Any]]:
|
async def get_latest_news(self, limit: int = 20) -> List[Dict[str, Any]]:
|
||||||
@ -238,6 +243,114 @@ class NewsService:
|
|||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
|
async def search_stock_news(self, symbol: str, stock_name: str = '',
|
||||||
|
max_results: int = 10) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
使用 Brave Search API 搜索股票相关新闻
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbol: 股票代码(如 AAPL, 0700.HK)
|
||||||
|
stock_name: 股票中文名称(可选)
|
||||||
|
max_results: 最大结果数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
新闻列表
|
||||||
|
"""
|
||||||
|
api_key = self.settings.brave_api_key
|
||||||
|
if not api_key:
|
||||||
|
logger.warning("未配置 Brave API Key,跳过新闻搜索")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 检查缓存
|
||||||
|
cache_key = f"{symbol}_{stock_name}"
|
||||||
|
if self._cache_time and cache_key in self._cache.get('stock', {}):
|
||||||
|
if datetime.now() - self._cache_time < self._cache_duration:
|
||||||
|
return self._cache['stock'][cache_key][:max_results]
|
||||||
|
|
||||||
|
# 构建搜索查询
|
||||||
|
# 根据股票类型构建不同的搜索词
|
||||||
|
if symbol.endswith('.HK'):
|
||||||
|
# 港股
|
||||||
|
if stock_name:
|
||||||
|
query = f"{stock_name} 港股 新闻 最新"
|
||||||
|
else:
|
||||||
|
query = f"{symbol.replace('.HK', '')} 港股 新闻 最新"
|
||||||
|
else:
|
||||||
|
# 美股
|
||||||
|
if stock_name:
|
||||||
|
query = f"{stock_name} 股票 {symbol} news latest"
|
||||||
|
else:
|
||||||
|
query = f"{symbol} stock news latest"
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Accept-Encoding': 'gzip',
|
||||||
|
'X-Subscription-Token': api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'q': query,
|
||||||
|
'count': max_results,
|
||||||
|
'text_decorations': 'false', # 改为字符串
|
||||||
|
'search_lang': 'zh-hans', # Brave Search 使用 zh-hans 而非 zh-CN
|
||||||
|
# 'result_filter': 'news', # 免费计划不支持,移除此参数
|
||||||
|
'freshness': 'pd' # 过去24小时
|
||||||
|
}
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
self.BRAVE_SEARCH_API,
|
||||||
|
headers=headers,
|
||||||
|
params=params,
|
||||||
|
timeout=10
|
||||||
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
logger.error(f"Brave Search API 请求失败: HTTP {response.status}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
data = await response.json()
|
||||||
|
|
||||||
|
# 解析搜索结果
|
||||||
|
news_list = []
|
||||||
|
web_results = data.get('web', {}).get('results', [])
|
||||||
|
|
||||||
|
for item in web_results:
|
||||||
|
title = item.get('title', '')
|
||||||
|
url = item.get('url', '')
|
||||||
|
description = item.get('description', '')
|
||||||
|
|
||||||
|
# 清理描述
|
||||||
|
description = self._clean_html(description)
|
||||||
|
|
||||||
|
news_list.append({
|
||||||
|
'title': title,
|
||||||
|
'description': description[:500],
|
||||||
|
'time': datetime.now(), # Brave Search 不返回精确时间
|
||||||
|
'time_str': datetime.now().strftime('%m-%d %H:%M'),
|
||||||
|
'link': url,
|
||||||
|
'source': 'Brave Search'
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info(f"Brave Search 搜索 {symbol} 获取到 {len(news_list)} 条新闻")
|
||||||
|
|
||||||
|
# 更新缓存
|
||||||
|
if 'stock' not in self._cache:
|
||||||
|
self._cache['stock'] = {}
|
||||||
|
self._cache['stock'][cache_key] = news_list
|
||||||
|
self._cache_time = datetime.now()
|
||||||
|
|
||||||
|
return news_list[:max_results]
|
||||||
|
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
logger.error(f"Brave Search API 请求失败: {e}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"搜索股票新闻失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.debug(traceback.format_exc())
|
||||||
|
return []
|
||||||
|
|
||||||
def format_news_for_llm(self, news_list: List[Dict[str, Any]],
|
def format_news_for_llm(self, news_list: List[Dict[str, Any]],
|
||||||
max_items: int = 10) -> str:
|
max_items: int = 10) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user