文档1.0 (latest)
页面选股器 · 22 / 29

筛选器(Screener)

筛选器根据一万多只美国股票的财务报表对其进行筛选,并将匹配结果显示在可排序的表格中。你可以用无代码的筛选行构建筛选,也可以为筛选行无法表达的逻辑编写 Python Screener Script。保存的筛选还可以为批量前向运行或回测挑选标的。

筛选仅基于基本面数据。没有实时价格或行业板块数据,因此结果表默认没有 Market Cap / Last / Net Change 列——SIC industry 描述代替了行业板块。市值、企业价值和期末股价可作为 Valuation 组中的字段使用,因此你可以据此筛选,或将其添加为列;它们反映的是每个报告期末的数值,而不是今天的报价。加密货币被排除在外:它们没有财务报表。

你必须已登录——基本面数据从 QuantCraft 服务器下载。


在界面中的位置

位置如何到达
Screener 页面导航栏 → Screener(紧跟在 Trade 之后)
Screener Script 文件QuantCraft IDE → New File → 类型 Screener Script
批量前向目标AutoTrading 或 QuantCloud → 新建批量运行 → 目标 Screener
回测标的QuantCraft IDE → Run backtest → Symbol source → Screener
智能体智能体的 Test 对话框(Scheduler → Symbol source → Screener)和 New Agent Bulk Run 的目标
独立窗口中的 ScreenerScreener 页面 → 页面标题旁的 Pop out 按钮

页面有两张卡片:Filters(带有 No-Code 和 Python Script 标签页)和 Results。Run 位于筛选页脚中的 Reset filters 旁边。Filters 卡片上的箭头(Collapse filters)可将其折叠,给结果留出更多空间。

标题旁的 Pop out 按钮会在单独的窗口中打开 Screener,这样你在 IDE 或图表中工作时,可以把一个筛选放在第二块显示器上。该窗口是同一个页面——预设和 Screener Script 与主窗口共享——但每个窗口保留各自的筛选条件和结果,并且同一时间只有一个窗口运行筛选。使用 ← Return to main window 关闭;只要弹出窗口仍然打开,应用就不会退出。

当 QuantCraft AI 正在驱动 Screener 窗口时,你会看到 “The AI is using this window”,且控件会被锁定,直到它完成。


下载基本面数据

你第一次使用 Screener 时,它会把整个基本面数据集下载到你的机器上。在该缓存有数据之前,Filters 和 Results 卡片会被一个带有 Download Fundamentals 按钮的门控界面取代。

下载在应用打开时开始,而不是在你打开 Screener 时——等你到达该页面时,它通常已在运行或已完成。在下载期间点击 Run 绝不会启动第二次下载。

  • 首次冷启动运行需要几分钟。之后的每次运行只需几秒钟。
  • 运行横幅显示阶段(connecting → universe → download → convert → screen)、类似 shard 14 of 32 的分片计数器、已扫描/总数,以及一个 Cancel 按钮。热运行只显示筛选阶段。
  • 横幅向上计时已用时间,且不显示预计时间——一次耗时波动达数分钟的运行,无法诚实地预测。完成时你会得到类似 12 matched in 3m 20s 的摘要,后面跟着 · Fundamentals updated <time>。如果某些标的无法获取,可点击的 N unavailable 链接会打开它们的列表。
  • 被中断的下载会在下一次运行时恢复,而不是从头开始。在下载中途取消是安全的:未完成的部分只会在下次重新获取。
  • 即使你离开该页面,下载也会继续。

数据多久刷新一次

何时下载什么
你的第一次筛选整个范围
同一 UTC 日的任何后续筛选无
次日的第一次筛选仅最新申报已超过 62 天的标的

62 天大约是一个季度,因此只有在公司下一份申报文件可能已发布时,才会重新检查该公司。

