2b5a32ca1e
新增模块: - fund_flow_analyzer.py: 主力资金流向分析(P0, ±20) - market_sentiment.py: 市场情绪指标(P1, ±10) - external_factors.py: 北向资金/美股/大宗商品/汇率(P2-P4,P7) - news_analyzer.py: 公告/并购/政策面LLM分析(P5-P6) - score_engine.py: 综合评分引擎,整合技术面+外部因素 路由更新: - analysis.py: deep_analyze接入综合评分,根据最终评级修正买卖建议 - market.py: 新增4个外部因素API端点 - trades.py: 交易路由更新 算法文档重构: - 章节重排: 技术面(二三)→外部因素(四)→买卖决策(五)→数据源(六)→性能(七) - 架构图更新为五层,标注章节对应 - 5.1/5.2标注纯技术面,5.3整合外部因素修正推荐
255 lines
9.9 KiB
Python
255 lines
9.9 KiB
Python
"""
|
||
豆包AI服务模块
|
||
用于股票分析的AI对话(流式输出版本)
|
||
"""
|
||
|
||
import requests
|
||
import json
|
||
from config import Config
|
||
|
||
# API配置
|
||
API_KEY = Config.DOUBAO_API_KEY or "9fd8383f-5776-4366-855d-c6f40e867940"
|
||
API_URL = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
|
||
MODEL = "doubao-seed-1-6-251015"
|
||
|
||
|
||
def analyze_stock(stock_code, stock_name, stock_data):
|
||
"""
|
||
使用豆包AI分析股票(流式输出,获取思考和结论)
|
||
"""
|
||
|
||
# 构建分析提示词(基于推荐模型v3.0)
|
||
fund_flow_str = format_fund_flow(stock_data.get('fund_flow_3days', []))
|
||
|
||
prompt = f"""分析{stock_name}({stock_code})投资价值。
|
||
|
||
数据:价格{stock_data.get('price', 'N/A')}元,PE={stock_data.get('pe', 'N/A')},PB={stock_data.get('pb', 'N/A')},ROE={stock_data.get('roe', 'N/A')}%,市值{format_market_cap(stock_data.get('total_market_cap'))}
|
||
资金流向:{fund_flow_str}
|
||
|
||
评分模型:价格位置(30分,低于50%为低位)、资金流向(25分,主力净流入占比)、趋势(15分)、连续性(15分,≥3天强连续)、涨跌配合(10分)、量能(5分)
|
||
推荐率:80-100强买/卖,60-79可操作,40-59观望,0-39不建议
|
||
|
||
按以下格式输出:
|
||
## 技术面分析
|
||
分析资金流向趋势和连续性
|
||
|
||
## 基本面分析
|
||
分析PE/ROE估值和盈利能力
|
||
|
||
## 操作建议
|
||
明确建议买入/持有/卖出,给出预估推荐率(0-100)
|
||
|
||
## 风险提示
|
||
列出2-3个风险点"""
|
||
|
||
try:
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {API_KEY}"
|
||
}
|
||
|
||
payload = {
|
||
"model": MODEL,
|
||
"max_completion_tokens": 2048,
|
||
"stream": True, # 启用流式输出
|
||
"messages": [
|
||
{
|
||
"role": "user",
|
||
"content": prompt
|
||
}
|
||
]
|
||
}
|
||
|
||
# 流式请求
|
||
response = requests.post(API_URL, headers=headers, json=payload, timeout=120, stream=True)
|
||
|
||
if response.status_code == 200:
|
||
reasoning_content = "" # 思考过程
|
||
content = "" # 最终结论
|
||
|
||
for line in response.iter_lines():
|
||
if line:
|
||
line_str = line.decode('utf-8')
|
||
if line_str.startswith('data: '):
|
||
data_str = line_str[6:]
|
||
if data_str == '[DONE]':
|
||
break
|
||
try:
|
||
data = json.loads(data_str)
|
||
if 'choices' in data and len(data['choices']) > 0:
|
||
delta = data['choices'][0].get('delta', {})
|
||
# 获取思考过程
|
||
if 'reasoning_content' in delta and delta['reasoning_content']:
|
||
reasoning_content += delta['reasoning_content']
|
||
# 获取最终内容
|
||
if 'content' in delta and delta['content']:
|
||
content += delta['content']
|
||
except json.JSONDecodeError:
|
||
continue
|
||
|
||
# 组合思考过程和结论
|
||
full_analysis = ""
|
||
if reasoning_content:
|
||
full_analysis += "## 💭 AI思考过程\n" + reasoning_content + "\n\n---\n\n"
|
||
if content:
|
||
full_analysis += content
|
||
|
||
if full_analysis:
|
||
return {'success': True, 'analysis': full_analysis}
|
||
else:
|
||
return {'success': False, 'error': 'AI返回内容为空'}
|
||
else:
|
||
return {'success': False, 'error': f'API请求失败: {response.status_code}'}
|
||
|
||
except requests.exceptions.Timeout:
|
||
return {'success': False, 'error': 'AI分析超时,请稍后重试'}
|
||
except Exception as e:
|
||
return {'success': False, 'error': f'AI分析失败: {str(e)}'}
|
||
|
||
|
||
def analyze_stock_stream(stock_code, stock_name, stock_data):
|
||
"""
|
||
使用豆包AI分析股票(流式生成器,用于SSE)
|
||
"""
|
||
|
||
# 构建分析提示词
|
||
fund_flow_str = format_fund_flow(stock_data.get('fund_flow_3days', []))
|
||
|
||
signal_status = stock_data.get('signal_status', [])
|
||
indicators = stock_data.get('indicators', {})
|
||
|
||
signal_lines = []
|
||
triggered_names = []
|
||
for s in signal_status:
|
||
status = '✅已触发' if s.get('triggered') else '○未触发'
|
||
signal_lines.append(f" {s.get('name','')}: {status} (胜率{s.get('strength',0)}%) — {s.get('description','')}")
|
||
if s.get('triggered'):
|
||
triggered_names.append(s.get('name', ''))
|
||
signal_text = '\n'.join(signal_lines) if signal_lines else ' 暂无扫描数据'
|
||
triggered_text = '、'.join(triggered_names) if triggered_names else '无'
|
||
|
||
macd = indicators.get('macd', {})
|
||
skdj = indicators.get('skdj', {})
|
||
ema = indicators.get('ema', {})
|
||
indicator_text = f"MACD: DIF={macd.get('dif','N/A')}, DEA={macd.get('dea','N/A')} | SKDJ: K={skdj.get('k','N/A')}, D={skdj.get('d','N/A')} | EMA: EMA3={ema.get('ema3','N/A')}, EMA21={ema.get('ema21','N/A')}"
|
||
|
||
prompt = f"""基于"交易信号实战体系"分析{stock_name}({stock_code})。
|
||
|
||
【基本面数据】
|
||
价格{stock_data.get('price', 'N/A')}元,PE={stock_data.get('pe', 'N/A')},PB={stock_data.get('pb', 'N/A')},ROE={stock_data.get('roe', 'N/A')}%,市值{format_market_cap(stock_data.get('total_market_cap'))}
|
||
资金流向:{fund_flow_str}
|
||
|
||
【技术指标】
|
||
{indicator_text}
|
||
|
||
【7大交易信号状态】(当前已触发: {triggered_text})
|
||
{signal_text}
|
||
|
||
【交易信号体系规则】
|
||
信号胜率排行: ★主升浪85% > 日线底背离80% > 龙抬头75% > 真龙70% > 短底背离65% > 老鼠仓60% > 反弹55%
|
||
标准牛股启动顺序: 日线底背离→龙抬头→真龙→★主升浪→反弹
|
||
体系最强战法: 1)日线底背离出现→关注 2)龙抬头出现→买入 3)真龙/★主升浪→持有加仓 4)不见主升浪→不出场
|
||
|
||
请按以下格式分析:
|
||
## 交易信号分析
|
||
根据7大信号的触发状态,判断该股在"底部→拉升"流程中处于哪个阶段。已触发的信号说明什么?距离下一个关键信号还有多远?
|
||
|
||
## 基本面分析
|
||
简要分析PE/ROE估值水平和盈利能力(2-3句话)
|
||
|
||
## 操作建议
|
||
基于体系最强战法规则,给出明确建议(关注/买入/持有加仓/减仓/观望),并解释理由。给出推荐率(0-100)
|
||
|
||
## 风险提示
|
||
列出2-3个关键风险点"""
|
||
|
||
try:
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {API_KEY}"
|
||
}
|
||
|
||
payload = {
|
||
"model": MODEL,
|
||
"max_completion_tokens": 2048,
|
||
"stream": True,
|
||
"messages": [
|
||
{"role": "user", "content": prompt}
|
||
]
|
||
}
|
||
|
||
response = requests.post(API_URL, headers=headers, json=payload, timeout=120, stream=True)
|
||
|
||
if response.status_code == 200:
|
||
in_reasoning = False
|
||
|
||
for line in response.iter_lines():
|
||
if line:
|
||
line_str = line.decode('utf-8')
|
||
if line_str.startswith('data: '):
|
||
data_str = line_str[6:]
|
||
if data_str == '[DONE]':
|
||
break
|
||
try:
|
||
data = json.loads(data_str)
|
||
if 'choices' in data and len(data['choices']) > 0:
|
||
delta = data['choices'][0].get('delta', {})
|
||
|
||
# 思考过程
|
||
if 'reasoning_content' in delta and delta['reasoning_content']:
|
||
if not in_reasoning:
|
||
yield {'type': 'reasoning_start'}
|
||
in_reasoning = True
|
||
yield {'type': 'reasoning', 'content': delta['reasoning_content']}
|
||
|
||
# 最终内容
|
||
if 'content' in delta and delta['content']:
|
||
if in_reasoning:
|
||
yield {'type': 'reasoning_end'}
|
||
in_reasoning = False
|
||
yield {'type': 'content', 'content': delta['content']}
|
||
except json.JSONDecodeError:
|
||
continue
|
||
|
||
if in_reasoning:
|
||
yield {'type': 'reasoning_end'}
|
||
else:
|
||
yield {'type': 'error', 'content': f'API请求失败: {response.status_code}'}
|
||
|
||
except requests.exceptions.Timeout:
|
||
yield {'type': 'error', 'content': 'AI分析超时,请稍后重试'}
|
||
except Exception as e:
|
||
yield {'type': 'error', 'content': f'AI分析失败: {str(e)}'}
|
||
|
||
|
||
def format_market_cap(value):
|
||
"""格式化市值"""
|
||
if not value:
|
||
return 'N/A'
|
||
try:
|
||
value = float(value)
|
||
if value >= 100000000000: # 千亿
|
||
return f"{value/100000000000:.2f}千亿"
|
||
elif value >= 100000000: # 亿
|
||
return f"{value/100000000:.2f}亿"
|
||
else:
|
||
return f"{value/10000:.2f}万"
|
||
except:
|
||
return str(value)
|
||
|
||
|
||
def format_fund_flow(fund_flow_list):
|
||
"""格式化资金流向数据"""
|
||
if not fund_flow_list:
|
||
return "暂无数据"
|
||
|
||
lines = []
|
||
for item in fund_flow_list:
|
||
date = item.get('date', '')
|
||
change = item.get('change_pct', 0)
|
||
main = item.get('main_pct', 0)
|
||
super_pct = item.get('super_pct', 0)
|
||
lines.append(f"- {date}: 涨跌{change:+.2f}%, 主力{main:+.2f}%, 超大单{super_pct:+.2f}%")
|
||
|
||
return '\n'.join(lines)
|