教程

A 股因子选股怎么做:用一个接口筛出低估高 ROE 的股票

因子选股 = 把财务与行情指标写成条件,让接口全市场筛一遍。本文讲清三件事:表达式怎么写、结果怎么读、为什么筛完必须做一次数据体检(否则很容易把异常值当机会)。

一、因子选股是什么(30 秒版)

因子就是一个可计算的指标:`PE_TTM`(市盈率)、`ROETTM`(净资产收益率)、`DividendRatioTTM`(股息率)、`DebtAssetsRatio`(资产负债率)……因子选股就是把这些指标写成条件,让接口从全市场 5000 多只里筛出同时满足的股票。

它替代的是"手动翻财报"——把「低估值 + 高盈利 + 低负债」这类想法,变成一条可复现、可回测的规则。

  • 可复现:同一条件任何时候跑,结果口径一致(人工翻财报做不到)
  • 可回测:配合 `--date` 可以拿历史某天的因子值筛,验证"当时选出来会怎样"
  • 不构成投资建议:因子选股只做"客观条件筛选",选出来不等于该买(本文只讲怎么取数与验证)

二、最快上手:一行请求筛出低估高 ROE

`/v1/screen` 是付费端点(需要 Key)。最常用的写法是 `expr` 多因子交集——`intersect([...])` 表示同时满足括号内所有条件。

Python:筛「PE 在 0~20 之间 + ROE 大于 15%」,按 ROE 降序取前 5
import requests

BASE = "https://api.ashareapi.com/v1"
H = {"Authorization": "Bearer ct-你的Key"}     # Key 见 https://ashareapi.com/pricing

r = requests.get(
    f"{BASE}/screen",
    params={
        "expr": "intersect([PE_TTM > 0, PE_TTM < 20, ROETTM > 15])",
        "orderby": "ROETTM",
        "desc": "1",
        "limit": 5,
    },
    headers=H,
    timeout=30,
)
body = r.json()
if not body.get("ok"):
    raise RuntimeError(body)                  # 上游取数失败会返回 ok=false(不扣次数)

for row in body["structured"]:                # structured = 给代码用的结构化结果
    print(row["code"], row["name"], row["PE_TTM"], row["ROETTM"])
  • `PE_TTM > 0` 是必须的前置条件 —— 亏损公司的 PE 为负,不加会把它们当"低 PE"筛进来
  • 百分数字段直接写数字:`ROETTM > 15` 就是"大于 15%"(不要写 `0.15`)
  • 字段值是字符串(`"14.33"`)—— 要算数先 `float()`,否则 `+` 会拼字符串

三、结果长这样(2026-09-29 实跑)

下面是上面那条请求的真实返回(`limit=5`),`structured` 字段直接就是二维结果:

sz000656
金科股份
0.36
883.0546
1.25
3.31
sz300972
万辰集团
14.33
77.2634
151.22
-2.81
sh600132
重庆啤酒
15.54
73.3501
37.30
-0.80
sh688331
荣昌生物
11.28
70.0198
116.31
0.84
sz001309
德明利
12.32
69.7845
370.69
-0.16

先别急着高兴 —— 第一行那个 `PE_TTM=0.36`、`ROETTM=883%` 就是典型的需要体检的对象。下一节讲怎么查。

四、筛完必做的 3 步数据体检(重要)

因子值是"机器算出来的数字",不等于"公司真实经营状况"。遇到极端值(PE 极低 / ROE 极高 / 增速极大)时,先用同一份财报里的其他字段做交叉验证——三步就能筛掉大部分干扰:

Python:对筛出的股票做体检(查扣非 / 多口径 / 杠杆)
def health_check(code: str) -> None:
    # 对单只股票做因子体检:看多口径 ROE + 扣非 + 杠杆 + 当期净利
    r = requests.get(f"{BASE}/finance", params={"code": code, "limit": 1},
                     headers=H, timeout=30)
    tables = r.json()["data"]            # finance 返回 3 张表(利润表/资产负债表/现金流)
    income, balance = tables[0][0], tables[1][0]

    print(f"{code}")
    print("  ROE 多口径:", income.get("ROE"), "/", income.get("ROETTM"),
          "/", income.get("ROEWeighted"), "/ 扣非", income.get("ROECut"))
    print("  归母净利  当期:", income.get("NPParentCompanyOwners"),
          " TTM:", income.get("NPParentCompanyOwnersTTM"))
    print("  负债率:", balance.get("DebtAssetsRatio"), "%  股东权益:",
          balance.get("TotalShareholderEquity"))

    # 体检规则(示例,非投资建议)
    try:
        if float(income.get("ROECut") or 0) < 0:
            print("  [!] 扣非 ROE 为负 —— 利润可能来自非经常性损益")
        if float(balance.get("DebtAssetsRatio") or 0) > 70:
            print("  [!] 负债率高于 70% —— ROE 可能被杠杆放大")
    except (TypeError, ValueError):
        pass

