第 1 章 · 03 yfinance 免费数据三件套:行情/期权/财报(对应 QS046) 速查摘要: 是最易上手的免费金融数据源——一行 ,无需注册 API key、无需 OpenBB 这套庞然大物,就能拉到股票行情(OHLCV)、期权链(calls/puts)、财务报表三大类数据。本节对 AAPL/MSFT/GOOGL 三只科技股批量拉取这三类数据,再用一套数据清洗模板(选列 + astype 强制类型 + 加 Symbol 标签列 + dropna + concat)把它们整理成统一的标准 DataFrame,是"免费数据获取"最实用的主角脚本。 涉及脚本:原项目 QS046-download-free-market-data/01downloadfreemarketdata.
速查摘要:
yfinance是最易上手的免费金融数据源——一行pip install yfinance,无需注册 API key、无需 OpenBB 这套庞然大物,就能拉到股票行情(OHLCV)、期权链(calls/puts)、财务报表三大类数据。本节对 AAPL/MSFT/GOOGL 三只科技股批量拉取这三类数据,再用一套数据清洗模板(选列 + astype 强制类型 + 加 Symbol 标签列 + dropna + concat)把它们整理成统一的标准 DataFrame,是"免费数据获取"最实用的主角脚本。
涉及脚本:原项目 QS046-download-free-market-data/01_download_free_market_data.py(约 80 行 Python)
⚠️ 注意:
yfinance不依赖 OpenBB,独立可跑,是本章对环境要求最低的脚本。但它本质上是"逆向"雅虎财经网页接口的非官方库,接口随时可能因雅虎改版而失效——稳定性不如付费 API,但免费、零门槛、覆盖全球市场。财报返回的 DataFrame 默认是"行=指标、列=日期",要.transpose()转置才能按公司逐行整理。
yfinance 围绕 Ticker 对象组织,一个 ticker 对应一只股票:
Ticker.history(period="1y"):历史 OHLCV(开高低收成交量),period 可选 1mo/3mo/1y/5y/max 等。返回行=日期、列=Open/High/Low/Close/Volume 的 DataFrame。Ticker.option_chain():期权链。返回一个含 .calls 和 .puts 两个 DataFrame 的对象,字段包括合约代号 contractSymbol、行权价 strike、最新价 lastPrice、买一 bid、卖一 ask、成交量 volume、未平仓量 openInterest。期权数据反映市场对未来的预期(波动率、价格区间)。Ticker.financials:年度财务报表(损益表)。返回的 DataFrame 默认是"行=财务指标(如 Total Revenue、Net Income)、列=报告日期",每只股票一张表。openInterest(未平仓量)高的行权价往往是市场认为的"关键价位"(支撑/阻力)。API 返回的原始 DataFrame 有几个通病:列太多(含 Dividends/Stock Splits 等用不上的列)、类型混乱(数值列可能存成 object)、多只股票数据无法区分来源。本节给出一套可复用的四步清洗模板:
这套模板适用于几乎所有"多标的 + 多类数据"的批量整理场景,值得背下来。
stock_symbols = ["AAPL", "MSFT", "GOOGL"] market_data = { symbol: yf.Ticker(symbol).history(period="1y") for symbol in stock_symbols } options_data = {symbol: yf.Ticker(symbol).option_chain() for symbol in stock_symbols} financials_data = {symbol: yf.Ticker(symbol).financials for symbol in stock_symbols}
要点:
{symbol: Ticker(symbol).history(...) for symbol in stock_symbols} 一次拿到三只股票的行情,key 是代码、value 是 DataFrame。Ticker 对象(三行各构造一次)——虽然能复用同一个 Ticker 对象,但这样写更直白、可读性高。option_chain() 不传参,默认返回最近到期月份的期权链;传 expiration="2024-12-20" 可指定到期日。standardized_market_data = [] for symbol, df in market_data.items(): if not df.empty: df = df[["Open", "High", "Low", "Close", "Volume"]] df = df.dropna() df = df.astype(float) df["Symbol"] = symbol standardized_market_data.append(df) market_df = pd.concat(standardized_market_data).reset_index()
四步模板的完整体现:
df[["Open", "High", "Low", "Close", "Volume"]] 选列,丢掉 Dividends、Stock Splits 等无关列。df.dropna() 清缺失。df.astype(float) 强制把所有列转 float。这一步很关键——history() 返回的列虽然看起来是数值,但有时 Dividends/Splits 列会混入 int 或 object,统一成 float 后续算指标、画图才不会出错。df["Symbol"] = symbol 打标签,标记每行属于哪只股票。pd.concat(standardized_market_data).reset_index() 把多只股票的 DataFrame 纵向拼接成长表,reset_index() 把日期从索引变成普通列(因为不同股票的日期索引会冲突,变成长表更通用)。这种"一行一只股票一天"的长表(Long Format)是 pandas 推荐的整洁数据(Tidy Data)格式,适合后续 groupby、pivot。
standardized_options_data = [] for symbol, option_chain in options_data.items(): calls = option_chain.calls puts = option_chain.puts for opt_type, opt_df in [("call", calls), ("put", puts)]: if not opt_df.empty: opt_df = opt_df[ [ "contractSymbol", "strike", "lastPrice", "bid", "ask", "volume", "openInterest", ] ] opt_df = opt_df.dropna() opt_df = opt_df.astype( { "strike": float, "lastPrice": float, "bid": float, "ask": float, "volume": float, "openInterest": float, } ) opt_df["Type"] = opt_type opt_df["Symbol"] = symbol standardized_options_data.append(opt_df) options_df = pd.concat(standardized_options_data).reset_index(drop=True)
期权清洗比行情多两个细节:
[("call", calls), ("put", puts)] 元组列表复用同一段清洗逻辑——避免重复代码的常用模式。astype({...}) 用字典批量转多列:不同列类型转换规则不同,传字典可一次性指定每列目标类型,比逐列写简洁。Type(call/put)+ Symbol(股票代码),concat 后能按这两个维度筛选。reset_index(drop=True) 的 drop=True 表示丢弃原索引直接重编号,不像行情那样需要把日期保留成列。
standardized_financials_data = [] for symbol, df in financials_data.items(): if not df.empty: df = df.transpose() df["Symbol"] = symbol df = df.dropna(axis=1, how="all") standardized_financials_data.append(df) financials_df = pd.concat(standardized_financials_data).reset_index()
关键操作——df.transpose() 转置:financials 默认"行=指标、列=日期",但我们要"一行一只股票、列=各指标"才能跨公司 concat。.transpose() 行列互换,加 Symbol 列后 concat 成长表。
dropna(axis=1, how="all") 删掉全空的指标列——有些财务项在某些公司不存在(比如研发费用),整列 NaN 的删掉。
if market_df.isnull().values.any(): market_df = market_df.dropna() if options_df.isnull().values.any(): options_df = options_df.dropna()
兜底清洗——isnull().values.any() 先检查整张表有没有任何缺失,有再 dropna。这是防御性写法,确保最终三张表都干净。
技巧一:transpose() 处理财务报表。这是 yfinance 财报数据的标准操作:API 默认"行=指标、列=日期"是为单只股票查看方便,但跨公司对比时要先 transpose 成"行=日期、列=指标",再加 Symbol 列 concat 成长表。规律:**API 返回的"天然形状"未必是分析要的形状,转置是家常便饭**。
技巧二:astype(dict) 批量转多列类型。当不同列要转不同类型(有的转 float、有的转 int、有的转 str),传字典 astype({"strike": float, "volume": float}) 一次性搞定,比逐列赋值优雅,也比 astype(float) 全转更安全(能跳过不该转的列)。
技巧三:加 Symbol 列让多标的 concat 不丢身份。批量整理多只股票数据时,concat 前先给每个子表加 Symbol = symbol 列,这样拼起来后还能用 df[df.Symbol == "AAPL"] 筛选。否则多只股票数据混在一起无法区分,是新手最常犯的错。
技巧四:for opt_type, opt_df in [("call", calls), ("put", puts)] 元组列表复用逻辑。把"标识 + 数据"打包成元组列表,一次循环处理多个变体(call/put、买/卖、多空),是消除重复代码的利器,比写两遍几乎相同的代码清晰得多。
技巧五:长表(Long Format)是整洁数据。多标的场景下,"一行一只股票一天/一个合约/一个报告期"的长表比宽表更通用——支持 groupby、pivot、merge,符合 Hadley Wickham 的 Tidy Data 原则。
💡 速查要点:yfinance 三件套(
history/option_chain/financials)是免费拉数据的主力。清洗模板四步走——选列、astype、加 Symbol 标签、dropna——能把多只股票、多类数据整理成统一长表。财报务必transpose()转置后再跨公司 concat。
pip install yfinance 即可,是免费数据源里最易上手的。Ticker.history(period="1y") 行情、Ticker.option_chain() 期权链(calls+puts)、Ticker.financials 财报。transpose() 转置财报:API 默认"行=指标、列=日期",转置后才能跨公司 concat 成长表。astype(dict) 批量转多列类型,比逐列写更优雅;长表(Long Format)是 Tidy Data 推荐格式。