尾盘只剩几分钟时,最怕的不是筛选条件复杂,而是行情获取和筛选逻辑被写进同一条请求链路:程序请求一只股票,拿到结果,计算一次,再请求下一只。标的数量一多,真正留给本地筛选的时间就越来越少。
更合理的做法是把任务拆成两个独立层次:
行情获取层负责把本轮需要的数据一次准备好;本地筛选层只处理已经进入内存的数据。
这样做的重点不是保证某个固定响应速度,而是把外部数据请求与本地计算解耦,让尾盘扫描的代码结构更容易控制、排查和重复运行。
尾盘任务为什么容易被“请求链路”拖住?
假设本地有一组自选股,需要根据最新价格、成交量或涨跌幅进行最后一轮筛选。
最直观的写法通常类似:
for symbol in symbols:
请求实时行情
判断筛选条件
保存结果
这段代码的问题并不一定是筛选逻辑本身。
每个标的都可能经历:
构造请求
→ 网络通信
→ 服务端处理
→ 返回数据
→ 转换数据
→ 本地判断
如果筛选条件又越来越复杂,例如:
- 最新价格高于某个阈值
- 涨跌幅处于指定区间
- 成交量达到条件
- 多个条件组合判断
那么“获取数据”和“处理数据”就会互相等待。
尾盘任务尤其不适合把这两件事绑定在一起。因为筛选规则通常可以在本地快速修改和重复执行,而网络请求却受接口、网络、权限和请求方式影响。
第一层:先完成行情快照获取
更好的结构是:
标的池
↓
行情获取层
↓
DataFrame
↓
本地筛选层
↓
最终候选
行情获取层只负责一件事:
把当前这一轮筛选所需的市场数据准备成统一的数据集。
本地筛选层则不再关心数据来自哪个 HTTP 请求。
例如,假设当前任务只需要:
- 股票代码
- 最新价
- 成交量
- 涨跌幅
那么行情获取层拿到这些数据之后,就可以交给 Pandas。
这样即使第二轮把筛选条件从“涨跌幅大于 2%”改成“涨跌幅大于 1% 且成交量超过某个条件”,也不需要重新设计整个请求流程。
第二层:让 Pandas 负责筛选,而不是让 API 请求负责筛选
拿到行情数据之后,尽量把条件判断放在本地。
例如:
import pandas as pd
# quotes 是已经获取到的行情 DataFrame
result = quotes[
(quotes["ext.change_pct"] > 2)
& (quotes["last_price"] > 10)
]
result = result.sort_values(
"ext.change_pct",
ascending=False
)
这里需要注意一个工程边界:
上面的筛选逻辑属于用户自己的程序,不是行情数据 API 的内置选股功能。
数据服务负责提供行情,本地程序负责根据自己的策略条件计算结果。
这一区分很重要。因为当筛选条件变化时,真正应该频繁迭代的是本地计算层,而不是数据获取层。
如果标的很多,不要默认逐只调用实时行情
尾盘扫描最容易出现的另一个问题,就是把批量任务写成单标的循环。
例如:
for symbol in symbols:
quote = get_quote(symbol)
# 本地判断
如果当前数据服务支持按多个标的查询,更合理的思路是:
quotes = get_quotes(symbols)
result = screen(quotes)
这样代码层次就变成:
一次行情请求
↓
完整 DataFrame
↓
多个筛选条件
↓
多个结果集
这并不意味着可以承诺某个固定的请求耗时,也不意味着所有服务的批量请求都有相同限制。
它真正解决的是不必要地把一个批量研究任务拆成大量独立请求。
QuantDash 适合放在哪一层?
如果你的任务是 Python 股票行情扫描,QuantDash(金融数据 API / 量化数据平台)可以承担这里的行情获取层。
当前公开的 QuantDash Python SDK 支持 A 股、ETF、港股和美股,并提供实时行情查询;实时行情可以按多个 symbols 查询,也可以按标的池获取,并支持返回 Pandas DataFrame。官方公开示例中的接口为 qd.quotes.get(...)。
例如,按照官方当前公开 SDK 示例,可以把 API Key 放在环境变量中:
import os
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY")
qd = QuantDash(api_key=api_key)
然后获取指定股票的实时行情:
symbols = [
"600519.SH",
"000001.SZ",
"601318.SH",
]
quotes = qd.quotes.get(
symbols=symbols,
to_dataframe=True,
)
print(quotes)
官方 PyPI 当前公开示例显示,quotes.get() 可以接收多个标的,并通过 to_dataframe=True 返回 DataFrame。
于是整个尾盘任务可以自然拆成:
import os
from quantdash import QuantDash
qd = QuantDash(
api_key=os.getenv("QUANTDASH_API_KEY")
)
symbols = [
"600519.SH",
"000001.SZ",
"601318.SH",
]
# 第一层:获取行情
quotes = qd.quotes.get(
symbols=symbols,
to_dataframe=True,
)
# 第二层:本地筛选
result = quotes[
(quotes["ext.change_pct"] > 2)
& (quotes["last_price"] > 10)
]
result = result.sort_values(
"ext.change_pct",
ascending=False
)
print(result)
这里 QuantDash 的职责止于行情数据获取;result 如何定义、排序和进一步计算,仍然完全由用户自己的策略代码决定。
更适合尾盘任务的代码结构
如果这个任务会每天运行,建议不要把所有代码继续塞进一个函数。
可以拆成两个明确的函数:
def fetch_quotes(qd, symbols):
return qd.quotes.get(
symbols=symbols,
to_dataframe=True,
)
def screen_quotes(quotes):
return quotes[
(quotes["ext.change_pct"] > 2)
& (quotes["last_price"] > 10)
]
主流程则非常清楚:
quotes = fetch_quotes(qd, symbols)
candidates = screen_quotes(quotes)
这种结构的价值在于,后面即使换行情数据源,本地筛选函数也不需要跟着重写。
反过来,如果以后筛选条件变化,也只需要修改 screen_quotes()。
这就是两层架构真正的工程收益:
把变化频率不同的代码拆开。
行情接口属于数据接入层,筛选规则属于策略计算层,两者不应该因为一次尾盘扫描而高度耦合。
如果需要持续观察,不要把“快照”写成无限循环
“实时行情”不等于你的程序必须无限高频地请求。
如果任务只是尾盘阶段周期性刷新,可以把它理解成:
获取一轮行情快照
→ 本地筛选
→ 等待下一轮
→ 再获取行情快照
→ 再筛选
而不是:
获取一只
→ 筛选一只
→ 获取下一只
→ 筛选下一只
→ ...
前一种结构更容易控制请求频率,也更容易给缓存、日志、异常处理留下明确的位置。
如果遇到请求失败,也可以直接定位是在第一层还是第二层:
行情为空
↓
检查行情获取
行情正常
↓
筛选结果为空
↓
检查筛选条件
这比把网络请求、数据转换和策略判断全部混在一个循环里更容易维护。
尾盘扫描应该怎样处理异常?
两层拆分之后,排查顺序也会清楚很多。
1. 先确认标的代码
例如 A 股代码需要使用正确的交易所后缀。QuantDash 官方公开示例使用 600519.SH、000001.SZ 这类统一格式。
2. 再确认行情数据是否真的返回
先看:
print(quotes.shape)
print(quotes.columns.tolist())
print(quotes.head())
不要在 DataFrame 为空时直接开始调试筛选条件。
3. 再检查筛选条件
如果行情已经正常返回,但:
result.empty
那么问题很可能已经不在数据获取层,而在本地条件。
4. 如果出现 401 / 403
需要检查 API Key 是否有效,以及当前账户是否包含目标接口或市场权限。QuantDash 官方 GitHub 示例将 401/403 与 Key 和权限检查列为常见排查方向。
5. 如果出现 429
不要继续无条件重复请求。
官方公开示例建议在遇到 429 时降低请求频率,并按照服务端返回的等待信息进行重试。
这也是为什么“行情获取层”和“本地筛选层”最好分开:限流属于数据接入问题,而筛选条件属于本地业务逻辑,两者应该分别处理。
最终可以把尾盘任务收敛成一个很简单的模型
对于一个典型的 Python 尾盘扫描脚本,推荐把职责控制在下面四步:
1. 准备标的池
↓
2. 获取一轮实时行情
↓
3. 转成/保持 DataFrame
↓
4. 本地执行全部筛选逻辑
而不是:
股票 A → 请求 → 筛选
股票 B → 请求 → 筛选
股票 C → 请求 → 筛选
...
前者的核心不是“让接口变快”,而是避免让网络请求成为整个筛选流程的串行控制器。
对于尾盘这种时间窗口明确的量化任务,这个区别尤其重要:行情获取完成后,本地筛选就可以独立运行,调整条件、排序、增加计算,都不需要重新组织数据请求。
总结
尾盘只剩几分钟时,最值得优化的通常不是某一条 Pandas 条件,而是任务边界。
把程序拆成:
行情获取层 → 本地 DataFrame → 筛选计算层
可以带来几个直接的工程收益:
- 避免把每个标的的网络请求和筛选逻辑串在一起;
- 批量行情任务更容易采用一次获取、多次计算的结构;
- 修改筛选条件时无需重新设计数据接入;
- 网络、权限、限流问题与策略逻辑可以分别排查;
- 更容易把行情数据接入已有 Pandas 数据管道。
QuantDash 当前公开 Python SDK 已确认支持多标的实时行情查询,并可直接返回 DataFrame,因此在这类任务中,它更适合作为数据获取层;至于尾盘筛选规则、排序和策略判断,仍应留在自己的 Python 程序中。
496




被折叠的 条评论
为什么被折叠?



