跳到正文

研究指南

股票筛选与候选名单

自然语言选股:把一句要求变成可执行筛选条件

自然语言选股需要把一句话变成可以逐项复核的条件,再对有明确日期的股票池执行。完整交付包括通过的股票、已知不通过的股票,以及暂时无法判断的股票。只有一段通顺的选股理由,不能证明用户提出的条件真的被执行。先保留原话,再确认含义,最后检查候选集合,才能把语言转换与数据计算连接起来。

本文使用虚构股票和人工确认的规则表。示例没有实现通用自然语言解析器,没有连接行情接口,也不证明CoTrading已有对应页面。A股、港股和美股可以参考这套方法,但必须分别核实证券标识、字段覆盖、单位及期间,不能把一个市场的字段名直接复制到其他市场。

先保留原话,再决定数字含义

假设要求是:“找估值合理、正在成长的公司,市盈率低于20,收入增长至少10%,管理层优秀。”原话应原样保存在任务记录里。“低于20”给出了明确边界,但市盈率可能是静态、滚动或前瞻口径;“成长”需要明确指标与期间;“管理层优秀”则没有双方认可的可执行定义。不能因为接口恰好提供某一列,就把它自动当成用户的意思。

本例经过人工确认,使用滚动十二个月市盈率和可比年度营业收入同比增速。股票池只有三只虚构普通股,使用同一个筛选日期。增速按比例存储,因此10%写成0.10。“管理层优秀”保留为未解决的定性要求,交给后续材料阅读。由此得到的数值筛选只是原始要求的一部分,不能对外描述成已经筛出了优秀管理层。

原话确认字段运算存储阈值尚需说明
市盈率低于20pe_ttm严格小于20扩展股票池前处理非正市盈率
收入增长至少10%revenue_growth大于等于0.10已确认可比年度
管理层优秀无不执行无单独安排定性复核

亏损公司的负市盈率是否算“便宜”,不能靠数学上的小于关系决定。小样例只放入正市盈率。将来扩展到非正或不存在的市盈率,应补充资格规则和对应测试,不能一边扩大股票池,一边悄悄改变投资问题。这属于含义未确认,与已经确认含义但缺少观测值,是两种不同故障。

给字段建立带日期的数据约定

每个字段至少保留定义、单位、观察日期、可获得日期和来源位置。市盈率要说明股价是收盘价还是其他时点,盈利分母覆盖哪些月份。收入增速要能追溯到两个可比期间,说明是否发生币种转换、并表范围变化或供应商自行计算。列名相同只能说明名称相似,不能证明数值可以直接比较。

SEC的EDGAR接口说明展示了披露事实的单位和期间上下文为何需要一并保留。它可以支持这种数据整理思路,但不是全球选股器,也不能当作A股财务数据源。不同市场应从相应披露或合法数据渠道取得上下文,不能把当前价格随意除以一条能找到的旧盈利。

把股票池名单与输入表一起冻结。证券使用带交易所限定的标识,避免短代码重名;任务保存规则版本、数据截止日和原始输入引用。供应商以后修订同一条财务数据,重复执行时才有机会解释为什么结果改变。没有这些记录,所谓“同一条件重跑”可能已经换了股票池、分母或数据版本。

用三只股票执行已确认规则

AAA的市盈率为18、收入增速为0.12;BBB为24和0.30;CCC的市盈率为16,但收入增速缺失。可执行表达式为(pe_ttm < 20) AND (revenue_growth >= 0.10)。本例采用三态逻辑:只要有已观察到的不满足条件就判不通过;没有不满足条件但存在缺值则为未知;两个条件都有值并满足时才通过。

from decimal import Decimal as D

def evaluate(row, active):
    checks = []
    for field, operator, threshold in active:
        if operator not in {"lt", "ge"}:
            raise ValueError("unsupported_operator")
        value = row.get(field)
        checks.append(None if value is None else
                      (D(value) < threshold if operator == "lt"
                       else D(value) >= threshold))
    return "fail" if False in checks else "unknown" if None in checks else "pass"

完整标准库样例位于作者仓库的docs/growth/research/round-5/examples/R517.py,运行命令为python docs/growth/research/round-5/examples/R517.py。实际输出为AAA通过、BBB不通过、CCC未知,同时保留management quality未解决项。附加断言验证市盈率恰好20时不通过;市盈率19、增速恰好0.10时通过。这两个等号边界检查的是用户的措辞,而不只是三个容易判断的普通数据点。

