> Source: https://ashareapi.com/docs/guides/value-stock-screening/  ·  Markdown version for LLMs / AI agents

教程

# 实战：价值投资筛选与异常值排查
 前面三篇讲了语法、因子、现成方案。这篇是完整实战：写一个四条件的价值筛选 → 跑出结果 → 对筛出来的第一名做完整排查，展示"看着像捡钱的值"是怎么一步步被验证为"不可用"的。

## 一、先定规则：价值筛选的四个维度
 价值投资不是"PE 越低越好"，而是**同时满足多个条件**才算"便宜且不差"。我用的四维规则（每个维度各选一个代表因子）：

 Python：四条件价值筛选
```
import requests

BASE = "https://api.ashareapi.com/v1"
H = {"Authorization": "Bearer ct-你的Key"}

EXPR = ("intersect([PE_TTM > 0, PE_TTM < 20, ROETTM > 15, "
 "DebtAssetsRatio < 60, NetOperateCashFlowTTM > 0])")

r = requests.get(f"{BASE}/screen",
 params={"expr": EXPR, "orderby": "ROETTM", "desc": "1", "limit": 10},
 headers=H, timeout=40)
body = r.json()
assert body["ok"], body

rows = body["structured"]
print("命中 %d 只（最多返回 limit 条）" % len(rows))
for i, row in enumerate(rows, 1):
 print("%2d. %s %-8s PE=%-7s ROE=%-10s 负债=%-8s"
 % (i, row["code"], row["name"], row["PE_TTM"],
 row["ROETTM"], row.get("DebtAssetsRatio", "-")))
```
 |
| | 维度 | 因子 | 条件 | 为什么

| | 估值 | PE_TTM | 0） | 便宜；> 0 排除亏损

| | 盈利 | ROETTM | > 15 | 赚钱效率高

| | 杠杆 | DebtAssetsRatio | | 排除高杠杆撑起来的 ROE

| | 现金流 | NetOperateCashFlowTTM | > 0 | 利润有现金支撑

 估值
 PE_TTM
 0）
 便宜；> 0 排除亏损

 盈利
 ROETTM
 > 15
 赚钱效率高

 杠杆
 DebtAssetsRatio
 排除高杠杆撑起来的 ROE

 现金流
 NetOperateCashFlowTTM
 > 0
 利润有现金支撑

## 二、结果（2026-09-29 实跑）
 跑出来会看到类似的排序（ROE 降序，所以极端值必然排在最前）：
 |
| | # | code | name | PE_TTM | ROETTM

| | 1 | sz000656 | 金科股份 | 0.36 | 883.0546

| | 2 | sz300972 | 万辰集团 | 14.33 | 77.2634

| | 3 | sh600132 | 重庆啤酒 | 15.54 | 73.3501

| | 4 | sh688331 | 荣昌生物 | 11.28 | 70.0198

 1
 sz000656
 金科股份
 0.36
 883.0546

 2
 sz300972
 万辰集团
 14.33
 77.2634

 3
 sh600132
 重庆啤酒
 15.54
 73.3501

 4
 sh688331
 荣昌生物
 11.28
 70.0198

 第 2~4 名看起来都在合理范围（PE 11~16 倍、ROE 70%+）。**但第 1 名 PE=0.36 / ROE=883% 明显是另一个量级。**下面完整排查它。

## 三、排查流程（四步定位根因）
 排查的原则是：**不看单项数字，看同一份财报里其他字段是否与它互相印证**。四步走：

 Python：四步排查（一次拿全部证据）
```
CODE = "sz000656"

r = requests.get(f"{BASE}/finance", params={"code": CODE, "limit": 1}, headers=H, timeout=40)
tables = r.json()["data"] # [利润表, 资产负债表, 现金流]
income, balance = tables[0][0], tables[1][0]

print("=== 第二步：ROE 多口径对比 ===")
for k in ("ROE", "ROETTM", "ROEWeighted", "ROECut"):
 print(" %-14s %s" % (k, income.get(k)))

print("=== 第三步：当期 vs TTM ===")
print(" 当期归母净利 ", income.get("NPParentCompanyOwners"))
print(" 归母净利 TTM ", income.get("NPParentCompanyOwnersTTM"))

print("=== 第四步：交叉验证 ===")
print(" 扣非归母净利 ", income.get("NPDeductNonRecurringPL"))
print(" 产权比率 ", balance.get("DebtEquityRatio"))
print(" 股东权益 ", balance.get("TotalShareholderEquity"))
print(" 总负债 ", balance.get("TotalLiability"))
```