在运行横幅的右侧,你会看到类似 10,384 symbols cached · 250 MB 的一行,并带有一个 Clear cache 按钮(筛选运行时隐藏,AI 使用该窗口时禁用)。清除会要求确认——Clear cache 或 Keep it——因为它会丢弃一次耗时数分钟的下载。缓存是可再生的数据,卸载应用不会删除它,这就是它在这里可见并可回收的原因。

在更改缓存格式的应用更新之后,下一次筛选会重新下载整个范围一次(添加 Per Share、Valuation 和 Scores 时就发生过)。


构建筛选(No-Code)

每个筛选行从左到右阅读:Period → Field → Filter type → 取值输入。用 + 添加一行,用 − 移除一行。+ 按钮会询问新行是 Fundamentals 行还是 Industry 行。

各行以 AND 组合——标的必须满足每一个已启用的行。(由 AI 创建或从文件导入的预设可以改用 OR——“Any filter can match”;Load Preset 会显示是哪一种。)

默认的起始行(Quarterly / Total Revenue / % Change / 1 period back / min 0%)本身就有效,所以 Run 可以立即使用。运行开始之前,会校验每个已启用的行;如果某一行不完整,你会收到提示,并在该行显示内联错误,且运行不会开始——耗时数分钟的扫描不应以一个无法使用的查询开始。

一个实例:Quarterly / Total Revenue / % Change / periods back 3 / min 1 / max 3 会找出在最近三个季度中营收增长在 1% 到 3% 之间的公司。

筛选类型

除非另有说明,每个筛选都读取最新值。

筛选参数匹配内容
Rangemin、max最新值介于 min 和 max 之间(含端点)。某一侧留空表示无界。
Abovevalue最新值大于 value。
Belowvalue最新值小于 value。
Equalsvalue、tolerance最新值在 value ± tolerance 之内。
Has a value—存在最新值。与 Financials Extended 搭配使用很有用。
% Changeperiods back、min %、max %从 n 期之前到最新的百分比变化。起始值为零时失败。
Absolute Changeperiods back、min、max原始差值(最新 − n 期之前)。
QoQ Change %min %、max %相对于上一季度的百分比变化。仅限季度——期间选择器被锁定。
YoY Change %min %、max %相对于一年前的百分比变化。滞后是自动的——季度为 4,年度为 1。
CAGR %periods back、min %、max %年化增长率。两个端点都必须为正。
Average over Nperiods back、min、max最近 N 期的平均值。窗口内任何缺失值都会使该标的失败。
Grew N in a rowperiods back在窗口内的每一期该值都在上升。
Fell N in a rowperiods back在窗口内的每一期该值都在下降。
Above X for Nvalue、periods back最近 N 期中的每一期都高于 value。
Below X for Nvalue、periods back最近 N 期中的每一期都低于 value。
Top N by fieldn排名筛选——在所有已扫描的标的中保留前 n 名。
Percentile rank ≥min %排名筛选——在所有已扫描的标的中保留处于或高于该百分位的标的。

这两个排名筛选是在整个总体上决定的,而不是针对每个标的,因此要在所有标的扫描完之后才能确定。对于被排名字段没有值的标的,永远不可能入选。

你可以使用的最大 periods back 是 40。

选择字段

字段选择器可搜索,并按以下顺序对字段分组:

Ratios → Valuation → Scores → Per Share → Income Statement → Balance Sheet → Cash Flow → Financials Extended → Earnings History

