量化数据的 batch 接口有多好用?从 1 只到 500 只,批量拉数据的正确姿势

量化数据的 batch 接口有多好用?从 1 只到 500 只,批量拉数据的正确姿势

做量化最烦的事之一:一只一只地拉数据。

python 复制代码
# 你可能写过这种代码
import time

results = {}
for sym in my_500_stocks:
    try:
        df = some_api.get_klines(sym)
        results[sym] = df
    except Exception:
        pass
    time.sleep(0.5)  # 怕被封

# 500 只票 × 0.5 秒 = 250 秒 = 4 分钟起步

循环、sleep、try/except、进度不可见、失败了不知道哪些没拉到。

AlphaFeed 的 batch 接口把这些全解决了:传一个列表进去,内部自动分块、并发、重试,几秒钟全部拉完。

这篇文章详细讲 batch 接口的用法、原理和实战技巧。

最基本的用法

python 复制代码
from alphafeed import AlphaFeed

af = AlphaFeed()

symbols = ["600519.SH", "000001.SZ", "300750.SZ", "002594.SZ", "601318.SH"]

# 一行搞定
dfs = af.klines.batch(
    symbols,
    period="1d",
    count=250,
    adjust="forward",
    to_dataframe=True,
)

# 返回字典:{symbol: DataFrame}
for sym, df in dfs.items():
    print(f"{sym}: {len(df)} 条数据")

就这么简单。没有循环、没有 sleep、没有 try/except。

batch 内部做了什么

当你调用 af.klines.batch(symbols, ...) 时,SDK 内部做了这些事:

markdown 复制代码
你传入 500 个标的
    ↓
自动分块(每 100 个一组,分成 5 组)
    ↓
5 个线程并发请求(不是串行!)
    ↓
每个请求自动重试(最多 3 次,遇到超时或 5xx 自动重试)
    ↓
合并所有结果,返回一个字典

关键参数:

  • 默认分块大小:100 个标的/请求
  • 默认并发数:5 个线程
  • 默认重试:3 次

这意味着 500 只票,分成 5 组,5 个线程同时请求------时间约等于请求 1 组的时间,而不是请求 5 组的时间。

显示进度条

加上 show_progress=True,拉数据的过程可视化:

python 复制代码
dfs = af.klines.batch(
    symbols,
    period="1d",
    count=500,
    adjust="forward",
    to_dataframe=True,
    show_progress=True,  # 显示 tqdm 进度条
)
bash 复制代码
Fetching data: 100%|████████████████████| 5/5 [00:03<00:00,  1.67it/s]

进度条显示的是"分块"的进度。500 只票分 5 块,进度条就是 5 步。

实战 1:批量拉 K 线做对比分析

同时拉 20 只票的 K 线,算过去一年的涨幅和波动率:

python 复制代码
import pandas as pd
from alphafeed import AlphaFeed

af = AlphaFeed()

symbols = [
    "600519.SH", "000001.SZ", "300750.SZ", "002594.SZ", "601318.SH",
    "000858.SZ", "600036.SH", "000333.SZ", "601012.SH", "600276.SH",
    "600900.SH", "601398.SH", "600030.SH", "000651.SZ", "002415.SZ",
    "600887.SH", "601166.SH", "000568.SZ", "600809.SH", "002304.SZ",
]

dfs = af.klines.batch(
    symbols, period="1d", count=250,
    adjust="forward", to_dataframe=True, show_progress=True,
)

results = []
for sym, df in dfs.items():
    df = df.sort_values("trade_date").reset_index(drop=True)
    ret = df["close"].iloc[-1] / df["close"].iloc[0] - 1
    vol = df["close"].pct_change().std() * (252 ** 0.5)
    avg_amount = df["amount"].mean()

    results.append({
        "代码": sym,
        "年涨幅": f"{ret:+.1%}",
        "年化波动": f"{vol:.1%}",
        "日均成交额(亿)": f"{avg_amount / 1e8:.1f}",
    })

result_df = pd.DataFrame(results).sort_values("年涨幅", ascending=False)
print(result_df.to_string(index=False))

20 只票的 250 天数据,几秒钟拉完。

实战 2:批量拉分时数据

不只日线,分时数据也支持批量:

python 复制代码
from alphafeed import AlphaFeed

af = AlphaFeed()

symbols = ["600519.SH", "000001.SZ", "300750.SZ"]

