第 1 章 · 02 OpenBB SDK 选股器与 CSV 缓存模式(对应 QS003) 速查摘要:本节演示一个最朴素的工程模式——CSV 缓存。先用 OpenBB SDK 的 FinViz 选股器选出"创新高"的股票,把结果落盘成 CSV;再用 批量拉每只股票 4 年的收盘价,也落盘成 CSV;之后所有分析都改成读本地 CSV。这套"调一次 API 落盘,后续 readcsv 复用"的写法,是量化研究里处理慢接口、避免反复请求限流的标准做法。 涉及脚本:原项目 QS003-stock-screener-openbb-sdk/01stockscreeneropenbbsdk.
速查摘要:本节演示一个最朴素的工程模式——CSV 缓存。先用 OpenBB SDK 的 FinViz 选股器选出"创新高"的股票,把结果落盘成 CSV;再用
economy.index批量拉每只股票 4 年的收盘价,也落盘成 CSV;之后所有分析都改成读本地 CSV。这套"调一次 API 落盘,后续 read_csv 复用"的写法,是量化研究里处理慢接口、避免反复请求限流的标准做法。
涉及脚本:原项目 QS003-stock-screener-openbb-sdk/01_stock_screener_openbb_sdk.py(约 65 行 Python)
⚠️ 注意:本脚本路径是写死的相对路径(
QS003-openbb-sdk/new_highs.csv、stock_data.csv),所以必须从仓库根目录运行,否则to_csv/read_csv找不到路径会报错。同样基于 OpenBB 3(from openbb_terminal.sdk import openbb),v4 不兼容。脚本里openbb.economy.index拉多只股票 4 年日线,作者自己注释"Takes a minute to run"(要跑一分钟),这正是要用缓存的核心原因。
FinViz(Financial Visualizations)是一个免费股票筛选网站,提供几十个预设筛选器(preset),比如 new_high(创新高)、top_gainers(涨幅榜)、undervalued(低估)等。OpenBB 的 openbb.stocks.screener.screener_data(preset) 把这些预设包装成一行 Python 调用,返回含 Ticker、Company、Sector、Price、Country 等列的 DataFrame。
筛选器的金融含义:每种 preset 对应一种选股逻辑。new_high 找近期股价创 52 周新高的股票,背后是动量效应(Momentum)——学术上证明强势股短期往往继续强势,是趋势跟随策略的基础。但创新高的股票也可能已经在顶部,所以还要叠加价格/行业/市场等过滤条件做二次筛选。
量化研究流程里,"拉数据"经常是最慢的一环。原因有三:
CSV 缓存模式就是解决这三个问题的最朴素方案:第一次调 API 拿到数据后立刻 to_csv 落盘,之后所有分析改成 read_csv 读本地。代价是文件占空间(本项目 stock_data.csv 就有 1.5MB),但换来零延迟、零限流风险、完全可复现。生产环境会换成 parquet/sqlite/专业缓存层,但思想一致。
脚本分两大段:选股落盘、拉价落盘。
第一段——选股 + 过滤 + 落盘(12-28 行):
# Stock screening with FinViz new_highs = openbb.stocks.screener.screener_data("new_high") # Filter Price > $15.00 and Country == USA portfolio_data = new_highs[ (new_highs.Price > 15) & (new_highs.Country == "USA") ] # Save screener portfolio portfolio_data.to_csv("QS003-openbb-sdk/new_highs.csv") # Load screener portfolio portfolio_data = pd.read_csv("QS003-openbb-sdk/new_highs.csv")
逐行解读:
screener_data("new_high") 一行拿到 FinViz"创新高"预设的完整列表。Price > 15 滤掉低价股(低于 15 美元的往往流动性差、噪声大、易被操纵),Country == "USA" 限定美国市场(数据质量好、交易规则统一)。to_csv("QS003-openbb-sdk/new_highs.csv") 落盘——注意相对路径写死了目录名,所以必须从仓库根跑。read_csv 读回——这就是缓存模式的标志。之后改代码、调参数,只需要重跑后面这部分,不用再请求 FinViz。第二段——批量拉价 + 落盘 + 读回(30-52 行):
# Get the tickers as a list tickers = portfolio_data.Ticker.tolist() # Get Stock Data for each Ticker # NOTE: Takes a minute to run... stock_data = openbb.economy.index( tickers, start_date="2016-01-01", end_date="2019-12-30" ) # Save stock_data.to_csv("QS003-openbb-sdk/stock_data.csv") # Load stock_data = pd.read_csv( "QS003-openbb-sdk/stock_data.csv", index_col="Date" )
逐行解读:
portfolio_data.Ticker.tolist() 把筛选结果的 Ticker 列转成 Python 列表——这是 pandas 列转 list 的标准写法,后续作为 API 入参。openbb.economy.index(tickers, start_date, end_date) 一次性拉多只股票的日线收盘价,返回宽表:行是日期、列是 ticker。这种"长格式变宽表"的设计对后续 pct_change、画图、组合优化都最友好。to_csv 落盘后立刻 read_csv 读回,完成一次缓存往返。关键细节:index_col="Date"(47-50 行):
stock_data = pd.read_csv( "QS003-openbb-sdk/stock_data.csv", index_col="Date" )
这是本节最值得注意的参数。CSV 文件存盘时把 Date 索引也写成了普通列,读回时如果不指定 index_col="Date",Date 会变成一列普通数据而非索引——后续所有按时序对齐的操作(pct_change、画图、合并其他股票)都会错位或报错。index_col="Date" 让读回的 DataFrame 立刻恢复成"日期索引 + 各 ticker 收盘价列"的标准时序结构。这是处理时序数据的基本功。
第三段——可视化(54-59 行):
stock_data['DELL'].plot(title="Dell") stock_data[['DELL','GOOG', 'V']].plot(subplots=True)
.plot() 画一只股票。.plot(subplots=True) 把多只股票画成分开的子图(默认会画在同一张图上,量纲不同时不好看)。技巧一:CSV 缓存模式的标准写法。模板就是 to_csv 紧跟 read_csv:
data = api_call(...) # 慢,只跑一次 data.to_csv("cache/path.csv") # 落盘 # ↓ 之后所有迭代都用这行 data = pd.read_csv("cache/path.csv", index_col="Date")
第一次跑整段,落盘后把第一行 API 调用注释掉,只保留 read_csv,迭代就快了。这种模式适合探索期;正式工程会换成 @cache 装饰器、joblib、parquet 分区存储等更自动化的方案。
技巧二:index_col="Date" 让时序对齐。读回 CSV 时,务必把日期列指定为索引,否则 pandas 不知道哪列是时间轴,后续 pct_change、rolling、画图都会出问题。规律:时序数据存盘会丢索引身份,读回必须重建。
技巧三:df['列名'].tolist() 列转 list。pandas 列是 Series,API 往往要 Python list。.tolist() 是最直接的转换,比 list(df['列']) 更地道、更快。
技巧四:plot(subplots=True) 多序列分开画。多只股票量纲不同(比如 DELL 几十块、GOOG 上百、V 几百),画在同一张图会被高价股"压扁",看不出低价股的波动。subplots=True 给每只单独一个子图,各自有独立纵轴,对比清晰。
💡 速查要点:CSV 缓存的核心动作是"API 调一次
to_csv落盘,后续read_csv复用",配合index_col="Date"重建时序索引。看到注释"Takes a minute to run"就知道这一步是缓存对象——把它落盘,研究迭代就能从分钟级降到毫秒级。
openbb.stocks.screener.screener_data("new_high") 一行调用,返回含 Ticker/Price/Country 的 DataFrame,对应动量选股逻辑。to_csv 落盘,之后 read_csv 复用——解决网络慢、限流、可复现三大痛点,是量化研究的标配。index_col="Date" 读回时重建日期索引,否则时序对齐失效,后续 pct_change/画图都出错。df['列'].tolist() 列转 list,是把 pandas 列喂给 API 入参的标准写法。QS003-openbb-sdk/),所以脚本必须从仓库根目录运行——这是这类教程项目的常见约定,生产代码会改用 pathlib.Path(__file__).parent 或配置项。