比率排在最前面,因为它们与筛选最相关,并且已完成单位归一化。取值输入遵循字段的单位——视情况为 $、%、天数、股数、每股金额、计数或纯数字。每个组包含什么,请参见基本面数据字段参考。

  • 所有分组都支持季度和年度,但 Earnings History(仅限季度)和 CAGR 字段(revenueCagr3y、revenueCagr5y 等,仅限年度,并会从季度选择器中消失)除外。更改期间时,如果该字段在新期间不存在,字段会被清除。
  • **“Periods back”统计的是你所选期间内的行数。**季度 + 3 表示三个季度;年度 + 3 表示三个财年。两者不会混用。
  • Valuation 倍数即使在季度行上也是滚动十二个月的口径。
  • **Financials Extended 字段因标的而异。**只有在基本面数据缓存下载完成后它们才会出现在选择器中,并标注为 “varies by symbol”。针对某公司未披露的字段的筛选,会使该公司失败——在此情况重要时,请搭配 Has a value 使用。
  • 日期在未来的期末会被忽略,因此“最新”绝不会是尚未发生的期间。
  • 不可筛选:Company Info、Earnings Trend、Earnings Annual、TTM、最新(截至今日)估值、指标来源、Piotroski / Beneish 评分的组成部分,以及 priceBasis 或 approximate 之类的文本或是/否键。它们要么没有可比较的报告期,要么不是数字。

**已淘汰的字段。**八个旧的 Financials Extended 字段已被撤销,因为现在有了更好的版本:ebitda、ebitdaMargin、enterpriseValue、priceToEarnings、priceToBook、priceToSales、evToEbitda、evToRevenue。使用过其中之一的已保存筛选,在加载时会丢失该行(或列)——请从 Valuation 组重新构建,该组按滚动十二个月计算这些数值,因此数值与旧值不同。(ratios.debtToEquity 只是被重命名为 ratios.debtToEquityRatio,仍然可以加载。)

Industry 行

Industry 行用行业多选取代字段/类型/取值控件;如果标的的行业是你所选的行业之一,则该标的通过。必须至少选择一个行业。该列表由你实际下载的数据构建。


Python Script 模式

当一堆用 AND 连接的比较不够用时——多字段运算、条件逻辑或你自己的排名评分——请选择 Python Script。

在 QuantCraft IDE 中通过 New File → Screener Script 创建文件;它自带可运行的模板。工作区中任何定义了顶层 filter() 的 .py 文件也符合条件。Screener 的 Python Script 标签页会列出它们(以文件夹路径作为标签,使不同文件夹中的文件仍可区分),并预览所选文件。你工作区中的其他文件会随运行一起发送,因此你的脚本可以导入自己的辅助模块。

def filter(fundamentals, symbol): q = fundamentals["incomeStatement"]["quarterly"] if len(q) < 4: return {"result": False} latest, year_ago = q[-1]["totalRevenue"], q[-4]["totalRevenue"] if latest is None or not year_ago: return {"result": False} growth = (latest - year_ago) / abs(year_ago) return {"result": growth > 0.10, "value": growth} def rank(value, symbol): return min(max(value, 0), 1) * 100

filter(fundamentals, symbol) 对每个代码调用一次,必须返回带有布尔型 "result" 的 dict。"value" 是可选的,是你想带入排名的任何内容。抛出异常、返回单独的 True,或返回没有布尔型 "result" 的 dict 的脚本,会被视为仅对该代码不匹配——一个有问题的代码绝不会中止运行。

rank(value, symbol) 是可选的,只对匹配的代码运行。请返回一个实数——任何量纲都可以(0-1、1-10、0-100),并且越高始终表示越好。它填充可排序的 Rank 列,该列仅在 Python Script 模式下出现。失败的 rank() 不会使该行变为不匹配;该行保留其匹配,其排名显示为 —,并排在最后。

symbol 参数在两个函数中都是可选的——较早的单参数 filter(fundamentals) 和 rank(value) 仍然有效。

fundamentals 参数

{ "symbol": "AAPL", "entityName": "Apple Inc.", "sicDescription": "ELECTRONIC COMPUTERS", "balanceSheet": {"quarterly": [...], "yearly": [...]}, "incomeStatement": {"quarterly": [...], "yearly": [...]}, "cashFlow": {"quarterly": [...], "yearly": [...]}, "ratios": {"quarterly": [...], "yearly": [...]}, "perShare": {"quarterly": [...], "yearly": [...]}, "valuation": {"quarterly": [...], "yearly": [...]}, "scores": {"quarterly": [...], "yearly": [...]}, "financialsExtended": {"quarterly": [...], "yearly": [...]}, "earnings": {"history": [...]}, }

