Files
myquant/finance/factors/fundamental/_mapping.py
T
Simon 73d191b43a feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化
- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead)
- 数据层: data_manager / dao 优化,新增 upsert 逻辑
- 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构
- 回测层: vectorbt/engine 大改动(251 行),report 增强
- ML 层: features/backtest_integration 特征工程与回测优化
- CLI: agent_cli 重构
- config/settings 扩充配置项
2026-08-31 14:01:06 +08:00

100 lines
3.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.
"""
财务数据 → 日线映射(负责消除披露时点前视偏差)。
A 股季报的披露日远晚于报告期末:
- 一季报 / 年报 最迟约 4/30
- 中报 最迟约 8/31
- 三季报 最迟约 10/31
若直接把报告期 end_date 当日就"看到"本期财务结果,会引入前视偏差。
本模块统一在 end_date 上叠加一个保守的披露滞后:
1) 财务帧中若带 ann_date(实际披露日),则优先用 ann_date 作为可用日;
2) 否则按报告期月份推断法定披露时点,作为保守可用日。
"""
from __future__ import annotations
import pandas as pd
def _disclosure_available_date(end_date_ts: pd.Timestamp) -> pd.Timestamp:
"""根据报告期末推断法定披露可用日(无 ann_date 时的保守近似)。
一季度(0331)→4/30;中报(0630)→8/31;三季报(0930)→10/31;年报(1231)→次年4/30。
"""
if end_date_ts.month == 3 and end_date_ts.day == 31:
return pd.Timestamp(year=end_date_ts.year, month=4, day=30)
if end_date_ts.month == 6 and end_date_ts.day == 30:
return pd.Timestamp(year=end_date_ts.year, month=8, day=31)
if end_date_ts.month == 9 and end_date_ts.day == 30:
return pd.Timestamp(year=end_date_ts.year, month=10, day=31)
# 年报 12/31 → 次年 4/30
return pd.Timestamp(year=end_date_ts.year + 1, month=4, day=30)
def _eff_available_dates(fina_df: pd.DataFrame) -> pd.DataFrame:
"""计算每期财务的"可用日"(取最小滞后:有 ann_date 用它,否则法定截止)。"""
fina = fina_df.copy()
# 数值型 ann_date(YYYYMMDD)→ datetime;缺失用报告期末推断
if "ann_date" in fina.columns:
ann = pd.to_datetime(fina["ann_date"].astype(str), format="%Y%m%d", errors="coerce")
else:
ann = pd.Series(pd.NaT, index=fina.index)
end = pd.to_datetime(
fina["end_date"].astype(str), format="%Y%m%d", errors="coerce")
avail = ann.fillna(pd.Series(
[_disclosure_available_date(x) if not pd.isna(x) else pd.NaT for x in end],
index=end.index,
))
# 极少数 ann_date 早于报告期末(脏数据)时兜底用期末
avail = avail.where(avail >= end, end)
fina["_avail"] = avail
return fina
def effective_available_dates(fina_df: pd.DataFrame) -> pd.DataFrame:
"""
(公开) 返回带 _avail(披露可用日)的财务帧,供同比/环比等因子复用。
要求 fina_df 至少含 'end_date';可选 'ann_date'。
"""
return _eff_available_dates(fina_df)
def map_fundamental_to_daily(
daily_df: pd.DataFrame,
fina_df: pd.DataFrame,
column: str,
) -> pd.Series:
"""
将季度财务数据按披露可用日映射到日线索引(前值填充)。
- 优先按 ann_date(实际披露日)对齐;
- 无 ann_date 时按报告期末的法定披露截止日保守对齐;
- 从而避免"财报在披露日之前就被回测看到"的前视偏差。
"""
if column not in fina_df.columns or "end_date" not in fina_df.columns:
return pd.Series(float("nan"), index=daily_df.index)
cols = ["end_date", column] + (["ann_date"] if "ann_date" in fina_df.columns else [])
fina = fina_df[cols].dropna(subset=["end_date", column]).copy()
if fina.empty:
return pd.Series(float("nan"), index=daily_df.index)
fina = _eff_available_dates(fina)
fina = fina.sort_values("_avail")
dates = pd.to_datetime(daily_df.index, format="%Y%m%d", errors="coerce")
result = pd.Series(float("nan"), index=daily_df.index)
avail_dates = fina["_avail"].values
for i, avail_dt in enumerate(avail_dates):
if pd.isna(avail_dt):
continue
mask = dates >= avail_dt
if i + 1 < len(avail_dates) and not pd.isna(avail_dates[i + 1]):
mask &= dates < avail_dates[i + 1]
result[mask] = fina[column].iloc[i]
return result.astype("float64")