量化数据的 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 最实用的功能之一。它解决的问题很朴素:当你需要分析很多只票的时候,不应该等很久、写很多错误处理代码。
一个列表传进去,进度条跑一下,数据就到手了。剩下的时间你可以专注在分析和策略上,而不是和网络请求搏斗。
参考文献:
- AlphaFeed :alphafeed.org/
- Python SDK 快速开始:docs.alphafeed.org/zh-Hans/sdk...