-
 **第一步：拉原始财务报表** —— 不看筛选结果，直接查 `/v1/finance`，拿到利润表 + 资产负债表全部字段

-
 **第二步：多口径对比** —— 同一指标往往有多个口径（`ROE` / `ROETTM` / `ROEWeighted` / `ROECut`），比对是否量级一致

-
 **第三步：当期 vs TTM** —— 对比 `NPParentCompanyOwners`（当期）与 `NPParentCompanyOwnersTTM`（滚动），看是否脱节

-
 **第四步：交叉验证** —— 用扣非（`ROECut`）、杠杆（`DebtEquityRatio`）、股价做独立印证

## 四、排查结果：四步全部指向"该值不可用"

-
 `ROE`(0.44) / `ROEWeighted`(0.45) / `ROECut`(-1.71) **三个口径互相一致**（都在"几乎不赚钱到亏损"区间）

-
 只有 `ROETTM`(883) **单独站在另一边** —— 少数派且极端的那个，通常是**算错或含特殊项**的那个

-
 `NPParentCompanyOwnersTTM`(368.7 亿) 与当期(0.19 亿) 相差 1900 倍 → **TTM 被某个一次性事项污染**

-
 **结论**：`ROETTM` 不可用（若一定要用 ROE，应改用 `ROECut` 或 `ROEWeighted`）

 |
| | 步骤 | 字段 | 值 | 结论

| | 第二步 | ROETTM | 883.05 | 筛选使用的值

| | 第二步 | ROE | 0.44 | 同指标另一口径 —— 相差 2000 倍

| | 第二步 | ROEWeighted | 0.45 | 加权口径 —— 与 ROE 一致，与 ROETTM 矛盾

| | 第二步 | ROECut | -1.71 | 扣非为负 —— 主营不赚钱

| | 第三步 | NPParentCompanyOwners（当期） | 0.19 亿 | 正常量级

| | 第三步 | NPParentCompanyOwnersTTM | 368.7 亿 | 与当期相差 1900 倍 —— 明显异常

| | 第四步 | NPDeductNonRecurringPL | -0.71 亿 | 扣非亏损，印证 ROECut

| | 第四步 | DebtEquityRatio | 200.67% | 高杠杆

| | 第四步 | ClosePrice | 1.25 元 | 面值附近（市场也不认）

 第二步
 ROETTM
 883.05
 筛选使用的值

 第二步
 ROE
 0.44
 同指标另一口径 —— 相差 2000 倍

 第二步
 ROEWeighted
 0.45
 加权口径 —— 与 ROE 一致，与 ROETTM 矛盾

 第二步
 ROECut
 -1.71
 扣非为负 —— 主营不赚钱

 第三步
 NPParentCompanyOwners（当期）
 0.19 亿
 正常量级

 第三步
 NPParentCompanyOwnersTTM
 368.7 亿
 与当期相差 1900 倍 —— 明显异常

 第四步
 NPDeductNonRecurringPL
 -0.71 亿
 扣非亏损，印证 ROECut

 第四步
 DebtEquityRatio
 200.67%
 高杠杆

 第四步
 ClosePrice
 1.25 元
 面值附近（市场也不认）

 **关键推理链**：

## 五、把排查做成可复用的函数
 一次排查不如固化成规则。下面这个函数把上面的判断逻辑写成代码，可以对**任何**筛选结果批量跑：

 Python：可复用的体检函数（返回风险标记）
