Files
daily_stock_analysis/src/services/stock_service.py
Krane 9847157980 Feature/React web support 新的WebUI (#256)
* 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>
2026-02-05 20:56:38 +08:00

187 lines
6.4 KiB
Python
Raw Permalink 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.

# -*- coding: utf-8 -*-
"""
===================================
股票数据服务层
===================================
职责:
1. 封装股票数据获取逻辑
2. 提供实时行情和历史数据接口
"""
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
from src.repositories.stock_repo import StockRepository
logger = logging.getLogger(__name__)
class StockService:
"""
股票数据服务
封装股票数据获取的业务逻辑
"""
def __init__(self):
"""初始化股票数据服务"""
self.repo = StockRepository()
def get_realtime_quote(self, stock_code: str) -> Optional[Dict[str, Any]]:
"""
获取股票实时行情
Args:
stock_code: 股票代码
Returns:
实时行情数据字典
"""
try:
# 调用数据获取器获取实时行情
from data_provider.base import DataFetcherManager
manager = DataFetcherManager()
quote = manager.get_realtime_quote(stock_code)
if quote is None:
logger.warning(f"获取 {stock_code} 实时行情失败")
return None
# UnifiedRealtimeQuote 是 dataclass使用 getattr 安全访问字段
# 字段映射: UnifiedRealtimeQuote -> API 响应
# - code -> stock_code
# - name -> stock_name
# - price -> current_price
# - change_amount -> change
# - change_pct -> change_percent
# - open_price -> open
# - high -> high
# - low -> low
# - pre_close -> prev_close
# - volume -> volume
# - amount -> amount
return {
"stock_code": getattr(quote, "code", stock_code),
"stock_name": getattr(quote, "name", None),
"current_price": getattr(quote, "price", 0.0) or 0.0,
"change": getattr(quote, "change_amount", None),
"change_percent": getattr(quote, "change_pct", None),
"open": getattr(quote, "open_price", None),
"high": getattr(quote, "high", None),
"low": getattr(quote, "low", None),
"prev_close": getattr(quote, "pre_close", None),
"volume": getattr(quote, "volume", None),
"amount": getattr(quote, "amount", None),
"update_time": datetime.now().isoformat(),
}
except ImportError:
logger.warning("DataFetcherManager 未找到,使用占位数据")
return self._get_placeholder_quote(stock_code)
except Exception as e:
logger.error(f"获取实时行情失败: {e}", exc_info=True)
return None
def get_history_data(
self,
stock_code: str,
period: str = "daily",
days: int = 30
) -> Dict[str, Any]:
"""
获取股票历史行情
Args:
stock_code: 股票代码
period: K 线周期 (daily/weekly/monthly)
days: 获取天数
Returns:
历史行情数据字典
Raises:
ValueError: 当 period 不是 daily 时抛出weekly/monthly 暂未实现)
"""
# 验证 period 参数,只支持 daily
if period != "daily":
raise ValueError(
f"暂不支持 '{period}' 周期,目前仅支持 'daily'"
"weekly/monthly 聚合功能将在后续版本实现。"
)
try:
# 调用数据获取器获取历史数据
from data_provider.base import DataFetcherManager
manager = DataFetcherManager()
df, source = manager.get_daily_data(stock_code, days=days)
if df is None or df.empty:
logger.warning(f"获取 {stock_code} 历史数据失败")
return {"stock_code": stock_code, "period": period, "data": []}
# 获取股票名称
stock_name = manager.get_stock_name(stock_code)
# 转换为响应格式
data = []
for _, row in df.iterrows():
date_val = row.get("date")
if hasattr(date_val, "strftime"):
date_str = date_val.strftime("%Y-%m-%d")
else:
date_str = str(date_val)
data.append({
"date": date_str,
"open": float(row.get("open", 0)),
"high": float(row.get("high", 0)),
"low": float(row.get("low", 0)),
"close": float(row.get("close", 0)),
"volume": float(row.get("volume", 0)) if row.get("volume") else None,
"amount": float(row.get("amount", 0)) if row.get("amount") else None,
"change_percent": float(row.get("pct_chg", 0)) if row.get("pct_chg") else None,
})
return {
"stock_code": stock_code,
"stock_name": stock_name,
"period": period,
"data": data,
}
except ImportError:
logger.warning("DataFetcherManager 未找到,返回空数据")
return {"stock_code": stock_code, "period": period, "data": []}
except Exception as e:
logger.error(f"获取历史数据失败: {e}", exc_info=True)
return {"stock_code": stock_code, "period": period, "data": []}
def _get_placeholder_quote(self, stock_code: str) -> Dict[str, Any]:
"""
获取占位行情数据(用于测试)
Args:
stock_code: 股票代码
Returns:
占位行情数据
"""
return {
"stock_code": stock_code,
"stock_name": f"股票{stock_code}",
"current_price": 0.0,
"change": None,
"change_percent": None,
"open": None,
"high": None,
"low": None,
"prev_close": None,
"volume": None,
"amount": None,
"update_time": datetime.now().isoformat(),
}