任何“截至上传时”的数据都不会传给脚本:ttm、valuation.latest、metricsInputs 和 metricsHealth 不带期间,而筛选是在标的之间比较期间。valuation 倍数即使在季度行上也是滚动十二个月的口径,带符号收益率旁边的 None 倍数表示分母为非正,而不是数据缺失。

  • 值为数字或 None——绝不会是 "1234.00" 这样的字符串。不是数字的键会被完全省略:earnings.history 中的 beforeAfterMarket、reportDate 和 currency,scores 中的 piotroskiComponents / beneishComponents 明细,以及 valuation 中 priceBasis / approximate 之类的文本标志。
  • **行按从旧到新的顺序排列,所以 rows[-1] 是最新期间。**每一行都带有 date(其期末)。
  • 你最多获得最近 12 个季度和最近 6 个年度的行,这限制了脚本能回看的深度。

脚本中 print() 的任何内容都会被捕获,并由 Run 旁边的 Output logs 按钮显示——失败的运行也是如此,这样你可以看到崩溃前刚刚打印了什么。缓冲区容纳 200,000 个字符,超出后丢弃最旧的输出。无代码运行不会清除上一次脚本运行的日志。

如果没有选择脚本,或所选文件没有 filter(),Run 会被阻止并给出提示。


结果

每一行显示星标和图表按钮、Rank(仅限 Python Script 模式)、Symbol、Company、Industry 和 As of,以及每个筛选行一列(显示匹配的数值,No-Code 模式),外加你添加的任何额外字段。

“As of”是该标的所用的最近期末——即你的筛选实际评估所依据的财务期间。它永远不会是未来的期间。

控件作用
列标题对整个表格排序,而不只是当前页。空值在两个方向上始终排在最后。
列选择器添加或重新排列结果列。绝不会重新运行筛选——真正全新的字段只会为屏幕上的行获取该列。
搜索框过滤你已有的行。
Tradable only隐藏不在你的券商账户可交易标的列表中的标的。
星标将标的添加到自选列表。加星标的标的以标签形式显示在表格上方,并在会话之间保留。
标的(点击)打开每个标的的基本面详情对话框,包括 Factor Exposure 标签页。
图表按钮在弹出窗口中打开该标的的 Trade 图表(仅限可交易标的)。

运行结束时,匹配项会一次性全部出现,因此在筛选进行期间表格保持为空——请通过横幅观察进度。

**结果不会保存。**重新加载应用会有意清除表格,因为过时的结果比没有结果更糟。你的筛选条件、列、加星标的标的、活动标签页和所选脚本会被记住。


预设

预设是你的筛选行、它们的连接方式和你的结果列的命名快照。

按钮作用
Save Preset为当前筛选命名并保存。在你相对默认值做出更改之前保持禁用——仅选择列不算数。
Load Preset浏览已保存的预设,显示每个预设的描述、其筛选是以 AND(“All filters must match”)还是 OR(“Any filter can match”)连接,以及其筛选的白话摘要。
Update {name}替换你已加载的预设,而不是创建新的。当你编辑已加载的预设后,Save Preset 会变成这个按钮。
Reset filters清除与已加载预设的关联,按钮恢复为 Save Preset。

在 Load Preset 内你还可以:

  • Delete preset——确认后将其删除。
  • Export preset——将其保存为可分享或备份的 JSON 文件。
  • Import preset——从这样的 JSON 文件加载预设。

用筛选来挑选标的

批量前向运行

在 New Run 对话框的 Targets 标签页中,选择 Screener 范围。运行会交易已保存的预设或 Screener Script 所匹配的标的,并在运行期间每隔 Refresh interval (days) 重新筛选。Include tickers with open positions 会让某个持仓在不再匹配之后仍留在轮换中。Screener 目标仅限股票。启用 Use fundamentals 后,当前筛选中的每个标的都会自动获得其基本面数据,且该集合会跟随每次重新筛选。

