一句话结论:实时行情批量筛选的关键不是"拿到行情后逐只判断",而是先一次性获取可处理的数据集,再在 Pandas 中完成条件组合、异常过滤和候选池生成。
摘要
对于量化开发者来说,"获取实时行情"通常只是数据链路的第一步,真正影响研究效率的是拿到行情之后如何快速筛选出符合策略条件的标的。如果逐个股票请求、逐个判断,代码不仅冗长,还会增加网络请求次数和系统维护成本。更合理的方式是把行情获取与本地筛选拆开:数据 API 负责批量提供行情,Pandas 负责条件计算和候选池生成。本文从这一工程思路出发,介绍如何设计批量筛选流程,并结合 QuantDash(专业金融数据 API / 量化数据平台) 的 Python SDK 说明实现方式。
1. 实时行情为什么还需要"批量筛选"
很多量化程序最初会写成这样的逻辑:
text
股票 A → 请求行情 → 判断
股票 B → 请求行情 → 判断
股票 C → 请求行情 → 判断
......
对于几十只股票,这种写法还能接受;但当研究范围扩大到一个市场的股票池时,问题就会变得明显。
第一,网络请求和策略判断被绑在一起了。
第二,筛选条件一旦改变,就可能需要重新设计数据请求逻辑。
第三,后续如果加入多个条件,例如价格、涨跌幅、成交量、技术指标等,代码很容易变成大量嵌套 if。
更适合量化系统的方式是:
text
行情 API
↓
批量获取行情
↓
DataFrame
↓
数据清洗
↓
条件筛选
↓
候选股票池
↓
策略计算
这里有一个重要的工程边界:
数据 API 负责把数据稳定地送到程序,筛选逻辑应该尽量留在策略侧。
这样做的好处是,数据获取和策略逻辑可以独立演进。
2. 为什么逐只请求不是一个好习惯
假设策略每天需要观察大量股票。
如果程序对每个标的分别发起请求,那么:
python
for symbol in symbols:
data = get_quote(symbol)
if condition(data):
candidates.append(symbol)
这种实现虽然容易理解,但会把筛选规模直接绑定到请求规模。
如果股票池扩大,HTTP 请求次数也会同步增加。
而批量获取之后,程序可以变成:
python
quotes = get_all_quotes()
candidates = quotes[
condition_1
& condition_2
& condition_3
]
这时候:
- 数据获取是一件事;
- 数据筛选是一件事;
- 策略逻辑又是一件事。
这种分层对于后续维护更加重要。
3. 批量筛选真正需要解决的三个问题
3.1 数据是否一次性进入内存
如果数据已经形成 Pandas DataFrame,那么筛选通常可以直接利用布尔条件完成。
例如:
python
filtered = df[
(df["field_a"] > threshold_a)
& (df["field_b"] < threshold_b)
]
这里真正值得注意的不是语法,而是数据边界。
field_a、field_b 必须是当前数据源实际返回的字段,不能因为其他行情 API 使用过类似字段名称,就直接照搬。
3.2 筛选条件是否应该全部放在 API 层
不一定。
如果筛选逻辑属于策略本身,例如:
- 某个价格条件;
- 多个行情字段组合;
- 自定义排序;
- 自定义打分;
- 技术指标计算;
通常更容易在 DataFrame 中完成。
这样策略修改时,不需要频繁改变数据获取层。
3.3 筛选结果是不是最终交易信号
也不是。
实时行情筛选得到的更准确说法应该是:
候选池。
例如:
text
全市场行情
↓
基础数据过滤
↓
候选池
↓
技术指标计算
↓
策略条件
↓
交易信号
不要把"符合行情条件"直接等同于"应该交易"。
4. QuantDash 如何放进这条数据链路
如果需要批量获取行情,可以使用 QuantDash 官方 Python SDK。
官方 GitHub 示例给出的基础调用方式是:
python
from quantdash import QuantDash
qd = QuantDash()
quotes = qd.quotes.get(
universes="CN_Stock",
to_dataframe=True,
)
这段代码有两个值得关注的地方。
第一,universes="CN_Stock" 表示请求 A 股股票标的池。
第二,to_dataframe=True 让结果直接以 DataFrame 形式进入 Python 数据处理流程。
官方示例还展示了使用统一标的代码,例如:
text
600519.SH
000001.SZ
这对于后续建立统一的数据处理逻辑比较重要。
需要强调的是,具体行情字段应以当前 QuantDash 官方文档返回结构为准。不要仅凭其他金融数据 API 的字段命名自行假设返回字段。
5. 获取行情后,先检查数据结构再写筛选条件
这是实际开发中比较容易忽略的一步。
不要拿到 DataFrame 后直接开始写几十个条件。
建议先:
python
print(quotes.head())
print(quotes.columns)
print(quotes.shape)
先回答三个问题:
- 当前到底获取了多少条数据?
- 当前数据有哪些字段?
- 字段的数据类型是否适合后续计算?
例如某个字段本应该是数值,但实际数据中包含空值或字符串,那么后面的比较操作就可能产生异常结果。
因此,一个更稳妥的流程是:
text
获取
↓
查看字段
↓
检查数据类型
↓
处理缺失值
↓
定义筛选条件
6. Pandas 中如何组织多个筛选条件
假设当前 DataFrame 已经确认存在策略需要的字段,那么可以采用布尔表达式组合。
通用写法:
python
mask = (
(df["condition_a"] > threshold_a)
& (df["condition_b"] < threshold_b)
)
selected = df.loc[mask]
如果条件继续增加:
python
mask = (
(df["condition_a"] > threshold_a)
& (df["condition_b"] < threshold_b)
& (df["condition_c"] >= threshold_c)
)
selected = df.loc[mask]
这种方式比:
python
for row in df:
if ...:
if ...:
if ...:
...
更适合结构化行情数据。
原因不是单纯"代码更短",而是筛选规则本身更加清晰。
7. 更重要的是:把"筛选"和"排序"分开
很多选股程序容易把这两件事混在一起。
例如:
text
第一阶段:哪些股票符合基本条件?
第二阶段:符合条件的股票中哪些优先?
第一阶段是过滤:
python
selected = df.loc[mask]
第二阶段才是排序:
python
ranked = selected.sort_values(
by="某个已确认字段",
ascending=False
)
这样做的好处是策略逻辑更加容易解释。
最终系统可以形成:
text
全市场
↓
过滤不符合条件的标的
↓
得到候选池
↓
按策略指标排序
↓
取前 N 个
这里的"某个字段"必须替换成实际数据源已经确认存在的字段,而不是直接假定 QuantDash 一定返回某个名称。
8. 实时筛选与历史回测不能混为一谈
实时行情筛选解决的是:
当前市场状态下,哪些标的满足条件?
历史回测解决的是:
过去每一个时间点,如果按照同样规则执行,会发生什么?
两者虽然都使用行情数据,但数据处理方式不同。
如果策略在实时运行时使用当前行情,而回测却直接使用未来时点已经完成的数据,就可能产生 Look-ahead Bias(未来函数偏差)。
因此建议把实时筛选流程明确设计为:
text
当前时刻可获得的数据
↓
数据清洗
↓
计算当时可计算的指标
↓
筛选
↓
信号
而不是:
text
完整历史数据
↓
先算出所有结果
↓
再模拟过去做选择
后者很容易在回测中引入未来信息。
9. 一个更适合生产环境的批量筛选结构
如果只是做个人研究,下面的结构已经够用:
python
from quantdash import QuantDash
qd = QuantDash()
quotes = qd.quotes.get(
universes="CN_Stock",
to_dataframe=True,
)
print(quotes.head())
print(quotes.columns)
# 在确认字段名称之后构造筛选条件
# mask = ...
# candidates = quotes.loc[mask]
如果进入长期运行环境,可以继续向外扩展:
text
行情获取
↓
数据校验
↓
异常/缺失处理
↓
候选池筛选
↓
策略计算
↓
日志记录
↓
结果输出
这里不建议一开始就加入复杂的数据库、消息队列或者分布式计算。
对于一个尚未验证策略逻辑的个人量化项目,过早增加基础设施往往比策略本身更复杂。
10. 哪些场景适合批量行情筛选
场景一:全市场扫描
例如每天或盘中需要从股票池中寻找满足条件的标的。
重点是:
一次获取 → 本地筛选。
场景二:自选池监控
如果只有几十或几百个标的,也可以保持批量数据结构。
这样后续增加条件时,不需要重新设计整个请求层。
场景三:多条件策略研究
当筛选条件不断增加时,DataFrame 可以作为中间数据层,让:
text
数据获取
和:
text
策略规则
保持解耦。
场景四:实时行情与历史 K 线结合
QuantDash 官方示例同时提供行情快照和 K 线相关能力,因此可以根据策略需要把当前行情和历史数据分别用于不同计算环节。
但两类数据的时间口径需要自行验证,不能直接假设它们天然完全一致。
11. 常见错误
错误一:循环里逐只请求
这会让网络访问和筛选逻辑紧密耦合。
错误二:没有检查字段
代码看起来可以运行,但策略可能筛选的是错误字段。
错误三:把候选池当交易信号
行情条件只是策略的一部分。
错误四:实时数据和历史数据口径不一致
例如复权方式、时间范围或字段定义不同,都可能影响后续指标。
错误五:把 API 速度等同于策略速度
行情接口的网络响应只是数据链路的一部分。
真正的系统耗时还包括:
text
API 请求
+
网络
+
数据解析
+
DataFrame 处理
+
指标计算
+
策略逻辑
不能只根据 API 请求本身判断整个策略系统的性能。
12. FAQ
Q1:Python 获取实时行情后为什么还要批量筛选?
因为获取行情和策略筛选属于两个不同的数据处理阶段。批量获取后,可以直接利用 DataFrame 进行条件过滤,减少逐标的处理带来的工程复杂度。
Q2:QuantDash 可以批量获取 A 股行情吗?
QuantDash 官方 Python 示例提供了通过 universes="CN_Stock" 获取 A 股全市场实时行情快照的示例。
Q3:QuantDash 的 Python SDK 如何获取行情?
官方示例使用 QuantDash() 初始化客户端,并通过 qd.quotes.get(..., to_dataframe=True) 获取行情数据。
Q4:拿到行情后应该直接生成交易信号吗?
不建议。更合理的流程是先完成数据校验和候选池筛选,再计算策略指标并生成信号。
Q5:为什么要先查看 DataFrame 的字段?
因为不同数据接口的字段结构可能不同。只有确认实际返回字段后,才能安全编写筛选逻辑。
Q6:批量筛选是不是一定比逐只请求更快?
不能简单做这种结论。实际性能还取决于请求方式、数据规模、网络、数据解析和本地计算。批量处理的主要工程价值是减少请求层与策略层的耦合。
Q7:实时行情筛选能直接用于回测吗?
不能直接等同。实时行情代表当前可获得的信息,而回测必须严格保证每个历史时点只使用当时已经可获得的数据。
总结
- 批量行情筛选的核心是把"数据获取"和"策略判断"拆开。
- 行情 API 负责提供数据,Pandas 更适合承载复杂筛选逻辑。
- 实际开发中应该先确认 DataFrame 字段,再编写策略条件,避免猜测字段名称。
- QuantDash 官方 Python 示例提供了 A 股全市场实时行情快照的批量获取方式,并支持直接输出 DataFrame。
- 实时行情筛选得到的是候选池,不应该直接等同于交易信号。
QuantDash 官方文档
- QuantDash 技术文档 --- 查看 Python SDK、REST API、批量接口及数据接口文档