Files
qlib/backend/app/domain/providers.py
T
Simon 23972e7063 feat: 股息率案例口径 + 策略库与图表统一 + 回测存档完整化
汇总三轮未提交的开发(每轮均在本机 MariaDB + 真实浏览器上验证):

1) 股息率案例(全市场股息率最高 n 只,默认 20,每 m 月择股)
   - 新增日频估值表 daily_basic + 迁移;股息率因子(dv_ratio / dividend_yield / TTM)
   - 名称历史表 stock_name_history:剔除 ST 按**择股日当时名称**判定,消除
     「曾高股息后 ST」的股息陷阱(实测 3.70pp 偏差)
   - 区间择股/调仓双周期(m 择股 / y 调仓)、指数成分与白名单、停牌近似剔除
   - 复权因子口径核对(4,164,742 行、缺失 0.0%)、收盘价成交与涨跌停拦单
   - 案例实测:2020-01-01~2026-09-04 总收益 +24.86%(年化 3.52%、回撤 -28.58%)

2) 策略库与前端统一
   - strategy 表 + CRUD/PUT 原地更新 + `describe_strategy` 按 spec 真实推导
     「一句话说明 + 计算公式 + 执行步骤 + 注意事项」(与引擎实执行规则同源)
   - 任何出现股票代码处都成对显示名称且可点击进个股页
   - 全站图表基座统一 TradingView Lightweight Charts(ECharts 依赖、
     锁文件、组件与文档标注一并清除),买卖点标记只落在真实交易日上

3) 回测存档完整化(可往复查看)
   - 同步端点(POST /api/backtests、/api/factor-tests)此前完全不落库 → 现在同样归档,
     归档 id 经响应头 X-Experiment-Id 返回(不破坏 response_model)
   - data_version 首次真实写入(数据快照指纹:最新交易日 + 各表规模)
   - 个股收益曲线默认**全量保存**(此前硬截断 60 只);超出体积预算才裁剪,
     并写 archive_meta(机器可读)+ unimplemented(人可读)如实标注
   - 列表 kind/q 过滤 + X-Total-Count(此前 limit=50 静默截断)、DELETE 归档
   - 只读归档页 /experiments/{id}(Server Component,SSR 直出**选股条件**与
     **交易执行依据**);结果视图按 kind 分发(backtest/factor_test/selection),
     非回测归档不套用回测口径
   - 新增 CLI:prune_experiments(保留策略,默认 dry-run)、
     restore_experiment_from_job(从 Job 副本按原 id 重建被删的历史归档,默认 dry-run)

门禁:pytest 388 passed、ruff All checks passed、tsc 0 错误、图表单测 7 passed、
next build 成功、契约脚本 verify_strategy_workspace 59/59(含按 kind 逐类验证归档页)。
2026-09-20 07:31:04 +08:00

87 lines
3.3 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.
"""MarketDataProvider(数据源抽象)。
业务层只依赖本 Protocol(AGENT.md §6),禁止在业务代码中 import
tushare / 新浪实现。数据源一律返回 domain.entities 中的归一化实体。
"""
from __future__ import annotations
from datetime import date
from typing import Protocol
from app.domain.entities.index import IndexWeight
from app.domain.entities.market import (
AdjustFactor,
DailyBar,
DailyBasic,
FinancialIndicator,
Stock,
StockNameHistory,
TradingCalendar,
)
class MarketDataProvider(Protocol):
"""统一市场数据源接口。
实现约定:
- get_daily 返回**不复权**行情(复权经 AdjustFactor 显式计算,禁止静默改口径)
- get_financial 返回带 announce_date 的指标,供上层按 as_of 过滤
- 实现不得抛出裸连接异常以外的噪音;业务错误应转为 DataSourceError
"""
name: str
def get_stock_basic(self, list_status: str = "L") -> list[Stock]:
"""股票基础信息。list_status: L=上市 / D=退市 / P=暂停上市(tushare 口径)。
默认 "L" 保持既有行为不变;研究侧的**幸存者偏差**修正依赖 "D"(已退市)
——退市股的历史行情与 delist_date 缺失会让回测系统性高估收益。
"""
...
def get_trade_cal(self, start: date, end: date) -> list[TradingCalendar]: ...
def get_daily(self, symbol: str, start: date, end: date) -> list[DailyBar]: ...
def get_adjust_factor(self, symbol: str, start: date, end: date) -> list[AdjustFactor]: ...
def get_financial(
self,
symbol: str,
start: date | None = None,
end: date | None = None,
) -> list[FinancialIndicator]:
"""财务指标快照。
start/end 为**报告期**窗口(对应 Tushare fina_indicator 的
start_date/end_date 参数,按报告期过滤);不传表示全量历史。
新浪接口不支持按窗口拉取,提供方会忽略窗口后由调用方自行过滤。
"""
def get_index_weight(self, index_code: str) -> list[IndexWeight]:
"""指数历史成分(含权重):每期成分快照 → IndexWeight(index_code, trade_date, symbol)。
供 index_weight 同步与历史成分 Universe(v3 §9)。"""
def get_name_changes(self, start: date, end: date) -> list[StockNameHistory]:
"""区间内全市场股票名称变更(时点 ST / 风险警示判定依据)。
仅 Tushare 提供;备用源应抛 `DataSourceNotSupported`(不得静默返回空列表,
否则名称历史会「看起来同步成功但一条没有」,导致时点 ST 静默降级)。
"""
...
def get_daily_basic(self, trade_date: date) -> list[DailyBasic]:
"""单交易日全市场每日指标(估值 / 股息率 / 市值)。
按**交易日**整表拉取(Tushare daily_basic 支持 trade_date 参数一次返回全市场),
幂等键 (symbol, trade_date)。
实现约定:
- 只能返回该 trade_date 当天已可得的值(dv_ratio 为时点值,天然无未来函数);
- 不支持本接口的数据源(如新浪)必须抛 DataSourceNotSupported,
禁止返回空列表冒充成功(AGENT.md §7:禁止静默切换)。
"""