Files
stock/stock-html/services/doubao_api.py
T
selfrelease 2b5a32ca1e feat: 新增外部因素分析模块+综合评分引擎+算法文档重构
新增模块:
- 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整合外部因素修正推荐
2026-07-17 23:50:42 +08:00

255 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
豆包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)