# 批量拉日内 1 分钟线
dfs = af.klines.intraday_batch(symbols, to_dataframe=True)

for sym, df in dfs.items():
    morning_vol = df[df["trade_time"] < df["trade_time"].iloc[0][:11] + "11:30:00"]["volume"].sum()
    total_vol = df["volume"].sum()
    pct = morning_vol / total_vol if total_vol > 0 else 0
    print(f"{sym}: 上午成交占比 {pct:.1%}  总 {len(df)} 根分钟线")

实战 3:批量查五档盘口

python 复制代码
from alphafeed import AlphaFeed

af = AlphaFeed()

symbols = ["600519.SH", "000001.SZ", "300750.SZ", "002594.SZ"]

# 批量查盘口
depths = af.depth.batch(symbols)

print(f"{'标的':<12} {'买一':>10} {'卖一':>10} {'价差(bps)':>10}")
print("-" * 45)
for sym, d in depths.items():
    bid1 = d["bid_prices"][0]
    ask1 = d["ask_prices"][0]
    mid = (bid1 + ask1) / 2
    spread_bps = (ask1 - bid1) / mid * 10000 if mid > 0 else 0
    print(f"{sym:<12} {bid1:>10.2f} {ask1:>10.2f} {spread_bps:>10.1f}")

一次请求拿到多只票的盘口,适合做流动性分析或估算交易成本。

实战 4:批量查标的信息

python 复制代码
from alphafeed import AlphaFeed

af = AlphaFeed()

# 查一批标的的基本信息
symbols = ["600519.SH", "000001.SZ", "AAPL.US", "00700.HK"]
insts = af.instruments.batch(symbols)

for inst in insts:
    ext = inst.get("ext", {})
    print(f"{inst['symbol']:>12s}  {inst['name']:<8s}  "
          f"地区={inst['region']}  类型={inst['type']}  "
          f"上市={ext.get('listing_date', 'N/A')}")

实战 5:批量查复权因子

python 复制代码
from alphafeed import AlphaFeed

af = AlphaFeed()

symbols = ["600519.SH", "000001.SZ", "000858.SZ", "002594.SZ"]

df = af.klines.ex_factors(symbols, to_dataframe=True)

# 每只票有多少次除权除息
for sym in symbols:
    sub = df[df["symbol"] == sym]
    print(f"{sym}: {len(sub)} 次除权除息")

实战 6:200 只票的策略批量回测

batch 的真正威力在大规模分析。比如对 200 只票跑同一个策略:

python 复制代码
import pandas as pd
import numpy as np
from alphafeed import AlphaFeed

af = AlphaFeed()

# 假设你有一个 200 只票的股票池
stock_pool = [
    "600519.SH", "000001.SZ", "300750.SZ", "002594.SZ", "601318.SH",
    "000858.SZ", "600036.SH", "000333.SZ", "601012.SH", "600276.SH",
    # ... 省略,实际上你可以放 200 只
]

# 一次性拉取所有数据
print("正在拉取数据...")
dfs = af.klines.batch(
    stock_pool,
    period="1d",
    count=500,
    adjust="forward",
    to_dataframe=True,
    show_progress=True,
)
print(f"拉取完成: {len(dfs)} 只票")

# 对每只票跑双均线策略
def backtest_ma(df: pd.DataFrame) -> dict:
    df = df.sort_values("trade_date").reset_index(drop=True)
    df["ma20"] = df["close"].rolling(20).mean()
    df["ma60"] = df["close"].rolling(60).mean()
    df["signal"] = (df["ma20"] > df["ma60"]).astype(int)
    df["position"] = df["signal"].shift(1).fillna(0)
    df["ret"] = df["close"].pct_change().fillna(0)
    df["strat_ret"] = df["position"] * df["ret"]

    equity = (1 + df["strat_ret"]).cumprod()
    return {
        "total_return": equity.iloc[-1] - 1,
        "max_drawdown": (equity / equity.cummax() - 1).min(),
    }

results = []
for sym, df in dfs.items():
    if len(df) < 100:
        continue
    r = backtest_ma(df)
    r["symbol"] = sym
    results.append(r)

rdf = pd.DataFrame(results).sort_values("total_return", ascending=False)