health_check("sz000656")
  • ① 看扣非(最重要):`ROECut`(扣非 ROE)与 `NPDeductNonRecurringPL`(扣非归母净利)。若 `ROETTM` 很高但 `ROECut` 为负 → 利润主要来自非经常性损益(卖资产/债务重组等),不代表主营赚钱
  • ② 看多口径是否一致:同一份财报有 `ROE` / `ROETTM` / `ROEWeighted` / `ROECut` 多个口径。若它们量级差很多(如 0.44 vs 883)→ 说明其中一个口径失真,别直接采用
  • ③ 看杠杆与当期:`DebtEquityRatio` / `DebtAssetsRatio`(高杠杆会放大 ROE)+ `NPParentCompanyOwners`(当期归母净利)。若 TTM 净利与当期净利严重脱节,TTM 值可疑

五、真实案例:那个 883% 的 ROE 是怎么回事

上面筛出的 `sz000656 金科股份`(PE_TTM 0.36 / ROETTM 883%)——跑一遍体检就能看出问题。同一份财报里:

  • 判据不是"我觉得":同一份报表里 `ROE=0.44` 与 `ROETTM=883` 相差 2000 倍、扣非为负、TTM 与当期净利脱节 —— 多口径互相矛盾,说明 `ROETTM` 这个值不可直接采用
  • 这类情况的正确处理:不是"这只股票有问题",而是"这个因子值不可靠" —— 换口径(用 `ROECut`)或直接跳过
  • 这正是为什么体检是必须的:接口如实返回了各口径数值,是否需要剔除由使用者按规则判断(我们不给买卖结论)
ROETTM
883.05
选股用的那个(极高)
ROE
0.44
同一指标另一口径(相差约 2000 倍)
ROECut
-1.71
扣非 ROE(负 = 扣非亏损)
NPParentCompanyOwnersTTM
368.7 亿
归母净利 TTM
NPParentCompanyOwners
0.19 亿
当期归母净利(与 TTM 严重脱节)
DebtEquityRatio
200.67%
产权比率(高杠杆)
ClosePrice
1.25 元
股价(面值附近)

六、常用因子速查(写表达式最常用的那些)

估值
PE_TTM
市盈率 TTM(需 > 0)
倍
估值
PB
市净率(无 PB_TTM)
倍
估值
PS_TTM
市销率 TTM
倍
估值
TotalMV
总市值
元
估值
DividendRatioTTM
股息率 TTM
%
盈利
ROETTM
净资产收益率 TTM
百分数
盈利
ROECut
扣非 ROE(体检用)
百分数
盈利
GrossIncomeRatioTTM
毛利率 TTM
百分数
成长
NPParentCompanyYOY
归母净利同比
%
成长
OperatingRevenueGrowRate
营收增速
%
杠杆
DebtAssetsRatio
资产负债率
%
杠杆
DebtEquityRatio
产权比率
%
现金流
NetOperateCashFlowTTM
经营现金流 TTM
元

完整 85 个因子(估值 6 / 行情 2 / 盈利 38 / 资产负债 32 / 现金流 14)与 14 个组合模板 → 见《因子表达式语法与因子字典》。

常见问题

因子选股的结果能直接用来买吗?

不能。因子筛选只是"按客观条件过滤全市场",选出来的是"满足条件"而不是"值得买"。它替代的是人工翻财报的第一步(初筛),后续仍需自己判断。本文只讲怎么取数与验证,不构成投资建议。

为什么筛出来的 PE 那么低、ROE 那么高,看着像捡钱?

极端值往往来自口径问题。用同一份财报里 `ROECut`(扣非)、`ROE`/`ROEWeighted`(其他口径)、`DebtEquityRatio`(杠杆)交叉验证——多口径互相矛盾时,该因子值不可采用。第 4、5 节给了完整方法与真实案例。

免费能用因子选股吗?

不能,`/v1/screen` 属付费端点(需 Key)。免费的 5 个端点是 quote / kline / hot / market-overview / changedist——可以先拿免费端点验证"怎么调通",再决定是否用选股。

字段为什么是字符串?

接口为了让精度不丢失,数值型字段以字符串返回(如 "14.33")。比较、排序、计算前请先 float()——直接相加会变成字符串拼接。

最后更新: 2026-09-29

字段与方案清单取自 ashareapi /v1/screen 与 /v1/finance 的线上真实返回(2026-09-29 实测);因子释义与体检方法来自实测验证记录(85 因子,2026-08-31)。