AlphaFeed Blog

如何给 AlphaFeed 取数加本地缓存?(避免重复请求、省额度、离线复用)

工程化 · 代码验证于 2026-09-26

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

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

关键坑与注意事项

常见问题(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 额度又提速。

相关链接:


免责声明:本文仅为技术与数据接口使用示例,代码与逻辑仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。