```
def check_factor_health(code: str) -> list:
 # 返回风险提示列表；空列表 = 未发现明显异常
 r = requests.get(f"{BASE}/finance", params={"code": code, "limit": 1},
 headers=H, timeout=40)
 tables = r.json()["data"]
 income, balance = tables[0][0], tables[1][0]
 flags = []

 def f(d, k):
 try:
 return float(d.get(k) or 0)
 except (TypeError, ValueError):
 return 0.0

 # ① 扣非为负 -> 利润可能来自非经常性损益
 if f(income, "ROECut") < 0:
 flags.append("扣非 ROE 为负（利润或含非经常性损益）")

 # ② 多口径 ROE 量级不一致 -> 其中某个口径失真
 roe, roettm = f(income, "ROE"), f(income, "ROETTM")
 if roettm and roe and (abs(roettm) > abs(roe) * 10 or abs(roe) > abs(roettm) * 10):
 flags.append("ROE(%.2f) 与 ROETTM(%.2f) 量级不一致" % (roe, roettm))

 # ③ TTM 与当期净利脱节
 cur, ttm = f(income, "NPParentCompanyOwners"), f(income, "NPParentCompanyOwnersTTM")
 if cur > 0 and ttm and (ttm > cur * 10 or cur > ttm * 10):
 flags.append("当期净利(%.2f) 与 TTM(%.2f) 脱节" % (cur, ttm))

 # ④ 高杠杆
 if f(balance, "DebtAssetsRatio") > 70:
 flags.append("资产负债率 %.1f%%（偏高）" % f(balance, "DebtAssetsRatio"))

 return flags


for row in body["structured"][:10]:
 flags = check_factor_health(row["code"])
 mark = " ".join(flags) if flags else "OK"
 print("%s %-8s %s" % (row["code"], row["name"], mark))
```

## 六、实测输出（同一批筛选结果）

 输出
```
sz000656 金科股份 ROE(0.44) 与 ROETTM(883.05) 量级不一致 | 扣非 ROE 为负（利润或含非经常性损益） | 当期净利(0.19) 与 TTM(368.66) 脱节 | 资产负债率 200.7%（偏高）
sz300972 万辰集团 OK
sh600132 重庆啤酒 OK
sh688331 荣昌生物 OK
sz001309 德明利 OK
```
 **四步规则精准命中了那个异常值**，且没有误伤其余标的 —— 这就是"把排查固化成规则"的价值：**筛一次，批量体检，只看有标记的**。

## 七、使用注意（四条）

-
 **体检规则是示例，不是标准答案** —— 阈值（如负债率 70%）应按自己的风险偏好调整；我们只提供方法，不提供投资建议

-
 **"有标记"≠"不能买"** —— 标记的含义是"这个因子值需要人工再看一眼"，不是结论

-
 **体检不覆盖所有风险** —— 它只能发现"数据层面的矛盾"，发现不了行业风险、政策风险、治理问题

-
 **同一套规则要定期回看** —— 随着使用会发现新类型异常，持续补充规则（这也是它值得固化为代码的原因）

## 常见问题
 为什么筛出来第一名的 PE / ROE 那么极端？
 按单因子排序时，极端值天然排最前。可能是真机会，也可能是口径问题——本文演示了完整的四步排查流程来判断属于哪种。

 体检规则里的阈值怎么定？
 本文的 70% 负债率 / 10 倍量级差都是示例。建议按自己的风险偏好调整，并在使用中记录"哪些标记后来被验证是真问题"，逐步校准。

 这套方法能保证筛出好股票吗？
 不能。它只做两件事：把全市场缩到几十只（筛选）、把有数据矛盾的挑出来（体检）。剩余判断（行业、竞争、管理层、估值合理性）不在数据层面，需要你自己完成。

 要跑这些例子需要什么？
 一个 Key（/v1/screen 与 /v1/finance 都是付费端点）。文档里的表格与代码均为线上真实返回复现，可直接照跑。

 最后更新: 2026-09-29
 所有表格数字与代码输出均来自 ashareapi /v1/screen 与 /v1/finance 的线上真实返回（2026-09-29 实测复现）；体检规则设计参考实测验证记录（2026-08-31）。

 相关

-
[因子选股入门（含数据体检）](/docs/guides/factor-screening)

-
[因子表达式语法与因子字典](/docs/guides/screen-expressions)

-
[22 个现成的选股方案（低估值/高股息/高ROE）](/docs/guides/stock-screening-strategies)

-
[端点参考（screen）](/docs/endpoints/screen)

 [← 全部教程](/docs/guides)
