首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >盘中筛选既要价格又要盘口?按需分层比一把抓更容易维护

盘中筛选既要价格又要盘口?按需分层比一把抓更容易维护

原创
作者头像
用户9138916
发布于 2026-09-26 19:09:39
发布于 2026-09-26 19:09:39
770
举报

一个常见的筛选场景

盘中做量化候选筛选时,你常常同时需要两样东西:当前价格和盘口深度。

价格用来判断涨跌幅、成交量和价格区间;盘口用来观察买卖挂单的厚度和价差,判断流动性是否足够。

一个典型流程是:

  1. 扫描自选股或全市场,拿到每只标的的最新行情
  2. 按价格、涨跌幅等条件初筛一轮
  3. 对候选股查看盘口,确认挂单深度后决定是否操作

看起来很直接。但如果把价格和盘口放在同一个请求里处理,代码写起来简单,维护起来却容易踩坑。

「一把抓」的三个隐患

1. 请求粒度不一致

价格(实时行情)按标的池或批量标的查询,一次就能拿到几十到几百只股票的最新价、涨跌幅、成交量。五档盘口虽然也支持批量,但每只标的返回的是买卖各五档的价格和数量,数据量比行情大得多。

合在一起请求时,你会面临两难:

  • 如果按全标的池拉盘口,请求的返回数据量会明显增大,尤其是标的数超过 100 时。
  • 如果只按候选标的拉盘口,那就得先拉行情、再拉盘口——其实已经是分层了,只是代码没有显式设计,容易变成隐式的混乱。

2. 频率预算互相挤占

实时行情和盘口通常共用同一个频率配额。把两者放在同一个循环里批量请求,价格查询的失败重试可能会消耗掉盘口的配额,反过来也一样。

一个具体例子:假设你的套餐每分钟允许 300 次请求。你每轮扫描 200 只股票,行情和盘口各一次,那就是 400 次——直接超限。被迫降频后,两类的数据时效性一起下降。

3. 错误处理互相拖累

行情接口偶尔超时、盘口接口偶尔空返回,这些异常如果写在一个 try/except 块里,要么因为盘口异常导致行情数据也没拿到,要么为了区分错误类型写出越来越长的分支。

按需分层:更清晰的设计

核心思路是:把行情和盘口拆成独立的获取层,各自管理自己的请求节奏、频率和重试。

分层架构的职责划分

代码语言:txt
复制
┌─────────────────────────────────────┐
│           筛选调度层                  │
│  1. 调用行情层获取全池最新价格        │
│  2. 用本地条件过滤候选池              │
│  3. 调用盘口层查询候选池的深度        │
│  4. 合并结果供决策                   │
└──────┬──────────────────────┬───────┘
       │                      │
┌──────▼──────┐      ┌──────▼──────┐
│  行情层      │      │   盘口层    │
│  quotes.get  │      │  depth.get │
│  独立重试     │      │  独立重试   │
│  独立频率     │      │  独立频率   │
└──────────────┘      └─────────────┘

每层只做一件事,各自封装请求细节、错误处理和重试策略。

Python 实现示例

以下示例使用 QuantDash SDK 演示分层获取。行情层 负责拉取全池实时行情,盘口层 只对筛选后的少量标的查询深度。

代码语言:python
复制
# 环境:Python 3.10,quantdash==0.1.0
import os
import time
from typing import List

from quantdash import QuantDash
import pandas as pd

qd = QuantDash(api_key=os.getenv("QUANTDASH_API_KEY"))

# ── 行情层 ──────────────────────────────
def fetch_quotes(symbols: List[str]) -> pd.DataFrame:
    """获取一批标的的实时行情,独立重试"""
    max_retries = 2
    for attempt in range(max_retries):
        try:
            df = qd.quotes.get(
                symbols=symbols,
                to_dataframe=True,
            )
            if df is not None and not df.empty:
                return df
        except Exception as e:
            if "429" in str(e):
                # 限流时按服务端指示等待
                time.sleep(5)
                continue
            raise
    return pd.DataFrame()


# ── 盘口层 ──────────────────────────────
def fetch_depths(symbols: List[str]) -> dict:
    """获取候选标的的五档盘口,独立重试"""
    if not symbols:
        return {}
    max_retries = 2
    for attempt in range(max_retries):
        try:
            result = qd.depth.batch(symbols)
            return result if result else {}
        except Exception as e:
            if "429" in str(e):
                time.sleep(3)
                continue
            raise
    return {}