print(f"\n=== {len(rdf)} 只票回测完成 ===")
print(f"盈利的: {(rdf['total_return'] > 0).sum()} 只")
print(f"亏损的: {(rdf['total_return'] <= 0).sum()} 只")
print(f"平均收益: {rdf['total_return'].mean():+.2%}")
print(f"\n表现最好的 5 只:")
print(rdf.head()[["symbol", "total_return", "max_drawdown"]].to_string(index=False))
print(f"\n表现最差的 5 只:")
print(rdf.tail()[["symbol", "total_return", "max_drawdown"]].to_string(index=False))

重点: 200 只票的 500 天 K 线,用 batch 拉取只需要几秒钟。拉数据不再是瓶颈,你可以把时间花在策略分析上。

batch vs 手动循环:代码对比

手动循环(传统方式):

python 复制代码
import time

results = {}
failed = []
for i, sym in enumerate(symbols):
    try:
        df = some_api.get_klines(sym, period="1d", count=500)
        results[sym] = df
        print(f"[{i+1}/{len(symbols)}] {sym} OK")
    except Exception as e:
        print(f"[{i+1}/{len(symbols)}] {sym} 失败: {e}")
        failed.append(sym)
    time.sleep(0.5)  # 怕被封

# 重试失败的
for sym in failed:
    try:
        df = some_api.get_klines(sym, period="1d", count=500)
        results[sym] = df
    except Exception:
        print(f"{sym} 重试仍失败")
    time.sleep(1)

print(f"成功: {len(results)} / {len(symbols)}")

AlphaFeed batch:

python 复制代码
dfs = af.klines.batch(
    symbols, period="1d", count=500,
    adjust="forward", to_dataframe=True, show_progress=True,
)

一行代码替代了上面的 20 行。分块、并发、重试、进度显示全内置。

batch 的技术细节

参数 默认值 说明
分块大小 100 只/请求 SDK 自动把标的列表切成 100 个一组
并发数 5 个线程 5 组同时请求,不是排队
重试次数 3 次 超时、网络错误、5xx 自动重试
进度条 show_progress=True 显示 基于 tqdm,显示分块级进度

为什么分块而不是一个请求传 500 个标的? 因为 URL 有长度限制,500 个标的的代码拼成逗号分隔的字符串会超过 HTTP URL 的安全长度。SDK 帮你处理了这个问题。

哪些接口支持 batch

接口 单个查询 批量查询
K 线 af.klines.get(symbol) af.klines.batch(symbols)
日内分时 af.klines.intraday(symbol) af.klines.intraday_batch(symbols)
实时行情 af.quotes.get(symbols=[...]) 本身就支持多标的
五档盘口 af.depth.get(symbol) af.depth.batch(symbols)
标的信息 af.instruments.get(symbol) af.instruments.batch(symbols)
复权因子 --- af.klines.ex_factors(symbols)

和竞品的对比

操作 AlphaFeed akshare Tushare
拉 100 只票日 K af.klines.batch(symbols) 一行搞定 手动 for 循环 + sleep 手动 for 循环 + token 配额
自动重试 SDK 内置 需要自己写 try/except 需要自己写
并发请求 SDK 内置 5 线程 不支持(爬虫会被封) 有频率限制
进度显示 show_progress=True 自己用 tqdm 包一层 自己实现
500 只票耗时 约 5-10 秒 5+ 分钟(含 sleep) 1-3 分钟(含频率等待)

结语

batch 接口是 AlphaFeed 最实用的功能之一。它解决的问题很朴素:当你需要分析很多只票的时候,不应该等很久、写很多错误处理代码。

一个列表传进去,进度条跑一下,数据就到手了。剩下的时间你可以专注在分析和策略上,而不是和网络请求搏斗。

参考文献:

相关推荐
rruining2 小时前
Java设计模式——结构型
后端
卷无止境2 小时前
C++ 编程的一大坑:非常量全局变量是"万恶之源"
c++·后端
Sinclair3 小时前
认识安企CMS-系统和模板文件结构
后端
柒和远方4 小时前
Phase 7.4 学习博客:为什么多 API 项目需要 Swagger / OpenAPI
前端·后端·架构
柒和远方4 小时前
Phase 7.3 复盘:后台任务不只是“扔进队列”,还要能被看见
前端·后端·架构
易协同低代码4 小时前
通达OA模块开发实战
后端
聂二AI落地内参4 小时前
LLM 数据增强任务卡 4 天:upsert 少传 id 后发生了什么
后端
RainCity4 小时前
Java Swing 自定义组件库分享(十三)
java·笔记·后端
livemetee5 小时前
【关于Spring声明式事务】
java·后端·spring