在 QuantCloud 上,运行器会保留自己的一份筛选所需基本面数据副本。新运行器上的第一次筛选运行会下载全部内容,这需要一段时间;之后它每天只刷新一次。运行器使用你的登录来下载,并且无法自行续期,因此对于持续多天的运行,一旦登录过期,重新筛选会继续使用它已有的数据。

字段说明
Screener sourceScreener preset 或 Python script。
Preset / Screener script运行哪一个。
Sort column / Direction应用数量上限之前,预设匹配结果如何排序。
Rank direction针对脚本——优先取脚本 rank() 评分的哪一端。
Limit交易多少个标的。0 表示取所有匹配项。
Refresh interval (days)运行期间筛选重新运行的频率。最小值和默认值为 1 天。
Refresh time (ET)每次重新筛选发生的时刻。默认为 16:30(美国市场收盘后半小时)。
Include tickers with open positions即使已持仓的标的不再匹配,也将其加入,这样实盘运行绝不会悄悄丢弃某个持仓。

**为什么默认是 16:30 ET。**采用新的标的范围会重启运行,因此在 11:00 进行重新筛选会中断实盘交易。把它固定在收盘后的时间,意味着刷新发生在没有交易进行时,并且新的标的列表会在下一个交易时段开始之前准备就绪。

  • 该时间始终是市场时间(ET),无论你位于哪个时区。字段下方的帮助文字会显示你本地的对应时间,因此 16:30 ET · 21:30 your time 是同一时刻,而不是两个设置。
  • **随时可以更改。**如果你选择的时间落在 09:30–16:00 ET 的交易时段内,字段下方会出现警告,说明重启将中断交易,但运行仍会开始——选择权在你。
  • 间隔和时间共同起作用。16:30 时的 1 天表示每天收盘后重新筛选;16:30 时的 3 天表示每三天在收盘后重新筛选一次。
  • 将字段留空或输入非 HH:MM 的内容,会以内联错误阻止运行。

完整的 New Run 对话框请参见 AutoTrading 和 QuantCloud。

回测

在 Run backtest 对话框的 Symbol & data 标签页中,将 Symbol source 设为 Screener。回测不再交易固定列表,而是随着进行重新运行你的筛选,并交易模拟中该时点所匹配的标的。

设置作用
Screener sourceScreener preset 或 Python script。
Sort column / Direction应用数量上限之前,预设匹配结果如何排序。
Rank direction针对脚本——优先取你的 rank() 评分的哪一端。
Limit每次再平衡交易多少个标的。0 表示取所有匹配项。
Refresh interval (days)筛选重新运行的频率,按模拟的日历日计。脚本要求 7 天或更长。
Keep tickers with an open position标的不再匹配后仍保持可交易,以便继续管理未平仓头寸。

**基本面数据跟随筛选。**启用 Uses fundamentals 后,每个被筛选出的标的都会在 on_bar / on_tick 中自动获得其基本面数据——无需自行挑选。它们从 Screener 的缓存中读取,因此 Screener 已经下载过的标的不会产生额外下载。读取时请始终传入 symbol=symbol——参见基本面数据。

**“Keep tickers with an open position”影响的是交易,而不是估值。**开启时,你仍持有的标的在退出筛选后会继续接收 K 线,因此你的策略可以平掉它。关闭时,该标的不再接收回调,持仓会一直保持未平仓,直到回测结束。无论哪种情况,持仓始终按该标的的真实价格估值,所以你的权益曲线和指标都是正确的。

**on_init 在每次再平衡时都会重新触发。**当筛选器标的来源处于活动状态时,每次筛选重新运行,引擎都会再次调用 on_init(symbols=<ticker list>)。这让你的策略无需重启模拟就能应对新的标的范围。要点:

  • 运行开始时,on_init 会连续触发两次——标准的初始化调用(symbols=None),紧接着是初始的筛选结果。
  • 每次重新触发都会看到上一次调用留下的模块级状态。状态在再平衡之间不会被重置。
  • 声明为 def on_init():(无参数)的策略不受影响——可选参数按名称匹配,因此缺失的 symbols 会被直接忽略。

