A 股因子选股怎么做:用一个接口筛出低估高 ROE 的股票
因子选股 = 把财务与行情指标写成条件,让接口全市场筛一遍。本文讲清三件事:表达式怎么写、结果怎么读、为什么筛完必须做一次数据体检(否则很容易把异常值当机会)。
一、因子选股是什么(30 秒版)
因子就是一个可计算的指标:`PE_TTM`(市盈率)、`ROETTM`(净资产收益率)、`DividendRatioTTM`(股息率)、`DebtAssetsRatio`(资产负债率)……因子选股就是把这些指标写成条件,让接口从全市场 5000 多只里筛出同时满足的股票。
它替代的是"手动翻财报"——把「低估值 + 高盈利 + 低负债」这类想法,变成一条可复现、可回测的规则。
- 可复现:同一条件任何时候跑,结果口径一致(人工翻财报做不到)
- 可回测:配合 `--date` 可以拿历史某天的因子值筛,验证"当时选出来会怎样"
- 不构成投资建议:因子选股只做"客观条件筛选",选出来不等于该买(本文只讲怎么取数与验证)
二、最快上手:一行请求筛出低估高 ROE
`/v1/screen` 是付费端点(需要 Key)。最常用的写法是 `expr` 多因子交集——`intersect([...])` 表示同时满足括号内所有条件。
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` 字段直接就是二维结果:
| code | name | PE_TTM | ROETTM | ClosePrice | ChangePCT |
|---|---|---|---|---|---|
| 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 极高 / 增速极大)时,先用同一份财报里的其他字段做交叉验证——三步就能筛掉大部分干扰:
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 个组合模板 → 见《因子表达式语法与因子字典》。
常见问题
不能。因子筛选只是"按客观条件过滤全市场",选出来的是"满足条件"而不是"值得买"。它替代的是人工翻财报的第一步(初筛),后续仍需自己判断。本文只讲怎么取数与验证,不构成投资建议。
极端值往往来自口径问题。用同一份财报里 `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)。