下载完整示例。使用Python 3.12+,仅需标准库,全部为合成数据;下载后运行python R517.py。

Python Decimal官方文档支持这里显式使用十进制数的做法。十进制运算不会替用户选择正确的财务口径。示例只支持已声明的两个运算符,其他名称抛出unsupported_operator。脚本注入错拼的gte并断言执行被阻断,避免未知规则被当成可接受比较。

把候选表做成可以复核的研究交接

AAA进入候选表,因为两个数字条件均被样例支持。BBB虽然增长更快,仍被市盈率条件挡住。CCC进入补数队列,明确阻挡字段是revenue_growth。不要把缺失替换成零:零代表测量到了没有增长,而当前输入并没有给出增长事实,两者含义不同。

小股票池可以在结果表直接保留观测值、每项条件真假和最终状态。大股票池可以把明细独立保存,但候选表必须保留能找到明细的运行编号。核对通过、不通过、未知三组数量之和是否等于输入数量;本例三组各一只,合计三只,没有股票在执行中消失。排序与研究名额是下一步工作,不能倒过来影响是否满足硬条件。

审阅时先检查翻译是否符合原意,再讨论股票数量够不够。把“至少”改成“严格大于”,边界样例应产生预期差别。如果用户原本需要前瞻市盈率,就新建规则版本,并取得对应预测数据。只改字段显示名称而不换盈利分母,会掩盖真正的定义变化。

区分补数据与重新确认需求

本题的不同机制反例是“管理层优秀”。它存在于原始要求中,却没有进入可执行字段约定;即使市盈率和增速全部补齐,这个问题仍不会解决。可以将其加入定性复核,或与用户确认可测量代理指标,或明确把交付范围缩小为数值子集。高净资产收益率不能自动替代管理层质量,除非双方接受这个替代。

CCC缺增速则不同:定义已经确认,只欠一条合适观测。找到对应披露后,可以只重算CCC,并保留前后状态变化。这种分流能避免把数据取得任务误写成需求已被满足,也能防止模型凭常识填上一个“合理”的增长率。

正式扩展前,再覆盖过期数据、字段不支持、证券标识模糊及盈利分母不适用等情况。最终报告即使有入选股票,也应保留未执行要求。定义变了就更新规则版本,观测变了就更新输入版本;两者分开记录,才能让筛选结果成为有边界、可追溯的研究起点,而不是一张无法解释的推荐名单。

把条件审阅设为具体检查点

首次扩大运行前,导出已确认条件表,为每条条件分配简短编号,并把原话放在译出的条件旁。审阅者分别核对字段、运算符、阈值、单位、期间和缺值政策,不能只看阈值数字。数字正确但期间选错,仍然是错条件。保存条件表确认记录和规则版本,后续结果才有可以引用的共同约定。

接着从一条结果反向追查:候选状态对应哪些条件真假,条件使用哪些标准值,标准值从什么原始值转换,原始值位于哪个来源位置。对不通过和未知各做一次同样追查。若任一连接缺失,先修交接再扩大股票池。三只股票恰好覆盖三种路径,因此可以逐条检查,不依赖统计推断或抽样运气。

后续编辑还要区分语言澄清与数据更新。把“增长”从收入改为利润,需要新条件定义;补齐CCC收入观测,只更新对应输入。两种动作若沿用同一含义不明的版本记录,保存筛选的前后比较就无法解释。为每次改变记录对象、原因和影响条件,比只写一句“已优化规则”更便于复核。

条件表交接后,还要约定谁负责澄清语义、谁负责补数据。前者回答管理层质量如何定义,后者回答CCC指定期间收入在哪里取得。把两种责任分清,可以防止取数人员替用户做研究偏好决定,也避免语言审阅者以为数值尚未到手就代表条件本身不成立。

常见问题

这段脚本能理解任意自然语言吗?

不能。字段表已经由人确认,脚本只执行声明的数值规则。

未知股票等于不合格股票吗?

不等于。未知表示目前无法把已确认条件全部算完,应保留阻挡字段并安排补数。

相关阅读