""" PE / PB 估值因子。 基于日线收盘价 + 财务数据(EPS/每股净资产)计算。 """ import pandas as pd from factors.base import BaseFactor class PEFactor(BaseFactor): """ 市盈率因子 = close / eps。 eps 来自财务数据中的 'eps' 列或 TTM EPS。 因子值越大表示估值越贵。 """ category = "fundamental" def __init__(self, financial_df: pd.DataFrame | None = None): """ 参数: financial_df: 含 'end_date' 和 'eps' 的 DataFrame。 """ self._financial_df = financial_df self.name = "pe" def calculate(self, df: pd.DataFrame) -> pd.Series: if self._financial_df is None or self._financial_df.empty: return pd.Series(float("nan"), index=df.index) eps_series = _map_to_daily(df, self._financial_df, "eps") close = df["close"] return close / eps_series.replace(0, float("nan")) def get_required_columns(self) -> list[str]: return ["close"] class PBFactor(BaseFactor): """ 市净率因子 = close / bvps(每股净资产)。 因子值越大表示估值越贵。 """ category = "fundamental" def __init__(self, financial_df: pd.DataFrame | None = None): self._financial_df = financial_df self.name = "pb" def calculate(self, df: pd.DataFrame) -> pd.Series: if self._financial_df is None or self._financial_df.empty: return pd.Series(float("nan"), index=df.index) bvps_series = _map_to_daily(df, self._financial_df, "bvps") return df["close"] / bvps_series.replace(0, float("nan")) def get_required_columns(self) -> list[str]: return ["close"] class EPFactor(BaseFactor): """ 盈利收益率因子 = eps / close = 1 / PE。 值越大表示估值越便宜,适合与动量等因子同向排序。 """ category = "fundamental" def __init__(self, financial_df: pd.DataFrame | None = None): self._financial_df = financial_df self.name = "ep" def calculate(self, df: pd.DataFrame) -> pd.Series: if self._financial_df is None or self._financial_df.empty: return pd.Series(float("nan"), index=df.index) eps_series = _map_to_daily(df, self._financial_df, "eps") return eps_series / df["close"].replace(0, float("nan")) * 100 def get_required_columns(self) -> list[str]: return ["close"] def _map_to_daily( daily_df: pd.DataFrame, fina_df: pd.DataFrame, column: str, ) -> pd.Series: """将季度财务数据填充到日线索引(前值填充)。""" fina = fina_df[["end_date", column]].dropna().copy() fina["end_date"] = fina["end_date"].astype(str) fina = fina.sort_values("end_date") result = pd.Series(float("nan"), index=daily_df.index) if fina.empty: return result dates = pd.to_datetime(daily_df.index, format="%Y%m%d", errors="coerce") fina_dates = pd.to_datetime(fina["end_date"], format="%Y%m%d", errors="coerce") for i, fina_date in enumerate(fina_dates): mask = dates >= fina_date if i + 1 < len(fina_dates): mask &= dates < fina_dates.iloc[i + 1] result[mask] = fina[column].iloc[i] return result.astype("float64")