被筛选范围的日历数据

Include calendar data 适用于筛选器标的来源。日历在筛选确定某次再平衡的完整代码列表之后加载——没有逐 tick 的查询延迟。需要了解的要点:

  • 被筛选匹配但没有价格 K 线的标的(在日期范围之外,或在运行开始前已退市)不会产生日历请求。
  • 范围宽泛的筛选经常会匹配到服务器上没有日历的标的——通常是规模较小或覆盖较少的代码。运行日志会报告其数量,而不会让运行失败。
  • 日历在每次再平衡时刷新,以覆盖新加入的代码。

结果显示在 Logs 旁边的 Screener Output 标签页中,将每次再平衡显示为可折叠的日期。点击 + 展开某个日期,查看该窗口内交易的标的,或使用 Expand all。由于筛选在第一根 K 线之前就已确定,整个计划从一开始就是完整的。

需要了解的要求:由筛选器驱动的回测需要明确的开始和结束日期;不支持附加时间周期(因此 get_bar 返回 None——请使用每个代码自己的 bar.close[i] 偏移读取历史);Debug Test 不能使用筛选器标的来源——请切换到 Selected symbols 来调试。参见回测。

时点(Point-in-time)与幸存者偏差

**每次再平衡只能看到在该日期实际已经提交的报告。**在 2021 年 5 月 5 日的筛选,看不到一个在 4 月结束但直到 6 月才发布的季度。例外是没有记录申报日期的期间,它会从其期末开始被采纳——这种情况很少见,但意味着此处可能存在少量前视偏差。这正是结果诚实的原因,也是回测的选股结果与你用今天的数据筛选同一日期所得结果不一致的原因。

有两件事仍然反映的是今天,而不是模拟日期:

  • 在已连接券商的数据源上,匹配项会被过滤为你的券商此刻可交易的标的,因此,之后退市的公司即使在它完全可交易的某次再平衡中也会被剔除。QuantCraft(B2)数据源会跳过这项检查。
  • 缓存的范围只包含基本面数据集仍然保留的公司,所以很早以前退市的公司根本不会出现。

时点准确性适用于筛选所读取的数值,而不适用于其中存在哪些公司。请在查看长周期结果时牢记这一点。


解读结果

**历史数据不足会使标的失败——它绝不会悄悄通过。**对只有五个季度数据的公司要求 “Grew 8 in a row”,会将其排除。选择排除而不是放行,意味着结果集绝不会过度报告。

标的被排除而非匹配的其他情况:

  • % Change 无法从零起始值计算。
  • CAGR % 需要两个端点都为正。
  • Average over N 在窗口内有任何值缺失时失败——null 不会被跳过。
  • 公司未披露的字段会使该公司失败,这在 Financials Extended 中最为重要。
  • 排名筛选会拒绝在被排名字段上没有值的标的。

排名的一个怪癖:并列的值不共享百分位排名,因此两个相同的值可能落在 Percentile rank ≥ 截止线的两侧。

如果无法连接后端,你会得到明确的错误,而不是缓慢的静默回退;如果一次运行沉默 90 秒,它会被报告为错误,而不是挂起。


另请参阅

  • AutoTrading——批量前向运行,包括 Screener 目标范围
  • QuantCloud——带有 Screener 驱动的批量作业的云运行器
  • 回测——将筛选用作回测的标的来源
  • 基本面数据——在策略回调中读取基本面数据,以及字段参考
  • QuantCraft AI——让 AI 为你构建并运行筛选
  • Fama-French 因子——详情对话框 Factor Exposure 标签页背后的因子数据
阅读约 7 分钟建议修改 ↗
选股器 — 文档 (1.0 (latest)) | QuantCraft