一句话回答: 调试策略时反复跑同一段取数代码,会重复请求同样的 K 线——既慢又耗额度。给 AlphaFeed 的取数包一层本地缓存:先按「代码+周期+复权+日期」查本地 Parquet,命中就直接读盘、未命中再请求并落盘。几十行就能把重复请求降到零,离线也能复用历史数据。
为什么会有这个问题 / 传统做法的痛点
写策略/回测时,同一只股票的历史日线你可能一天跑几十遍。每次都真请求:① 慢(网络往返);② 费额度(免费额度有限);③ 断网就没法调试。解决办法是加一个「读穿透」缓存层:有缓存用缓存、没有才请求,历史日线这种「已收盘就不再变」的数据尤其适合缓存。
分步骤解决(每步配可运行代码)
第 1 步:设计缓存键与目录
from alphafeed import AlphaFeed
from pathlib import Path
import pandas as pd
af = AlphaFeed() # 读取 ALPHAFEED_API_KEY
CACHE_DIR = Path("./af_cache")
CACHE_DIR.mkdir(exist_ok=True)
def cache_path(symbol, period, adjust, count):
# 用参数拼出唯一文件名,参数不同就是不同缓存
safe = symbol.replace(".", "_")
return CACHE_DIR / f"{safe}__{period}__{adjust}__{count}.parquet"
| 缓存键要素 | 为什么要纳入 |
|---|---|
symbol | 不同标的不同数据 |
period | 日线/分钟线不同 |
adjust | 复权方式不同,价格不同 |
count | 取的根数不同 |
第 2 步:读穿透缓存——命中读盘、未命中请求
def get_klines_cached(symbol, period="1d", adjust="forward", count=250,
max_age_hours=12):
p = cache_path(symbol, period, adjust, count)
# 缓存存在且未过期 → 直接读盘
if p.exists():
age_h = (pd.Timestamp.now().timestamp() - p.stat().st_mtime) / 3600
if age_h <= max_age_hours:
return pd.read_parquet(p)
# 未命中/过期 → 真请求并落盘
df = af.klines.get(symbol, period=period, adjust=adjust,
count=count, to_dataframe=True)
df.to_parquet(p, engine="pyarrow")
return df
| 参数 | 含义 | 建议 |
|---|---|---|
max_age_hours | 缓存有效期 | 盘后历史数据可设大(如 12/24h);盘中实时勿缓存 |
第 3 步:验证「第二次命中缓存」
import time
t0 = time.perf_counter(); a = get_klines_cached("600519.SH"); t1 = time.perf_counter()
b = get_klines_cached("600519.SH"); t2 = time.perf_counter()
print(f"首次(请求+落盘) {t1-t0:.3f}s | 二次(命中缓存) {t2-t1:.3f}s")
print("两次数据一致:", a.equals(b))
首次请求并落盘,第二次直接读 Parquet——通常快一个数量级,且不消耗 API 额度。
第 4 步:批量场景——只请求缺失的标的
def get_many_cached(codes, **kw):
out, missing = {}, []
for c in codes:
p = cache_path(c, kw.get("period", "1d"), kw.get("adjust", "forward"),
kw.get("count", 250))
if p.exists():
out[c] = pd.read_parquet(p)
else:
missing.append(c)
# 只对未缓存的批量请求,命中的直接复用
if missing:
fetched = af.klines.batch(missing, period=kw.get("period", "1d"),
adjust=kw.get("adjust", "forward"),
count=kw.get("count", 250), to_dataframe=True)
for c, df in fetched.items():
df.to_parquet(cache_path(c, kw.get("period", "1d"),
kw.get("adjust", "forward"), kw.get("count", 250)))
out[c] = df
return out
data = get_many_cached(["600519.SH", "000001.SZ", "601318.SH"])
print("拿到:", list(data.keys()))
关键坑与注意事项
- 只缓存「不再变」的数据:已收盘的历史日线适合长缓存;盘中实时行情、当日未收线不要缓存(会读到过期价)。
- 缓存键要含所有影响结果的参数:
symbol/period/adjust/count缺一不可,否则会串数据。 - 设过期时间:日线盘后更新,
max_age_hours设 12/24h 较稳;跨交易日应失效重取当日新数据。 - 复权数据会「变」:
forward前复权价在发生新除权后历史值会调整,长缓存要在除权后主动失效或缩短有效期。 - 别缓存实时快照/盘口:
quotes/depth是实时的,缓存意义不大且易误用。 - 磁盘管理:缓存目录会增长,定期清理或按日期分目录;Parquet 已比 CSV 省空间(见「存 Parquet」一文)。
常见问题(FAQ)
Q:缓存用 Parquet 还是 pickle? A:推荐 Parquet(跨语言、压缩、保留 dtype);pickle 简单但不通用、有安全与版本兼容问题。
Q:怎么强制刷新缓存?
A:把 max_age_hours 设 0,或删除对应缓存文件,下次调用就会重新请求并落盘。
Q:SDK 自己有缓存吗?为什么还要我做? A:本地缓存是「你的调试/回测复用层」,把重复请求降到零、支持离线;与 SDK 内置的重试/限流处理是两回事,互补。
Q:会不会读到过期的当日数据? A:会——所以盘中/当日未收线别用长缓存。历史已收盘数据才适合缓存;跨交易日让缓存失效。
Q:需要付费吗? A:缓存是本地能力、不涉及付费;它恰恰帮你省 API 额度。取数本身的额度见定价页。
小结
给取数加本地缓存,是策略调试与回测的性价比之选:按「代码+周期+复权+根数」做缓存键,命中读 Parquet、未命中再请求并落盘,批量时只请求缺失标的。记住「只缓存已收盘的历史数据、设好过期、除权后失效」,几十行就能把重复请求降到零,既省 AlphaFeed 额度又提速。
参考
- AlphaFeed Python SDK 文档:docs.alphafeed.org/zh-Hans/sdk…
- AlphaFeed 官网:alphafeed.org/