- TushareProvider:频率超限按指数退避重试(不再一次 200/min 即中断),最长等待 30s
- SinaProvider 重构(参考 cc-cursor 公开接口实现):
· 新增财务通道 CompanyFinanceService.getFinanceReport2022(source=gjzb) → FinancialIndicator
(report_date / announce_date=publish_date),与 Tushare fina_indicator schema 一致
· 日 K 保留 jsonp(前复权),统一 UA + 重试
· 不支持方法仍抛 DataSourceNotSupported(复权因子/交易日历/基础信息)
- FailoverProvider 现在可对 daily 与 financial 兜底(CLI _failover_provider 接 SinaProvider)
- DailyBar + stock_daily 表新增 source/adjust 列:新浪兜底行标记 sina/qfq,
Tushare 恢复后 --resume 按同键覆盖回不复权 → 两源格式一致且可追溯
- 迁移 91c4e27a03fb 已生成;执行需在全市场同步结束后:uv run alembic upgrade head
- 测试 34+ 项(新浪财务解析/格式一致/限速退避等)通过
113 lines
3.8 KiB
Python
113 lines
3.8 KiB
Python
"""市场数据领域实体(Phase 1)。
|
|
|
|
约定(AGENT.md §8/§9):
|
|
- 行情时间用 trade_date;财务数据同时区分 report_date(报告期)与 announce_date(公告日)
|
|
- 禁止以 report_date 作可见性依据 —— 只允许 announce_date 已过的数据进入研究
|
|
- 复权一律通过独立 AdjustFactor 表达,不在此层偷偷改前/后复权口径
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
# 常见精度:价格 4 位小数;成交量(股) 2 位;金额(元) 2 位
|
|
PRICE_PLACES = Decimal("0.0001")
|
|
AMOUNT_PLACES = Decimal("0.01")
|
|
|
|
|
|
class Stock(BaseModel):
|
|
"""A 股基础信息。symbol 统一为 Tushare 风格,如 600519.SH。"""
|
|
|
|
model_config = ConfigDict(str_strip_whitespace=True)
|
|
|
|
symbol: str = Field(pattern=r"^\d{6}\.(SH|SZ|BJ)$", description="如 600519.SH")
|
|
name: str
|
|
industry: str | None = None
|
|
area: str | None = None
|
|
market: str | None = Field(default=None, description="主板/创业板/科创板/北交所")
|
|
exchange: str | None = None
|
|
list_date: date
|
|
delist_date: date | None = None
|
|
status: str = Field(default="L", description="L 上市 / D 退市 / P 暂停")
|
|
|
|
|
|
class TradingCalendar(BaseModel):
|
|
"""交易日历。"""
|
|
|
|
calendar_date: date
|
|
is_open: bool = True
|
|
|
|
|
|
class DailyBar(BaseModel):
|
|
"""日线。默认不复权(source=tushare, adjust=none)。
|
|
|
|
备用源兜底行会标记 source=sina、adjust=qfq(新浪返回前复权价)。
|
|
字段统一、可区分、可追溯(AGENT §5.2/§8):研究侧应优先消费
|
|
source=tushare 且 adjust=none 的行;新浪行仅在 Tushare 不可用期间作为兜底,
|
|
Tushare 恢复后重跑 --resume 会按日覆盖回不复权口径。
|
|
"""
|
|
|
|
symbol: str
|
|
trade_date: date
|
|
source: str = Field(default="tushare", description="tushare | sina")
|
|
adjust: str = Field(default="none", description="none 不复权 | qfq 前复权")
|
|
open: Decimal | None = None
|
|
high: Decimal | None = None
|
|
low: Decimal | None = None
|
|
close: Decimal | None = None
|
|
volume: Decimal | None = Field(default=None, description="成交量(股)")
|
|
amount: Decimal | None = Field(default=None, description="成交额(元)")
|
|
|
|
@property
|
|
def is_complete(self) -> bool:
|
|
"""基础行情字段是否齐全(供校验器使用)。"""
|
|
return all(
|
|
v is not None
|
|
for v in (self.open, self.high, self.low, self.close, self.volume, self.amount)
|
|
)
|
|
|
|
|
|
class AdjustFactor(BaseModel):
|
|
"""复权因子。因子原始口径由数据源决定,必须与数据源文档一致地存取。"""
|
|
|
|
symbol: str
|
|
trade_date: date
|
|
factor: Decimal
|
|
|
|
|
|
class FinancialIndicator(BaseModel):
|
|
"""核心财务指标(快照)。
|
|
|
|
可见性红线:研究侧查询一律按 announce_date <= as_of_date 过滤,
|
|
report_date 只表示报告所属期间,不代表公开时间。
|
|
"""
|
|
|
|
symbol: str
|
|
report_date: date
|
|
announce_date: date
|
|
eps: Decimal | None = None
|
|
roe: Decimal | None = None
|
|
total_revenue: Decimal | None = None
|
|
net_profit: Decimal | None = None
|
|
gross_margin: Decimal | None = None
|
|
|
|
def announced_by(self, as_of_date: date) -> bool:
|
|
"""as_of_date(含当日)是否已可见。防未来函数的核心判断。"""
|
|
return self.announce_date <= as_of_date
|
|
|
|
|
|
class SyncLog(BaseModel):
|
|
"""数据拉取审计记录(AGENT.md §7:来源必须可追踪,禁止静默切换)。"""
|
|
|
|
source: str
|
|
api: str
|
|
request_time: datetime = Field(default_factory=datetime.utcnow)
|
|
success: bool
|
|
failure_reason: str | None = None
|
|
row_count: int = 0
|
|
data_start: date | None = None
|
|
data_end: date | None = None
|