a68cf4905e
把聚宽"全天候轮动"(post48819)搬到 BulletTrade。融合=pip+扩展点注入 (SanguoMiniQmtProvider 继承 MiniQMTProvider 只 override get_fundamentals, set_data_provider 公开 API 注入, BulletTrade 源码 0 改动)。 - providers: SanguoMiniQmtProvider 补 get_fundamentals(PershareIndex+自算PE/PS/PB/PCF/市值/ROIC) - strategies/all_weather: 4选股函数+大小盘轮动+ETF兜底+涨停止损(聚宽风格翻译) - factors(估值/ROIC自算) + filters(ST/涨跌停/次新/停牌) - 88/88 测试 Mac+VPS 双过; VPS 回测 pipeline 跑通(修9bug:Capital单位/日期格式/百分数口径/11字段alias) - 实盘 runner_live+runbook 就绪等交易日; DEFAULT_DATA_PROVIDER=miniqmt env 不装 jqdatasdk - 文档: sanguo_portfolio_plan / portfolio_backtest_result / portfolio_live_runbook
130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
"""估值因子:市值 + PE/PB/PS/PCF 自算。
|
||
|
||
为什么自己算而不是直接读 miniQMT:
|
||
- miniQMT 的 PershareIndex 给的是现成指标(ROE/ROA/EPS/毛利率...),**没有** valuation
|
||
表(PE/PB/PS/PCF/总市值/流通市值)。聚宽的 valuation 表是单独一张快照表,miniQMT
|
||
对应能力 = ``xtdata.get_market_data_ex`` 拿 close + ``Capital`` 拿股本 → 乘出来。
|
||
- 单期×4 近似 TTM:报告期利润/现金流是单季累计值,直接×4 当 TTM 用是**近似口径**,
|
||
对账聚宽时会偏差(聚宽是滚 4 季度精确 TTM)。回测对相对排序影响小,绝对估值会偏。
|
||
精确 TTM 滚 4 季度留 v2。
|
||
|
||
口径约定(字段名对齐聚宽 valuation 表):
|
||
- ``market_cap`` → 总市值(亿元) = close × total_capital / 1e8
|
||
- ``circulating_market_cap`` → 流通市值(亿元) = close × circulating_capital / 1e8
|
||
- ``pe_ratio`` → close × total_capital / (归母净利润 × 4) 近似 TTM
|
||
- ``pb_ratio`` → close × total_capital / 归母净资产
|
||
- ``ps_ratio`` → close × total_capital / (营业收入 × 4) 近似 TTM
|
||
- ``pcf_ratio`` → close × total_capital / (经营现金流 × 4) 近似 TTM
|
||
|
||
所有 ``calc_*`` 接受标量或 pandas Series/numpy array(靠 pandas/numpy 广播)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Union
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
NumberLike = Union[float, int, "pd.Series", "np.ndarray"]
|
||
|
||
# 分母下限,避免除零产生 inf 污染后续排序。1e-9 远小于任何实际财务数据。
|
||
_EPS = 1e-9
|
||
|
||
|
||
def calc_market_cap(close: NumberLike, total_capital: NumberLike) -> NumberLike:
|
||
"""总市值(元) = close × 总股本。聚宽 ``market_cap`` 是亿元,见 ``to_yi``。"""
|
||
return close * total_capital
|
||
|
||
|
||
def calc_circulating_market_cap(
|
||
close: NumberLike, circulating_capital: NumberLike
|
||
) -> NumberLike:
|
||
"""流通市值(元) = close × 流通股本。聚宽 ``circulating_market_cap`` 是亿元。"""
|
||
return close * circulating_capital
|
||
|
||
|
||
def _safe_denominator(value: NumberLike) -> NumberLike:
|
||
"""分母保护:把 |x|<eps 的值替换成带符号的 eps,保留正负号。"""
|
||
if isinstance(value, (pd.Series, pd.DataFrame)):
|
||
sign = np.sign(value)
|
||
sign = sign.where(sign != 0, 1.0)
|
||
return sign * value.abs().where(value.abs() > _EPS, _EPS)
|
||
if isinstance(value, np.ndarray):
|
||
sign = np.sign(value)
|
||
sign[sign == 0] = 1.0
|
||
abs_v = np.abs(value)
|
||
return sign * np.where(abs_v > _EPS, abs_v, _EPS)
|
||
if value is None or (isinstance(value, float) and math.isnan(value)):
|
||
return _EPS
|
||
abs_v = abs(value)
|
||
return value if abs_v > _EPS else (1.0 if value >= 0 else -1.0) * _EPS
|
||
|
||
|
||
def calc_pe(
|
||
close: NumberLike,
|
||
net_profit_excl_min_int: NumberLike,
|
||
total_capital: NumberLike,
|
||
) -> NumberLike:
|
||
"""市盈率(近似 TTM) = close × total_capital / (归母净利润 × 4)。
|
||
|
||
口径:``net_profit_excl_min_int`` 是最近报告期**单期**归母净利润,
|
||
×4 近似 TTM(标注"近似口径,对账聚宽时校准")。
|
||
亏损股返回负 PE,保留排序信息(聚宽也是负值)。
|
||
"""
|
||
numerator = close * total_capital
|
||
denominator = _safe_denominator(net_profit_excl_min_int) * 4
|
||
return numerator / denominator
|
||
|
||
|
||
def calc_pb(
|
||
close: NumberLike,
|
||
tot_shrhldr_eqy_excl_min_int: NumberLike,
|
||
total_capital: NumberLike,
|
||
) -> NumberLike:
|
||
"""市净率 = close × total_capital / 归母净资产。
|
||
|
||
口径:净资产是时点存量,不需要×4。
|
||
"""
|
||
numerator = close * total_capital
|
||
denominator = _safe_denominator(tot_shrhldr_eqy_excl_min_int)
|
||
return numerator / denominator
|
||
|
||
|
||
def calc_ps(
|
||
close: NumberLike,
|
||
revenue: NumberLike,
|
||
total_capital: NumberLike,
|
||
) -> NumberLike:
|
||
"""市销率(近似 TTM) = close × total_capital / (营业收入 × 4)。"""
|
||
numerator = close * total_capital
|
||
denominator = _safe_denominator(revenue) * 4
|
||
return numerator / denominator
|
||
|
||
|
||
def calc_pcf(
|
||
close: NumberLike,
|
||
net_oper_cash_flow: NumberLike,
|
||
total_capital: NumberLike,
|
||
) -> NumberLike:
|
||
"""市现率(近似 TTM) = close × total_capital / (经营现金流 × 4)。"""
|
||
numerator = close * total_capital
|
||
denominator = _safe_denominator(net_oper_cash_flow) * 4
|
||
return numerator / denominator
|
||
|
||
|
||
def to_yi(value: NumberLike) -> NumberLike:
|
||
"""元 → 亿元(聚宽 valuation 口径)。"""
|
||
return value / 1e8
|
||
|
||
|
||
__all__ = [
|
||
"calc_market_cap",
|
||
"calc_circulating_market_cap",
|
||
"calc_pe",
|
||
"calc_pb",
|
||
"calc_ps",
|
||
"calc_pcf",
|
||
"to_yi",
|
||
]
|