如何给 AlphaFeed 取数加本地缓存?(省额度、提速、离线复用)

0 阅读4分钟

一句话回答: 调试策略时反复跑同一段取数代码,会重复请求同样的 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 额度又提速。

参考