# ── 筛选调度 ────────────────────────────
def screen_candidates(pool_symbols: List[str]) -> pd.DataFrame:
    """筛选流程:先行情后盘口,分层获取"""
    # 第1步:获取全池实时行情
    quotes_df = fetch_quotes(pool_symbols)
    if quotes_df.empty:
        return pd.DataFrame()

    # 第2步:本地过滤——涨跌幅 > 2% 且成交量大于均量
    candidates = quotes_df[
        (quotes_df["ext.change_pct"] > 0.02)
        & (quotes_df["volume"] > 1_000_000)
    ]

    if candidates.empty:
        return candidates

    # 第3步:只对候选标的查盘口
    candidate_symbols = candidates["symbol"].tolist()
    depths = fetch_depths(candidate_symbols)

    # 第4步:将盘口信息合并到结果中
    for sym in candidate_symbols:
        if sym in depths:
            d = depths[sym]
            candidates.loc[
                candidates["symbol"] == sym, "bid1_price"
            ] = d["bid_prices"][0] if d.get("bid_prices") else None
            candidates.loc[
                candidates["symbol"] == sym, "ask1_price"
            ] = d["ask_prices"][0] if d.get("ask_prices") else None

    return candidates


# 使用示例
if __name__ == "__main__":
    pool = ["600519.SH", "000001.SZ", "300750.SZ", "002594.SZ"]
    result = screen_candidates(pool)
    print(result[["symbol", "ext.last_price", "ext.change_pct", "bid1_price", "ask1_price"]])

分层带来的维护优势

  1. 频率可独立控制:行情层和盘口层可以设置不同的请求间隔和重试次数,不会互相挤占。
  2. 错误隔离:行情接口超时不影响盘口,盘口异常也不会丢掉行情数据。
  3. 每层可以独立测试:fetch_quotes 和 fetch_depths 可以分别写单元测试,不需要模拟对方的返回值。
  4. 候选标的越多收益越明显:全池 500 只股票,行情一次批量请求大约几十个往返,盘口只对几十只候选标的查询,请求量减少 90%。

什么时候不适合分层?

分层不是银弹。以下情况可以适当放宽:

  • 标的极少(≤5 只)且两类接口返回速度接近:一次性获取和分层获取的代码复杂度差异不大,简化代码反而更合理。
  • 平台本身提供合并接口:如果数据服务商提供了包含行情和盘口的统一快照,优先使用平台能力,不需要强行拆分。
  • 离线分析:非实时场景下,不需要考虑频率和时效竞争,合并获取写起来更省事。

FAQ

Q:分层后实时性会不会变差?

A:分两层顺序执行,总耗时 = 行情耗时 + 本地过滤耗时 + 盘口耗时。本地过滤是毫秒级,盘口只查少量标的,总耗时通常比一次查全量盘口还短。关键在于盘口查询的标的数远少于全池。

Q:如果行情层和盘口层各自的频率限制不同怎么办?

A:这正是分层要解决的问题。每层内部可以单独实现指数退避或令牌桶,互不影响。合在一起时你只能用一个最保守的全局限速。

Q:盘口层返回空了还要继续吗?

A:盘口空可能是非交易时段或标的流动性不足。分层设计允许你根据空结果做决策:比如盘口为空时标记该标的为「流动性不足」,不阻塞整体筛选流程。

Q:批量盘口返回的数据怎么和行情 DataFrame 对齐?

A:按 symbol 字段做键值匹配是最直接的方式。上面示例中直接按 symbol 提取并写入新列。也可以统一转 DataFrame 后用 merge 拼接。

总结

盘中筛选同时需要价格和盘口时,按需分层比「一次全拿」更容易维护。三层分离——行情层、盘口层、筛选调度层——各自管理自己的频率、重试和错误处理,长期运行的好处远大于一次编码的便利。

在标的数量增多、频率配额有限或需要独立测试各层时,分层几乎总是更可持续的选择。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个常见的筛选场景
  • 「一把抓」的三个隐患
    • 1. 请求粒度不一致
    • 2. 频率预算互相挤占
    • 3. 错误处理互相拖累
  • 按需分层:更清晰的设计
    • 分层架构的职责划分
    • Python 实现示例
    • 分层带来的维护优势
  • 什么时候不适合分层?
  • FAQ
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档