- 新增 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 扩充配置项
100 lines
3.9 KiB
Python
100 lines
3.9 KiB
Python
"""
|
||
财务数据 → 日线映射(负责消除披露时点前视偏差)。
|
||
|
||
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") |