update
This commit is contained in:
parent
946f1419f1
commit
24e8a5c1a2
@ -99,12 +99,14 @@ class SkillManager:
|
||||
skill = self.get_skill(skill_name)
|
||||
|
||||
if not skill:
|
||||
logger.error(f"❌ 技能不存在: {skill_name}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"技能不存在: {skill_name}"
|
||||
}
|
||||
|
||||
if not skill.enabled:
|
||||
logger.warning(f"⚠️ 技能已禁用: {skill_name}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"技能已禁用: {skill_name}"
|
||||
@ -113,6 +115,7 @@ class SkillManager:
|
||||
# 验证参数
|
||||
valid, error = skill.validate_params(**kwargs)
|
||||
if not valid:
|
||||
logger.error(f"❌ 技能参数验证失败 {skill_name}: {error}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": error
|
||||
@ -120,13 +123,15 @@ class SkillManager:
|
||||
|
||||
# 执行技能
|
||||
try:
|
||||
logger.info(f"🚀 开始执行技能: {skill_name}, 参数: {kwargs}")
|
||||
result = await skill.execute(**kwargs)
|
||||
logger.info(f"✅ 技能执行成功: {skill_name}")
|
||||
return {
|
||||
"success": True,
|
||||
"data": result
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"技能执行失败 {skill_name}: {e}")
|
||||
logger.error(f"❌ 技能执行失败 {skill_name}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
@ -249,13 +254,14 @@ class SkillManager:
|
||||
# 按优先级顺序执行
|
||||
for priority in sorted(priority_groups.keys()):
|
||||
skill_group = priority_groups[priority]
|
||||
logger.info(f"执行优先级 {priority} 的技能: {[s['name'] for s in skill_group]}")
|
||||
logger.info(f"📋 执行优先级 {priority} 的技能: {[s['name'] for s in skill_group]}")
|
||||
|
||||
# 同一优先级的技能并行执行
|
||||
tasks = []
|
||||
for skill_info in skill_group:
|
||||
params = skill_info['params'].copy()
|
||||
params['stock_code'] = stock_code
|
||||
logger.info(f" ➡️ 准备执行技能: {skill_info['name']}, 原因: {skill_info.get('reason', '未知')}")
|
||||
task = self.execute_skill(skill_info['name'], **params)
|
||||
tasks.append((skill_info['name'], task))
|
||||
|
||||
@ -265,14 +271,15 @@ class SkillManager:
|
||||
# 处理结果
|
||||
for (skill_name, _), result in zip(tasks, results):
|
||||
if isinstance(result, Exception):
|
||||
logger.error(f"技能执行失败: {skill_name}, {result}")
|
||||
logger.error(f"❌ 技能执行异常: {skill_name}, {result}")
|
||||
all_errors.append(f"{skill_name}: {str(result)}")
|
||||
all_results[skill_name] = {'error': str(result)}
|
||||
elif result.get('success'):
|
||||
# 不在这里记录成功日志,因为 execute_skill 已经记录了
|
||||
all_results[skill_name] = result.get('data', {})
|
||||
else:
|
||||
error_msg = result.get('error', '未知错误')
|
||||
logger.error(f"技能执行失败: {skill_name}, {error_msg}")
|
||||
logger.error(f"❌ 技能执行失败: {skill_name}, {error_msg}")
|
||||
all_errors.append(f"{skill_name}: {error_msg}")
|
||||
all_results[skill_name] = {'error': error_msg}
|
||||
|
||||
|
||||
@ -11,27 +11,31 @@ class SkillPlanner:
|
||||
# 维度到技能的映射
|
||||
DIMENSION_SKILL_MAP = {
|
||||
'price_trend': {
|
||||
'required': ['market_data'],
|
||||
'required': ['market_data', 'brave_search'], # brave_search 必需
|
||||
'optional': []
|
||||
},
|
||||
'technical': {
|
||||
'required': ['market_data', 'technical_analysis'],
|
||||
'required': ['market_data', 'technical_analysis', 'brave_search'], # brave_search 必需
|
||||
'optional': ['visualization']
|
||||
},
|
||||
'fundamental': {
|
||||
'required': ['fundamental'],
|
||||
'required': ['fundamental', 'brave_search'], # brave_search 必需
|
||||
'optional': []
|
||||
},
|
||||
'valuation': {
|
||||
'required': ['advanced_data'],
|
||||
'required': ['advanced_data', 'brave_search'], # brave_search 必需
|
||||
'optional': []
|
||||
},
|
||||
'money_flow': {
|
||||
'required': ['advanced_data'],
|
||||
'required': ['advanced_data', 'brave_search'], # brave_search 必需
|
||||
'optional': []
|
||||
},
|
||||
'risk': {
|
||||
'required': ['technical_analysis', 'advanced_data'],
|
||||
'required': ['technical_analysis', 'advanced_data', 'brave_search'], # brave_search 必需
|
||||
'optional': []
|
||||
},
|
||||
'news': { # 新闻维度
|
||||
'required': ['brave_search'],
|
||||
'optional': []
|
||||
}
|
||||
}
|
||||
@ -46,6 +50,7 @@ class SkillPlanner:
|
||||
SKILL_PRIORITY = {
|
||||
'market_data': 1, # 最高优先级
|
||||
'fundamental': 1,
|
||||
'brave_search': 1, # 新闻搜索也是高优先级
|
||||
'technical_analysis': 2,
|
||||
'advanced_data': 2,
|
||||
'visualization': 3, # 最低优先级
|
||||
@ -141,7 +146,8 @@ class SkillPlanner:
|
||||
if enabled and dimension in self.DIMENSION_SKILL_MAP:
|
||||
mapping = self.DIMENSION_SKILL_MAP[dimension]
|
||||
skills.extend(mapping['required'])
|
||||
# 可选技能稍后根据深度策略添加
|
||||
# 默认也添加可选技能(特别是 brave_search)
|
||||
skills.extend(mapping['optional'])
|
||||
|
||||
return skills
|
||||
|
||||
@ -217,6 +223,37 @@ class SkillPlanner:
|
||||
|
||||
params['data_types'] = data_types
|
||||
|
||||
elif skill_name == 'brave_search':
|
||||
# 构建搜索查询
|
||||
target = intent.get('target', {})
|
||||
stock_name = target.get('stock_name', '')
|
||||
stock_code = target.get('stock_code', '')
|
||||
dimensions = intent.get('dimensions', {})
|
||||
|
||||
# 根据维度构建搜索关键词
|
||||
search_keywords = []
|
||||
if stock_name:
|
||||
search_keywords.append(stock_name)
|
||||
elif stock_code:
|
||||
search_keywords.append(stock_code)
|
||||
|
||||
# 添加维度相关关键词
|
||||
if dimensions.get('fundamental'):
|
||||
search_keywords.append('财报 业绩')
|
||||
if dimensions.get('news'):
|
||||
search_keywords.append('最新消息')
|
||||
if dimensions.get('risk'):
|
||||
search_keywords.append('风险 预警')
|
||||
|
||||
# 如果没有特定维度,搜索一般新闻
|
||||
if not any(dimensions.values()):
|
||||
search_keywords.append('最新动态')
|
||||
|
||||
params['query'] = ' '.join(search_keywords)
|
||||
params['search_type'] = 'news' # 默认搜索新闻
|
||||
params['count'] = 5
|
||||
params['freshness'] = 'pw' # 过去一周
|
||||
|
||||
return params
|
||||
|
||||
def _get_skill_reason(self, skill_name: str, intent: Dict[str, Any]) -> str:
|
||||
@ -253,4 +290,14 @@ class SkillPlanner:
|
||||
elif skill_name == 'visualization':
|
||||
reasons.append('生成K线图表')
|
||||
|
||||
elif skill_name == 'brave_search':
|
||||
if dimensions.get('news'):
|
||||
reasons.append('用户关注最新新闻')
|
||||
elif dimensions.get('fundamental'):
|
||||
reasons.append('搜索公司最新动态和财报信息')
|
||||
elif dimensions.get('risk'):
|
||||
reasons.append('搜索风险预警信息')
|
||||
else:
|
||||
reasons.append('获取最新市场资讯和舆情')
|
||||
|
||||
return ', '.join(reasons) if reasons else '提供分析数据'
|
||||
|
||||
@ -16,6 +16,7 @@ from app.skills.fundamental import FundamentalSkill
|
||||
from app.skills.visualization import VisualizationSkill
|
||||
from app.skills.advanced_data import AdvancedDataSkill
|
||||
from app.skills.us_stock_skill import USStockSkill
|
||||
from app.skills.brave_search import BraveSearchSkill
|
||||
from app.services.llm_service import llm_service
|
||||
from app.services.tushare_service import tushare_service
|
||||
from app.utils.logger import logger
|
||||
@ -60,7 +61,8 @@ class SmartStockAgent:
|
||||
skill_manager.register(VisualizationSkill())
|
||||
skill_manager.register(AdvancedDataSkill())
|
||||
skill_manager.register(USStockSkill())
|
||||
logger.info("技能注册完成(Tushare Pro高级数据 + 美股支持)")
|
||||
skill_manager.register(BraveSearchSkill())
|
||||
logger.info("技能注册完成(Tushare Pro高级数据 + 美股支持 + Brave搜索)")
|
||||
|
||||
async def process_message(
|
||||
self,
|
||||
@ -112,7 +114,7 @@ class SmartStockAgent:
|
||||
message: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
全面分析:整合多个数据源 + LLM深度分析
|
||||
全面分析:使用 skill_planner 智能规划技能 + LLM深度分析
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
@ -126,46 +128,48 @@ class SmartStockAgent:
|
||||
|
||||
display_name = stock_name or stock_code
|
||||
|
||||
# 1. 并行获取所有数据
|
||||
try:
|
||||
# 获取实时行情
|
||||
quote_result = await skill_manager.execute_skill(
|
||||
"market_data",
|
||||
stock_code=stock_code,
|
||||
data_type="quote"
|
||||
# 1. 使用 QuestionAnalyzer 分析问题意图
|
||||
intent = await self.question_analyzer.analyze_question(
|
||||
question=message,
|
||||
context=[],
|
||||
session_id=""
|
||||
)
|
||||
|
||||
# 获取技术指标
|
||||
technical_result = await skill_manager.execute_skill(
|
||||
"technical_analysis",
|
||||
stock_code=stock_code,
|
||||
indicators=["ma", "macd", "rsi", "kdj"]
|
||||
)
|
||||
# 确保 intent 包含股票信息
|
||||
if 'target' not in intent:
|
||||
intent['target'] = {}
|
||||
intent['target']['stock_code'] = stock_code
|
||||
intent['target']['stock_name'] = stock_name
|
||||
|
||||
# 获取基本面
|
||||
fundamental_result = await skill_manager.execute_skill(
|
||||
"fundamental",
|
||||
logger.info(f"问题意图分析: dimensions={intent.get('dimensions')}")
|
||||
|
||||
# 2. 使用 SkillPlanner 规划技能(包括 brave_search)
|
||||
plan = self.skill_planner.plan_skills(intent)
|
||||
logger.info(f"技能规划完成: {[s['name'] for s in plan['skills']]}, 策略: {plan['execution_strategy']}")
|
||||
|
||||
# 3. 执行技能规划
|
||||
execution_results = await skill_manager.execute_plan(
|
||||
plan=plan,
|
||||
stock_code=stock_code
|
||||
)
|
||||
|
||||
# 获取高级数据(Tushare Pro 5000+积分)
|
||||
advanced_result = await skill_manager.execute_skill(
|
||||
"advanced_data",
|
||||
stock_code=stock_code,
|
||||
data_type="all"
|
||||
)
|
||||
if execution_results['errors']:
|
||||
logger.warning(f"技能执行有错误: {execution_results['errors']}")
|
||||
|
||||
# 整合数据
|
||||
# 4. 整合数据(兼容旧格式)
|
||||
results = execution_results['results']
|
||||
all_data = {
|
||||
"stock_code": stock_code,
|
||||
"stock_name": display_name,
|
||||
"quote": quote_result.get("data") if quote_result.get("success") else None,
|
||||
"technical": technical_result.get("data") if technical_result.get("success") else None,
|
||||
"fundamental": fundamental_result.get("data") if fundamental_result.get("success") else None,
|
||||
"advanced": advanced_result.get("data") if advanced_result.get("success") else None
|
||||
"quote": results.get("market_data"),
|
||||
"technical": results.get("technical_analysis"),
|
||||
"fundamental": results.get("fundamental"),
|
||||
"advanced": results.get("advanced_data"),
|
||||
"news": results.get("brave_search") # 新增:新闻数据
|
||||
}
|
||||
|
||||
# 2. 使用LLM进行深度分析
|
||||
# 5. 使用LLM进行深度分析
|
||||
if self.use_llm:
|
||||
analysis = await self._llm_comprehensive_analysis(all_data, message)
|
||||
else:
|
||||
@ -175,12 +179,15 @@ class SmartStockAgent:
|
||||
"message": analysis,
|
||||
"metadata": {
|
||||
"type": "comprehensive",
|
||||
"data": all_data
|
||||
"data": all_data,
|
||||
"plan": plan
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"全面分析失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return {
|
||||
"message": f"分析{display_name}时出错:{str(e)}",
|
||||
"metadata": {"type": "error"}
|
||||
@ -256,6 +263,31 @@ class SmartStockAgent:
|
||||
else:
|
||||
advanced_summary = "\n【高级财务数据】\n暂无高级数据\n"
|
||||
|
||||
# 格式化新闻数据
|
||||
news_section = ""
|
||||
news_data = data.get('news', {})
|
||||
if news_data and not news_data.get('error'):
|
||||
results = news_data.get('results', [])
|
||||
if results:
|
||||
news_section = "\n【最新新闻和舆情】(来源:Brave Search)\n"
|
||||
for i, item in enumerate(results[:5], 1):
|
||||
title = item.get('title', '无标题')
|
||||
description = item.get('description', '')
|
||||
source = item.get('source', '')
|
||||
published = item.get('published', '')
|
||||
news_section += f"{i}. {title}\n"
|
||||
if description:
|
||||
news_section += f" 摘要:{description[:100]}...\n"
|
||||
if source:
|
||||
news_section += f" 来源:{source}\n"
|
||||
if published:
|
||||
news_section += f" 发布时间:{published}\n"
|
||||
news_section += "\n"
|
||||
else:
|
||||
news_section = "\n【最新新闻和舆情】\n暂无相关新闻\n"
|
||||
else:
|
||||
news_section = "\n【最新新闻和舆情】\n暂无相关新闻\n"
|
||||
|
||||
# 构建详细的分析提示
|
||||
prompt = f"""你是一位专业的股票分析师。请对{data['stock_name']}({data['stock_code']})进行全面分析,用简洁专业但易懂的语言回答。
|
||||
|
||||
@ -275,7 +307,7 @@ class SmartStockAgent:
|
||||
数据来源:Tushare Pro API
|
||||
{json.dumps(data.get('fundamental'), ensure_ascii=False, indent=2) if data.get('fundamental') else '数据获取失败'}
|
||||
{advanced_summary}
|
||||
|
||||
{news_section}
|
||||
请按以下结构进行分析,并在每个部分明确标注数据来源和时效性:
|
||||
|
||||
## 一、基本面分析
|
||||
@ -304,12 +336,13 @@ DIF和DEA的位置关系,MACD柱状图变化,判断动能强弱和买卖信
|
||||
**支撑与压力**
|
||||
关键支撑位和压力位的具体价格区间。
|
||||
|
||||
## 三、市场情绪分析
|
||||
## 三、市场情绪和新闻分析
|
||||
分段分析市场情绪:
|
||||
- 第一段:资金流向分析(主力资金、大单资金流入/流出情况)
|
||||
- 第二段:融资融券情况(如有)
|
||||
- 第三段:当前市场情绪(乐观/谨慎/悲观)及原因
|
||||
- 第四段:短期可能的催化因素
|
||||
- 第三段:**基于最新新闻分析市场情绪和舆情,识别可能影响股价的重要事件**
|
||||
- 第四段:当前市场情绪(乐观/谨慎/悲观)及原因
|
||||
- 第五段:短期可能的催化因素
|
||||
|
||||
## 四、投资建议
|
||||
基于技术面分析,给出具体的操作建议和点位:
|
||||
@ -349,6 +382,7 @@ DIF和DEA的位置关系,MACD柱状图变化,判断动能强弱和买卖信
|
||||
- 资金流向:Tushare Pro(主力资金、大单资金)
|
||||
- 融资融券:Tushare Pro(如有)
|
||||
- 公告数据:Tushare Pro(重大公告)
|
||||
- 新闻舆情:Brave Search(最新市场资讯)
|
||||
|
||||
写作要求:
|
||||
1. 语言简洁专业,避免过度修饰和比喻
|
||||
@ -1438,7 +1472,7 @@ MA60:{f"{ma['ma60']:.2f}" if ma['ma60'] else '计算中'}
|
||||
|
||||
async def _handle_us_stock(self, keyword: str, message: str) -> Dict[str, Any]:
|
||||
"""
|
||||
处理美股查询
|
||||
处理美股查询(使用 skill_planner)
|
||||
|
||||
Args:
|
||||
keyword: 股票关键词
|
||||
@ -1453,30 +1487,75 @@ MA60:{f"{ma['ma60']:.2f}" if ma['ma60'] else '计算中'}
|
||||
logger.info(f"处理美股查询: {keyword} -> {symbol}")
|
||||
|
||||
try:
|
||||
# 调用美股分析技能
|
||||
result = await skill_manager.execute_skill(
|
||||
"us_stock_analysis",
|
||||
symbol=symbol,
|
||||
analysis_type="comprehensive"
|
||||
# 1. 使用 QuestionAnalyzer 分析问题意图
|
||||
intent = await self.question_analyzer.analyze_question(
|
||||
question=message,
|
||||
context=[],
|
||||
session_id=""
|
||||
)
|
||||
|
||||
if not result.get("success"):
|
||||
# 确保 intent 包含股票信息
|
||||
if 'target' not in intent:
|
||||
intent['target'] = {}
|
||||
intent['target']['stock_code'] = symbol
|
||||
intent['target']['stock_name'] = keyword
|
||||
intent['target']['market'] = '美股'
|
||||
|
||||
logger.info(f"美股问题意图分析: dimensions={intent.get('dimensions')}")
|
||||
|
||||
# 2. 使用 SkillPlanner 规划技能(包括 brave_search)
|
||||
plan = self.skill_planner.plan_skills(intent)
|
||||
|
||||
# 3. 将 us_stock_analysis 添加到技能列表(如果不存在)
|
||||
skill_names = [s['name'] for s in plan['skills']]
|
||||
if 'us_stock_analysis' not in skill_names:
|
||||
plan['skills'].insert(0, {
|
||||
'name': 'us_stock_analysis',
|
||||
'params': {'symbol': symbol, 'analysis_type': 'comprehensive'},
|
||||
'priority': 1,
|
||||
'required': True,
|
||||
'reason': '获取美股基础数据'
|
||||
})
|
||||
|
||||
logger.info(f"美股技能规划完成: {[s['name'] for s in plan['skills']]}, 策略: {plan['execution_strategy']}")
|
||||
|
||||
# 4. 执行技能规划
|
||||
execution_results = await skill_manager.execute_plan(
|
||||
plan=plan,
|
||||
stock_code=symbol
|
||||
)
|
||||
|
||||
if execution_results['errors']:
|
||||
logger.warning(f"美股技能执行有错误: {execution_results['errors']}")
|
||||
|
||||
# 5. 检查 us_stock_analysis 是否成功
|
||||
us_stock_data = execution_results['results'].get('us_stock_analysis')
|
||||
if not us_stock_data or 'error' in us_stock_data:
|
||||
return {
|
||||
"message": f"抱歉,未找到美股 {symbol}。请确认股票代码是否正确。\n\n提示:美股代码通常为大写字母,如 AAPL(苹果)、TSLA(特斯拉)、MSFT(微软)等。",
|
||||
"metadata": {"type": "error"}
|
||||
}
|
||||
|
||||
# 使用LLM分析美股数据
|
||||
# 6. 整合数据
|
||||
all_data = {
|
||||
"symbol": symbol,
|
||||
"name": keyword,
|
||||
**us_stock_data,
|
||||
"news": execution_results['results'].get("brave_search") # 新增:新闻数据
|
||||
}
|
||||
|
||||
# 7. 使用LLM分析美股数据
|
||||
if self.use_llm:
|
||||
analysis = await self._llm_us_stock_analysis(result["data"], message)
|
||||
analysis = await self._llm_us_stock_analysis(all_data, message)
|
||||
else:
|
||||
analysis = self._format_us_stock_data(result["data"])
|
||||
analysis = self._format_us_stock_data(all_data)
|
||||
|
||||
return {
|
||||
"message": analysis,
|
||||
"metadata": {
|
||||
"type": "us_stock_analysis",
|
||||
"data": result["data"]
|
||||
"data": all_data,
|
||||
"plan": plan
|
||||
}
|
||||
}
|
||||
|
||||
@ -1509,10 +1588,35 @@ MA60:{f"{ma['ma60']:.2f}" if ma['ma60'] else '计算中'}
|
||||
week_52_low = data.get("52_week_low", 0)
|
||||
technical = data.get("technical_indicators", {})
|
||||
description = data.get("description", "")
|
||||
news_data = data.get("news", {}) # 新增:获取新闻数据
|
||||
|
||||
# 格式化市值
|
||||
market_cap_str = f"${market_cap / 1e9:.2f}B" if market_cap > 1e9 else f"${market_cap / 1e6:.2f}M"
|
||||
|
||||
# 格式化新闻数据
|
||||
news_section = ""
|
||||
if news_data and not news_data.get('error'):
|
||||
results = news_data.get('results', [])
|
||||
if results:
|
||||
news_section = "\n【最新新闻和舆情】(来源:Brave Search)\n"
|
||||
for i, item in enumerate(results[:5], 1):
|
||||
title = item.get('title', '无标题')
|
||||
description = item.get('description', '')
|
||||
source = item.get('source', '')
|
||||
published = item.get('published', '')
|
||||
news_section += f"{i}. {title}\n"
|
||||
if description:
|
||||
news_section += f" 摘要:{description[:100]}...\n"
|
||||
if source:
|
||||
news_section += f" 来源:{source}\n"
|
||||
if published:
|
||||
news_section += f" 发布时间:{published}\n"
|
||||
news_section += "\n"
|
||||
else:
|
||||
news_section = "\n【最新新闻和舆情】\n暂无相关新闻\n"
|
||||
else:
|
||||
news_section = "\n【最新新闻和舆情】\n暂无相关新闻\n"
|
||||
|
||||
# 构建分析提示
|
||||
prompt = f"""你是一位专业的美股分析师。请基于以下数据对 {name} ({symbol}) 进行全面分析。
|
||||
|
||||
@ -1545,7 +1649,7 @@ MA20:{f"${technical.get('ma20'):.2f}" if technical.get('ma20') else '计算中
|
||||
MA60:{f"${technical.get('ma60'):.2f}" if technical.get('ma60') else '计算中'}
|
||||
RSI:{f"{technical.get('rsi'):.2f}" if technical.get('rsi') else '计算中'}
|
||||
MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'}
|
||||
|
||||
{news_section}
|
||||
用户问题:{user_message}
|
||||
|
||||
请提供专业的分析报告,包括:
|
||||
@ -1563,6 +1667,11 @@ MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'
|
||||
- 关键支撑位和压力位
|
||||
- RSI和MACD信号解读
|
||||
|
||||
## 📰 市场情绪和新闻分析
|
||||
- 基于最新新闻分析市场情绪和舆情
|
||||
- 识别可能影响股价的重要事件或消息
|
||||
- 评估新闻对短期和中期走势的影响
|
||||
|
||||
## 💡 投资建议
|
||||
- 短期操作建议(1-2周)
|
||||
- 中期投资价值(1-3个月)
|
||||
@ -1572,16 +1681,17 @@ MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'
|
||||
1. 语言专业但易懂,避免过度修饰
|
||||
2. 分析客观理性,基于数据和事实
|
||||
3. 每个部分独立成段,段落间用空行分隔
|
||||
4. 控制在500-600字
|
||||
5. **不要在报告中添加日期标题,直接开始分析内容**
|
||||
6. 最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。"
|
||||
4. **充分利用新闻数据进行市场情绪分析**
|
||||
5. 控制在600-800字
|
||||
6. **不要在报告中添加日期标题,直接开始分析内容**
|
||||
7. 最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。"
|
||||
"""
|
||||
|
||||
try:
|
||||
analysis = await self._call_llm_async(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.7,
|
||||
max_tokens=2000
|
||||
max_tokens=2500 # 增加 token 数量以容纳新闻分析
|
||||
)
|
||||
|
||||
if analysis:
|
||||
@ -1732,7 +1842,7 @@ RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'}
|
||||
yield chunk
|
||||
|
||||
async def _handle_a_stock_stream(self, stock_keyword: str, message: str):
|
||||
"""流式处理A股分析"""
|
||||
"""流式处理A股分析(使用 skill_planner)"""
|
||||
# 使用LLM进行智能匹配
|
||||
stock_info = await self._match_stock_with_llm(stock_keyword)
|
||||
|
||||
@ -1744,23 +1854,50 @@ RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'}
|
||||
stock_name = stock_info['name']
|
||||
is_index = stock_info['is_index']
|
||||
|
||||
# 获取数据(非流式)
|
||||
try:
|
||||
quote_result = await skill_manager.execute_skill("market_data", stock_code=stock_code, data_type="quote")
|
||||
technical_result = await skill_manager.execute_skill("technical_analysis", stock_code=stock_code, indicators=["ma", "macd", "rsi", "kdj"])
|
||||
fundamental_result = await skill_manager.execute_skill("fundamental", stock_code=stock_code)
|
||||
advanced_result = await skill_manager.execute_skill("advanced_data", stock_code=stock_code, data_type="all")
|
||||
logger.info(f"[流式] A股分析: {stock_name}({stock_code})")
|
||||
|
||||
try:
|
||||
# 1. 使用 QuestionAnalyzer 分析问题意图
|
||||
intent = await self.question_analyzer.analyze_question(
|
||||
question=message,
|
||||
context=[],
|
||||
session_id=""
|
||||
)
|
||||
|
||||
# 确保 intent 包含股票信息
|
||||
if 'target' not in intent:
|
||||
intent['target'] = {}
|
||||
intent['target']['stock_code'] = stock_code
|
||||
intent['target']['stock_name'] = stock_name
|
||||
|
||||
logger.info(f"[流式] A股问题意图分析: dimensions={intent.get('dimensions')}")
|
||||
|
||||
# 2. 使用 SkillPlanner 规划技能(包括 brave_search)
|
||||
plan = self.skill_planner.plan_skills(intent)
|
||||
logger.info(f"[流式] A股技能规划完成: {[s['name'] for s in plan['skills']]}, 策略: {plan['execution_strategy']}")
|
||||
|
||||
# 3. 执行技能规划
|
||||
execution_results = await skill_manager.execute_plan(
|
||||
plan=plan,
|
||||
stock_code=stock_code
|
||||
)
|
||||
|
||||
if execution_results['errors']:
|
||||
logger.warning(f"[流式] A股技能执行有错误: {execution_results['errors']}")
|
||||
|
||||
# 4. 整合数据(兼容旧格式)
|
||||
results = execution_results['results']
|
||||
all_data = {
|
||||
"stock_code": stock_code,
|
||||
"stock_name": stock_name,
|
||||
"quote": quote_result.get("data") if quote_result.get("success") else None,
|
||||
"technical": technical_result.get("data") if technical_result.get("success") else None,
|
||||
"fundamental": fundamental_result.get("data") if fundamental_result.get("success") else None,
|
||||
"advanced": advanced_result.get("data") if advanced_result.get("success") else None
|
||||
"quote": results.get("market_data"),
|
||||
"technical": results.get("technical_analysis"),
|
||||
"fundamental": results.get("fundamental"),
|
||||
"advanced": results.get("advanced_data"),
|
||||
"news": results.get("brave_search") # 新增:新闻数据
|
||||
}
|
||||
|
||||
# 使用LLM流式分析
|
||||
# 5. 使用LLM流式分析
|
||||
if self.use_llm:
|
||||
async for chunk in self._llm_comprehensive_analysis_stream(all_data, message, is_index):
|
||||
yield chunk
|
||||
@ -1769,42 +1906,282 @@ RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"A股分析失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
yield f"分析{stock_name}时出错:{str(e)}"
|
||||
|
||||
async def _handle_us_stock_stream(self, keyword: str, message: str):
|
||||
"""流式处理美股分析(智能模式)"""
|
||||
"""流式处理美股分析(使用 skill_planner)"""
|
||||
symbol = self._get_us_stock_symbol(keyword)
|
||||
logger.info(f"[智能模式-流式] 美股查询: {keyword} -> {symbol}")
|
||||
|
||||
try:
|
||||
result = await skill_manager.execute_skill("us_stock_analysis", symbol=symbol, analysis_type="comprehensive")
|
||||
# 1. 使用 QuestionAnalyzer 分析问题意图
|
||||
intent = await self.question_analyzer.analyze_question(
|
||||
question=message,
|
||||
context=[],
|
||||
session_id=""
|
||||
)
|
||||
|
||||
if not result.get("success"):
|
||||
# 确保 intent 包含股票信息
|
||||
if 'target' not in intent:
|
||||
intent['target'] = {}
|
||||
intent['target']['stock_code'] = symbol
|
||||
intent['target']['stock_name'] = keyword
|
||||
intent['target']['market'] = '美股'
|
||||
|
||||
logger.info(f"[流式] 美股问题意图分析: dimensions={intent.get('dimensions')}")
|
||||
|
||||
# 2. 使用 SkillPlanner 规划技能(包括 brave_search)
|
||||
plan = self.skill_planner.plan_skills(intent)
|
||||
|
||||
# 3. 将 us_stock_analysis 添加到技能列表(如果不存在)
|
||||
skill_names = [s['name'] for s in plan['skills']]
|
||||
if 'us_stock_analysis' not in skill_names:
|
||||
plan['skills'].insert(0, {
|
||||
'name': 'us_stock_analysis',
|
||||
'params': {'symbol': symbol, 'analysis_type': 'comprehensive'},
|
||||
'priority': 1,
|
||||
'required': True,
|
||||
'reason': '获取美股基础数据'
|
||||
})
|
||||
|
||||
logger.info(f"[流式] 美股技能规划完成: {[s['name'] for s in plan['skills']]}, 策略: {plan['execution_strategy']}")
|
||||
|
||||
# 4. 执行技能规划
|
||||
execution_results = await skill_manager.execute_plan(
|
||||
plan=plan,
|
||||
stock_code=symbol
|
||||
)
|
||||
|
||||
if execution_results['errors']:
|
||||
logger.warning(f"[流式] 美股技能执行有错误: {execution_results['errors']}")
|
||||
|
||||
# 5. 检查 us_stock_analysis 是否成功
|
||||
us_stock_data = execution_results['results'].get('us_stock_analysis')
|
||||
if not us_stock_data or 'error' in us_stock_data:
|
||||
yield f"抱歉,未找到美股 {symbol}。请确认股票代码是否正确。"
|
||||
return
|
||||
|
||||
# 使用智能模式的动态prompt生成
|
||||
# 6. 整合数据
|
||||
all_data = {
|
||||
"symbol": symbol,
|
||||
"name": keyword,
|
||||
**us_stock_data,
|
||||
"news": execution_results['results'].get("brave_search") # 新增:新闻数据
|
||||
}
|
||||
|
||||
# 7. 使用智能模式的动态prompt生成
|
||||
if self.use_llm:
|
||||
# 构建美股数据的动态prompt
|
||||
us_data = result["data"]
|
||||
prompt = self._build_us_stock_dynamic_prompt(us_data, symbol, message)
|
||||
prompt = self._build_us_stock_dynamic_prompt(all_data, symbol, message)
|
||||
|
||||
# 流式生成
|
||||
stream = llm_service.chat_stream(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.7,
|
||||
max_tokens=2000
|
||||
max_tokens=2500 # 增加 token 数量以容纳新闻分析
|
||||
)
|
||||
|
||||
for chunk in stream:
|
||||
yield chunk
|
||||
else:
|
||||
yield self._format_us_stock_data(result["data"])
|
||||
yield self._format_us_stock_data(all_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"美股查询失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
yield f"查询美股 {symbol} 时出错:{str(e)}"
|
||||
|
||||
def _format_news_section(self, news_data: Dict[str, Any]) -> str:
|
||||
"""
|
||||
格式化新闻数据为统一格式
|
||||
|
||||
Args:
|
||||
news_data: 新闻数据字典
|
||||
|
||||
Returns:
|
||||
格式化后的新闻文本
|
||||
"""
|
||||
if not news_data or news_data.get('error'):
|
||||
return "\n**最新新闻和舆情**: 暂无相关新闻\n"
|
||||
|
||||
results = news_data.get('results', [])
|
||||
if not results:
|
||||
return "\n**最新新闻和舆情**: 暂无相关新闻\n"
|
||||
|
||||
news_section = "\n**最新新闻和舆情**(来源:Brave Search):\n"
|
||||
for i, item in enumerate(results[:5], 1):
|
||||
title = item.get('title', '无标题')
|
||||
description = item.get('description', '')
|
||||
source = item.get('source', '')
|
||||
published = item.get('published', '')
|
||||
news_section += f"{i}. {title}\n"
|
||||
if description:
|
||||
news_section += f" 摘要:{description[:100]}...\n"
|
||||
if source:
|
||||
news_section += f" 来源:{source}\n"
|
||||
if published:
|
||||
news_section += f" 发布时间:{published}\n"
|
||||
news_section += "\n"
|
||||
|
||||
return news_section
|
||||
|
||||
def _build_unified_analysis_prompt(
|
||||
self,
|
||||
market: str,
|
||||
stock_info: Dict[str, Any],
|
||||
data: Dict[str, Any],
|
||||
user_message: str
|
||||
) -> str:
|
||||
"""
|
||||
统一的分析提示词构建函数(自然语言风格)
|
||||
|
||||
Args:
|
||||
market: 市场类型('A股' 或 '美股')
|
||||
stock_info: 股票基本信息 {'code': ..., 'name': ...}
|
||||
data: 市场数据(行情、技术、基本面、新闻等)
|
||||
user_message: 用户问题
|
||||
|
||||
Returns:
|
||||
构建好的prompt字符串
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
stock_code = stock_info.get('code', '')
|
||||
stock_name = stock_info.get('name', '')
|
||||
|
||||
# 格式化新闻数据(统一格式)
|
||||
news_section = self._format_news_section(data.get('news', {}))
|
||||
|
||||
if market == '美股':
|
||||
# 美股数据格式
|
||||
current_price = data.get('current_price', 0)
|
||||
change = data.get('change', 0)
|
||||
change_percent = data.get('change_percent', 0)
|
||||
volume = data.get('volume', 0)
|
||||
market_cap = data.get('market_cap', 0)
|
||||
pe_ratio = data.get('pe_ratio', 0)
|
||||
pb_ratio = data.get('pb_ratio', 0)
|
||||
|
||||
technical = data.get('technical_indicators', {})
|
||||
ma5 = technical.get('ma5', 0)
|
||||
ma10 = technical.get('ma10', 0)
|
||||
ma20 = technical.get('ma20', 0)
|
||||
rsi = technical.get('rsi', 0)
|
||||
macd = technical.get('macd', 0)
|
||||
|
||||
market_data_section = f"""
|
||||
**行情数据**:
|
||||
- 最新价: ${current_price:.2f}
|
||||
- 涨跌: ${change:+.2f} ({change_percent:+.2f}%)
|
||||
- 成交量: {volume:,.0f}
|
||||
- 市值: ${market_cap:,.0f}
|
||||
- 市盈率(PE): {pe_ratio:.2f}
|
||||
- 市净率(PB): {pb_ratio:.2f}
|
||||
|
||||
**技术指标**:
|
||||
- 均线: MA5=${ma5:.2f}, MA10=${ma10:.2f}, MA20=${ma20:.2f}
|
||||
- RSI: {rsi:.2f}
|
||||
- MACD: {macd:.4f}
|
||||
"""
|
||||
|
||||
analyst_role = "美股分析师"
|
||||
risk_disclaimer = "以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。"
|
||||
|
||||
else: # A股
|
||||
# A股数据格式
|
||||
quote = data.get('quote', {})
|
||||
technical = data.get('technical', {})
|
||||
fundamental = data.get('fundamental', {})
|
||||
advanced = data.get('advanced', {})
|
||||
|
||||
# 获取交易日期
|
||||
quote_date = quote.get('trade_date', '未知') if quote else '未知'
|
||||
|
||||
# 构建行情数据部分
|
||||
if quote:
|
||||
market_data_section = f"""
|
||||
**行情数据**(截止:{quote_date}):
|
||||
- 最新价: ¥{quote.get('close', 0):.2f}
|
||||
- 涨跌幅: {quote.get('pct_chg', 0):+.2f}%
|
||||
- 成交量: {quote.get('vol', 0):,.0f}手
|
||||
- 成交额: {quote.get('amount', 0):,.0f}千元
|
||||
- 换手率: {quote.get('turnover_rate', 0):.2f}%
|
||||
"""
|
||||
else:
|
||||
market_data_section = "\n**行情数据**: 数据获取失败\n"
|
||||
|
||||
# 添加技术指标
|
||||
if technical:
|
||||
market_data_section += f"""
|
||||
**技术指标**(截止:{quote_date}):
|
||||
- 均线: MA5=¥{technical.get('ma5', 0):.2f}, MA10=¥{technical.get('ma10', 0):.2f}, MA20=¥{technical.get('ma20', 0):.2f}
|
||||
- RSI: {technical.get('rsi', 0):.2f}
|
||||
- MACD: {technical.get('macd', 0):.4f}
|
||||
"""
|
||||
|
||||
# 添加基本面数据
|
||||
if fundamental:
|
||||
market_data_section += f"""
|
||||
**基本面数据**:
|
||||
- 市盈率(PE): {fundamental.get('pe', 0):.2f}
|
||||
- 市净率(PB): {fundamental.get('pb', 0):.2f}
|
||||
- 总市值: {fundamental.get('total_mv', 0):,.0f}亿元
|
||||
- ROE: {fundamental.get('roe', 0):.2f}%
|
||||
"""
|
||||
|
||||
# 添加高级数据(资金流向、融资融券等)
|
||||
if advanced:
|
||||
if advanced.get('money_flow'):
|
||||
money_flow = advanced['money_flow'][0] if advanced['money_flow'] else {}
|
||||
market_data_section += f"""
|
||||
**资金流向**:
|
||||
- 主力净流入: {money_flow.get('net_mf_amount', 0):,.0f}万元
|
||||
- 大单净流入: {money_flow.get('buy_lg_amount', 0) - money_flow.get('sell_lg_amount', 0):,.0f}万元
|
||||
"""
|
||||
|
||||
if advanced.get('margin'):
|
||||
margin = advanced['margin'][0] if advanced['margin'] else {}
|
||||
market_data_section += f"""
|
||||
**融资融券**:
|
||||
- 融资余额: {margin.get('rzye', 0):,.0f}元
|
||||
- 融券余额: {margin.get('rqye', 0):,.0f}元
|
||||
"""
|
||||
|
||||
analyst_role = "A股分析师"
|
||||
risk_disclaimer = "以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。"
|
||||
|
||||
# 构建统一的prompt
|
||||
prompt = f"""你是一位专业的{analyst_role}。请根据以下数据分析【{stock_name}({stock_code})】。
|
||||
|
||||
**用户问题**: {user_message}
|
||||
|
||||
**【重要】最新新闻和舆情(必须分析)**
|
||||
{news_section}
|
||||
|
||||
## 数据信息
|
||||
{market_data_section}
|
||||
|
||||
## 分析要求
|
||||
|
||||
请根据用户的问题,提供自然、有针对性的分析。不要使用固定格式,而是像专业分析师一样,用自然的语言回答用户的问题。
|
||||
|
||||
**重要:必须在分析中包含对上面提供的最新新闻的分析,评估新闻对股价的影响。**
|
||||
|
||||
分析时请注意:
|
||||
- 如果用户关注价格走势,重点分析价格和趋势,并结合新闻分析市场情绪
|
||||
- 如果用户关注技术指标,重点分析技术面,并结合新闻判断短期走势
|
||||
- 如果用户关注基本面,重点分析公司情况和估值,并结合新闻评估投资价值
|
||||
- **无论用户问什么,都要在分析中提及最新新闻对股价的潜在影响**
|
||||
|
||||
请直接开始分析,不要添加日期标题。控制在600-800字。最后声明:"{risk_disclaimer}"
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
def _build_us_stock_dynamic_prompt(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
@ -1812,7 +2189,7 @@ RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'}
|
||||
user_message: str
|
||||
) -> str:
|
||||
"""
|
||||
为美股构建动态prompt
|
||||
为美股构建动态prompt(调用统一函数)
|
||||
|
||||
Args:
|
||||
data: 美股数据
|
||||
@ -1822,165 +2199,42 @@ RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'}
|
||||
Returns:
|
||||
prompt字符串
|
||||
"""
|
||||
# 提取数据
|
||||
name = data.get('name', symbol)
|
||||
current_price = data.get('current_price', 0)
|
||||
change = data.get('change', 0)
|
||||
change_percent = data.get('change_percent', 0)
|
||||
volume = data.get('volume', 0)
|
||||
market_cap = data.get('market_cap', 0)
|
||||
pe_ratio = data.get('pe_ratio', 0)
|
||||
stock_info = {
|
||||
'code': symbol,
|
||||
'name': data.get('name', symbol)
|
||||
}
|
||||
|
||||
# 技术指标
|
||||
technical = data.get('technical_indicators', {})
|
||||
ma5 = technical.get('ma5', 0)
|
||||
ma10 = technical.get('ma10', 0)
|
||||
ma20 = technical.get('ma20', 0)
|
||||
rsi = technical.get('rsi', 0)
|
||||
macd = technical.get('macd', 0)
|
||||
|
||||
prompt = f"""你是一个专业的美股分析师。请根据以下数据分析【{name}({symbol})】。
|
||||
|
||||
**用户问题**: {user_message}
|
||||
|
||||
## 数据信息
|
||||
|
||||
**行情数据**:
|
||||
- 最新价: ${current_price:.2f}
|
||||
- 涨跌: ${change:+.2f} ({change_percent:+.2f}%)
|
||||
- 成交量: {volume:,.0f}
|
||||
- 市值: ${market_cap:,.0f}
|
||||
- 市盈率(PE): {pe_ratio:.2f}
|
||||
|
||||
**技术指标**:
|
||||
- 均线: MA5=${ma5:.2f}, MA10=${ma10:.2f}, MA20=${ma20:.2f}
|
||||
- RSI: {rsi:.2f}
|
||||
- MACD: {macd:.4f}
|
||||
|
||||
## 分析要求
|
||||
|
||||
请根据用户的问题,提供自然、有针对性的分析。不要使用固定格式,而是像专业分析师一样,用自然的语言回答用户的问题。
|
||||
|
||||
- 如果用户关注价格走势,重点分析价格和趋势
|
||||
- 如果用户关注技术指标,重点分析技术面
|
||||
- 如果用户关注基本面,重点分析公司情况和估值
|
||||
|
||||
请直接开始分析,不要添加日期标题。最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。"
|
||||
"""
|
||||
|
||||
return prompt
|
||||
return self._build_unified_analysis_prompt(
|
||||
market='美股',
|
||||
stock_info=stock_info,
|
||||
data=data,
|
||||
user_message=user_message
|
||||
)
|
||||
|
||||
async def _llm_comprehensive_analysis_stream(self, data: Dict[str, Any], user_message: str, is_index: bool = False):
|
||||
"""使用LLM流式进行综合分析"""
|
||||
from datetime import datetime
|
||||
import json
|
||||
"""使用LLM流式进行综合分析(调用统一函数)"""
|
||||
stock_info = {
|
||||
'code': data.get('stock_code', ''),
|
||||
'name': data.get('stock_name', '')
|
||||
}
|
||||
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
# 使用统一的prompt构建函数
|
||||
prompt = self._build_unified_analysis_prompt(
|
||||
market='A股',
|
||||
stock_info=stock_info,
|
||||
data=data,
|
||||
user_message=user_message
|
||||
)
|
||||
|
||||
# 获取行情数据的交易日期
|
||||
quote_date = "未知"
|
||||
if data.get('quote') and data['quote'].get('trade_date'):
|
||||
quote_date = data['quote']['trade_date']
|
||||
|
||||
# 构建高级数据摘要
|
||||
advanced_summary = ""
|
||||
if data.get('advanced'):
|
||||
advanced_data = data['advanced']
|
||||
advanced_summary = "\n【高级财务数据】(Tushare Pro 5000+积分)\n"
|
||||
|
||||
# 财务指标
|
||||
if advanced_data.get('financial'):
|
||||
financial = advanced_data['financial']
|
||||
if financial.get('indicators'):
|
||||
indicators = financial['indicators'].get('indicators', {})
|
||||
advanced_summary += f"财务指标(截止:{financial['indicators'].get('end_date', '未知')}):\n"
|
||||
advanced_summary += f" ROE: {indicators.get('roe', 'N/A')}%\n"
|
||||
advanced_summary += f" ROA: {indicators.get('roa', 'N/A')}%\n"
|
||||
advanced_summary += f" 毛利率: {indicators.get('gross_margin', 'N/A')}%\n"
|
||||
advanced_summary += f" 资产负债率: {indicators.get('debt_to_assets', 'N/A')}%\n"
|
||||
advanced_summary += f" 流动比率: {indicators.get('current_ratio', 'N/A')}\n\n"
|
||||
|
||||
# 估值数据
|
||||
if advanced_data.get('valuation'):
|
||||
valuation = advanced_data['valuation']
|
||||
advanced_summary += f"估值指标:\n"
|
||||
advanced_summary += f" PE(市盈率): {valuation.get('pe', 'N/A')}\n"
|
||||
advanced_summary += f" PB(市净率): {valuation.get('pb', 'N/A')}\n"
|
||||
advanced_summary += f" PS(市销率): {valuation.get('ps', 'N/A')}\n"
|
||||
advanced_summary += f" 总市值: {valuation.get('total_mv', 'N/A')}万元\n"
|
||||
advanced_summary += f" 流通市值: {valuation.get('circ_mv', 'N/A')}万元\n"
|
||||
advanced_summary += f" 换手率: {valuation.get('turnover_rate', 'N/A')}%\n\n"
|
||||
|
||||
# 资金流向(最近一天)
|
||||
if advanced_data.get('money_flow') and len(advanced_data['money_flow']) > 0:
|
||||
latest_flow = advanced_data['money_flow'][0]
|
||||
advanced_summary += f"资金流向({latest_flow.get('trade_date', '最近')}):\n"
|
||||
advanced_summary += f" 主力净流入: {latest_flow.get('net_mf_amount', 'N/A')}万元\n"
|
||||
advanced_summary += f" 超大单净流入: {latest_flow.get('buy_elg_amount', 0) - latest_flow.get('sell_elg_amount', 0):.2f}万元\n"
|
||||
advanced_summary += f" 大单净流入: {latest_flow.get('buy_lg_amount', 0) - latest_flow.get('sell_lg_amount', 0):.2f}万元\n\n"
|
||||
|
||||
# 融资融券
|
||||
if advanced_data.get('margin') and len(advanced_data['margin']) > 0:
|
||||
latest_margin = advanced_data['margin'][0]
|
||||
advanced_summary += f"融资融券({latest_margin.get('trade_date', '最近')}):\n"
|
||||
advanced_summary += f" 融资余额: {latest_margin.get('rzye', 'N/A')}元\n"
|
||||
advanced_summary += f" 融券余额: {latest_margin.get('rqye', 'N/A')}元\n\n"
|
||||
|
||||
else:
|
||||
advanced_summary = "\n【高级财务数据】\n暂无高级数据\n"
|
||||
|
||||
# 构建详细的分析提示
|
||||
prompt = f"""你是一位专业的股票分析师。请对{data['stock_name']}({data['stock_code']})进行全面分析,用简洁专业但易懂的语言回答。
|
||||
|
||||
用户问题:{user_message}
|
||||
|
||||
【实时行情数据】
|
||||
数据来源:Tushare Pro API
|
||||
交易日期:{quote_date}
|
||||
{json.dumps(data.get('quote'), ensure_ascii=False, indent=2) if data.get('quote') else '数据获取失败'}
|
||||
|
||||
【技术指标数据】
|
||||
数据来源:Tushare Pro API(基于历史K线数据计算)
|
||||
计算截止日期:{quote_date}
|
||||
{json.dumps(data.get('technical'), ensure_ascii=False, indent=2) if data.get('technical') else '数据获取失败'}
|
||||
|
||||
【基本面数据】
|
||||
数据来源:Tushare Pro API
|
||||
{json.dumps(data.get('fundamental'), ensure_ascii=False, indent=2) if data.get('fundamental') else '数据获取失败'}
|
||||
{advanced_summary}
|
||||
|
||||
请按以下结构进行分析:
|
||||
|
||||
## 一、基本面分析
|
||||
分段说明公司情况,每个要点独立成段。
|
||||
|
||||
## 二、技术面分析(数据截止:{quote_date})
|
||||
使用清晰的分段结构,每个技术指标独立成段。
|
||||
|
||||
## 三、市场情绪分析
|
||||
分段分析市场情绪和资金流向。
|
||||
|
||||
## 四、投资建议
|
||||
基于分析给出具体的操作建议和点位。
|
||||
|
||||
写作要求:
|
||||
1. 语言简洁专业但易懂
|
||||
2. 分析客观理性,基于数据
|
||||
3. 每个分析点独立成段
|
||||
4. 控制在600-800字
|
||||
5. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。"
|
||||
"""
|
||||
|
||||
# 流式调用LLM(同步生成器,使用线程避免阻塞)
|
||||
# 流式调用LLM
|
||||
import asyncio
|
||||
stream = llm_service.chat_stream(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.7,
|
||||
max_tokens=2000
|
||||
max_tokens=2500
|
||||
)
|
||||
|
||||
# 在线程中迭代同步生成器,避免阻塞事件循环
|
||||
loop = asyncio.get_event_loop()
|
||||
for chunk in stream:
|
||||
# 每次yield后让出控制权
|
||||
await asyncio.sleep(0)
|
||||
@ -2078,7 +2332,7 @@ MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'
|
||||
stream = llm_service.chat_stream(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.7,
|
||||
max_tokens=2000
|
||||
max_tokens=2500 # 增加 token 数量以容纳新闻分析
|
||||
)
|
||||
|
||||
# 在线程中迭代同步生成器,避免阻塞事件循环
|
||||
|
||||
@ -55,9 +55,11 @@ if os.path.exists(frontend_path):
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""根路径,重定向到登录页"""
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url="/static/login.html")
|
||||
"""根路径,返回主应用页面"""
|
||||
index_path = os.path.join(frontend_path, "index.html")
|
||||
if os.path.exists(index_path):
|
||||
return FileResponse(index_path)
|
||||
return {"message": "页面不存在"}
|
||||
|
||||
@app.get("/app")
|
||||
async def app_page():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user