mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 02:43:35 +08:00
* feat(web): add FastAPI & React web connect * feat: add FastAPI package * feat: system base struct * feat: frontend base struct * feat: 基础分析接口,历史记录接口 * fix: 使用正确的 FastAPI 方法定义,避免线程阻塞 * fix: 对接历史报告页、详情页 * fix: 修复接口问题 * fix: 网络配置 允许公网访问 跨域配置 * fix: 页面打包 & 提供 Server 静态访问 * fix: 优化dock栏样式 * fix: 优化图标样式 * fix: 修改部分配色 * fix: 删除垃圾文档 * fix: 删除垃圾代码 * fix: 驼峰转换工具 * fix: 历史记录列表滚动加载(分页) * feat: 显示分析中任务 * fix: 调整布局 * fix: 优化 Market Sentiment 组件动效 * fix: 历史列表组件bug * fix: FastAPI 日志配置 * feat: 新闻历史接口 * feat: 资讯列表 * feat: 优化布局 * fix: 修复页面元素变宽问题 * fix: 中文标题 * fix: 任务列表状态显示问题 * fix: 抽取日志配置 * fix: 优化报告价格显示 * fix: 修复编译错误 * fix: FastAPI 启动提取到 main.py * fix: 补充新web-ui启动文档 * fix: 默认发送通知 * fix: FastAPI 模式适配 docker * fix: FastAPI 模式文档 * Update api/v1/endpoints/stocks.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: package-lock.json * fix: 修改错别字 * fix: 更新文档说明 * fix: 更新文档说明 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
179 lines
5.2 KiB
Python
179 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
===================================
|
||
分析服务层
|
||
===================================
|
||
|
||
职责:
|
||
1. 封装股票分析逻辑
|
||
2. 调用 analyzer 和 pipeline 执行分析
|
||
3. 保存分析结果到数据库
|
||
"""
|
||
|
||
import logging
|
||
import uuid
|
||
from typing import Optional, Dict, Any
|
||
|
||
from src.repositories.analysis_repo import AnalysisRepository
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AnalysisService:
|
||
"""
|
||
分析服务
|
||
|
||
封装股票分析相关的业务逻辑
|
||
"""
|
||
|
||
def __init__(self):
|
||
"""初始化分析服务"""
|
||
self.repo = AnalysisRepository()
|
||
|
||
def analyze_stock(
|
||
self,
|
||
stock_code: str,
|
||
report_type: str = "detailed",
|
||
force_refresh: bool = False,
|
||
query_id: Optional[str] = None,
|
||
send_notification: bool = True
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
执行股票分析
|
||
|
||
Args:
|
||
stock_code: 股票代码
|
||
report_type: 报告类型 (simple/detailed)
|
||
force_refresh: 是否强制刷新
|
||
query_id: 查询 ID(可选)
|
||
send_notification: 是否发送通知(API 触发默认发送)
|
||
|
||
Returns:
|
||
分析结果字典,包含:
|
||
- stock_code: 股票代码
|
||
- stock_name: 股票名称
|
||
- report: 分析报告
|
||
"""
|
||
try:
|
||
# 导入分析相关模块
|
||
from src.config import get_config
|
||
from src.core.pipeline import StockAnalysisPipeline
|
||
from src.enums import ReportType
|
||
|
||
# 生成 query_id
|
||
if query_id is None:
|
||
query_id = uuid.uuid4().hex
|
||
|
||
# 获取配置
|
||
config = get_config()
|
||
|
||
# 创建分析流水线
|
||
pipeline = StockAnalysisPipeline(
|
||
config=config,
|
||
query_id=query_id,
|
||
query_source="api"
|
||
)
|
||
|
||
# 确定报告类型
|
||
rt = ReportType.FULL if report_type == "detailed" else ReportType.SIMPLE
|
||
|
||
# 执行分析
|
||
result = pipeline.process_single_stock(
|
||
code=stock_code,
|
||
skip_analysis=False,
|
||
single_stock_notify=send_notification,
|
||
report_type=rt
|
||
)
|
||
|
||
if result is None:
|
||
logger.warning(f"分析股票 {stock_code} 返回空结果")
|
||
return None
|
||
|
||
# 构建响应
|
||
return self._build_analysis_response(result, query_id)
|
||
|
||
except Exception as e:
|
||
logger.error(f"分析股票 {stock_code} 失败: {e}", exc_info=True)
|
||
return None
|
||
|
||
def _build_analysis_response(
|
||
self,
|
||
result: Any,
|
||
query_id: str
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
构建分析响应
|
||
|
||
Args:
|
||
result: AnalysisResult 对象
|
||
query_id: 查询 ID
|
||
|
||
Returns:
|
||
格式化的响应字典
|
||
"""
|
||
# 获取狙击点位
|
||
sniper_points = {}
|
||
if hasattr(result, 'get_sniper_points'):
|
||
sniper_points = result.get_sniper_points() or {}
|
||
|
||
# 计算情绪标签
|
||
sentiment_label = self._get_sentiment_label(result.sentiment_score)
|
||
|
||
# 构建报告结构
|
||
report = {
|
||
"meta": {
|
||
"query_id": query_id,
|
||
"stock_code": result.code,
|
||
"stock_name": result.name,
|
||
"report_type": "detailed",
|
||
"current_price": result.current_price,
|
||
"change_pct": result.change_pct,
|
||
},
|
||
"summary": {
|
||
"analysis_summary": result.analysis_summary,
|
||
"operation_advice": result.operation_advice,
|
||
"trend_prediction": result.trend_prediction,
|
||
"sentiment_score": result.sentiment_score,
|
||
"sentiment_label": sentiment_label,
|
||
},
|
||
"strategy": {
|
||
"ideal_buy": sniper_points.get("ideal_buy"),
|
||
"secondary_buy": sniper_points.get("secondary_buy"),
|
||
"stop_loss": sniper_points.get("stop_loss"),
|
||
"take_profit": sniper_points.get("take_profit"),
|
||
},
|
||
"details": {
|
||
"news_summary": result.news_summary,
|
||
"technical_analysis": result.technical_analysis,
|
||
"fundamental_analysis": result.fundamental_analysis,
|
||
"risk_warning": result.risk_warning,
|
||
}
|
||
}
|
||
|
||
return {
|
||
"stock_code": result.code,
|
||
"stock_name": result.name,
|
||
"report": report,
|
||
}
|
||
|
||
def _get_sentiment_label(self, score: int) -> str:
|
||
"""
|
||
根据评分获取情绪标签
|
||
|
||
Args:
|
||
score: 情绪评分 (0-100)
|
||
|
||
Returns:
|
||
情绪标签
|
||
"""
|
||
if score >= 80:
|
||
return "极度乐观"
|
||
elif score >= 60:
|
||
return "乐观"
|
||
elif score >= 40:
|
||
return "中性"
|
||
elif score >= 20:
|
||
return "悲观"
|
||
else:
|
||
return "极度悲观"
|