ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

baostock详解:用Python轻松获取A股行情数据的实战指南

baostock详解:用Python轻松获取A股行情数据的实战指南 做量化分析的人都知道数据是一切策略的地基。但A股的历史行情数据想拿得干净、稳定、还免费真的没那么容易。以前我折腾过各种数据源有的要注册申请、有的要token积分、有的接口动不动就反爬封IP直到后来发现baostock这个Python金融数据获取工具库算是把“用Python拿A股数据”这件事彻底简化了。它不收费、不需要注册接口返回值设计得像数据库查询结果一样规整对新手和资深玩家都很友好。这篇博客我就把baostock从安装到实战的完整使用心得写出来包括接口细节、参数选择、数据落地方式以及我踩过的那些坑。1. 为什么我选baostock而不是其他数据源1.1 先聊聊选型背景做A股数据获取Python生态里绕不开几个选项tushare、akshare、baostock还有各种券商的量化接口。tushare知名度最高但要把历史数据拉得比较全对积分等级有要求新注册用户拿到的权限比较有限akshare免费但它的实现方式是爬取公开网页数据格式偶尔会变接口响应速度也偏慢券商接口通常绑定特定券商账户有资金门槛。这种情况下baostock算是一个相对平衡的方案免费、接口稳定、覆盖日线和部分分钟线数据而且它不需要用户注册和付费调用起来没有那么多条条框框。我2020年第一次接触baostock的时候最直观的感受就是它的接口设计很像“SQL查询”——你告诉它要哪只股票、哪个时间段、哪些字段它返回一组规整的行和列。这种设计对后续做量化回测、数据入库、字段筛选都非常友好不像爬虫型数据源那样还得自己清洗乱七八糟的HTML结构。1.2 baostock的长板和短板先说实话baostock不是万能的。它提供的是A股股票、指数、基金的日线数据分钟数据只覆盖最近几年的历史tick数据没有。如果你做的是中低频策略、因子研究、财务数据分析它完全够用如果你想做高频交易研究那它不适合。但从我的使用体验来看它的长板非常突出完全免费且无token机制不用申请密钥pip安装后直接login就能用数据规范性强返回字段名清晰日期格式统一除权除息数据可以通过adjustflag参数直接处理覆盖范围广股票、指数、基金、可转债都有还包括季度财务数据、每日估值指标、分红送转记录纯Python实现不依赖额外的数据库单机环境就能跑起来另外值得一提的还有它的速度。对于日线数据baostock的查询速度是可以接受的一次性拉取一只股票五年的日线也就几百毫秒。不过它有个隐性限制单次查询返回的行数不能太多如果一次性拉取几十年的全量分钟数据接口会自动截断这个我后面会详细讲怎么规避。2. 安装与快速上手2.1 安装和环境准备baostock对Python版本要求不高Python 3.6以上都能正常使用。安装非常简单用pip就能搞定pip install baostock国内用户建议加上国内镜像源速度会快很多pip install baostock -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成之后可以先在Python交互环境里验证一下import baostock as bs print(bs.__version__)如果能看到版本号输出说明安装成功。baostock依赖pandas所以安装时它会自动把pandas带上来不需要手动处理依赖问题。2.2 第一次拉数据baostock的使用套路非常固定分为登录、查询、登出三步。import baostock as bs import pandas as pd # 登录 lg bs.login() print(lg.error_code, lg.error_msg) # 获取股票日线数据 rs bs.query_history_k_data_plus( sh.600000, # 浦发银行 date,code,open,high,low,close,preclose,volume,amount,adjustflag, start_date2020-01-01, end_date2024-12-31, frequencyd, adjustflag2 ) # 解析返回结果 data_list [] while (rs.error_code 0) and rs.next(): data_list.append(rs.get_row_data()) result pd.DataFrame(data_list, columnsrs.fields) # 登出 bs.logout()这段代码把浦发银行五年的日线数据一次性拉下来了放到DataFrame里。注意几个关键点login()和logout()是成对出现的每次运行脚本时登录一次即可不需要反复登录query_history_k_data_plus()是最核心的历史行情接口支持日线、周线、月线、5分钟、15分钟、30分钟、60分钟线返回对象是结果集迭代器需要用while rs.next()循环去取每一行数据最后用rs.fields作为列名构建DataFrame我以为很多初学者第一次用baostock时会困惑为什么返回的不是DataFrame而是一个结果集对象其实这是baostock刻意的设计——当数据量很大的时候一次性全部加载到内存会占用大量资源迭代器模式允许你逐条处理。但在实际使用中我都是把数据追加到list里最后统一转DataFrame这样操作最方便。2.3 参数选择的门道query_history_k_data_plus的参数看起来简单但每个参数都有讲究。code参数必须带交易所前缀sh.600000是上交所sz.000001是深交所。指数的话是sh.000001上证指数、sz.399001深证成指这种格式。如果前缀写错或者代码写错接口会返回错误码而不是直接抛异常。fields参数用逗号分隔的字符串决定返回哪些字段。常见字段有字段名含义date交易日期code证券代码open/high/low/close开高低收价格不复权原始价格preclose前收盘价volume成交量股amount成交额元adjustflag复权状态turn换手率tradestatus交易状态1为正常交易pctChg涨跌幅百分比peTTM/pbMRQ滚动市盈率/市净率adjustflag参数这是复权选择的关键取值1代表后复权2代表前复权3代表不复权。我一般默认用前复权做回测因为前复权价格能反映当前视角下的真实收益表现。注意即使设置了复权fields里的open/high/low/close返回的也是复权后的价格而preclose不一定同步复权所以计算涨跌幅的时候要小心最好直接用pctChg字段。3. 核心接口逐个拆解3.1 历史行情之外股票列表与交易状态做量化策略时光有个股日线数据还不够你还得知道有哪些股票在交易、停牌状态如何、上市日期是哪天。baostock提供了query_stock_basic接口来获取证券基本信息。rs bs.query_stock_basic(codesh.600000) # 或者不传code获取所有股票的基本信息 rs bs.query_stock_basic()返回字段包括code、code_name证券名称、ipoDate上市日期、outDate退市日期没退市则为空、type证券类型1为股票、status上市状态1为上市。我在做全市场选股的时候第一步永远是拉全量股票列表然后再去判断哪些股票处于可交易状态、上市满多长时间。还有一个很容易被忽视但实际很有用的接口query_trade_dates交易日历查询。rs bs.query_trade_dates(start_date2024-01-01, end_date2024-12-31)它会返回一年里所有的交易日和节假日。这种数据在做时间对齐、判断某一天是否交易日、计算持仓天数时非常有用。比如你想在策略里判断“如果今天是交易日且不是本周最后一个交易日就执行某操作”这个接口就能派上用场。3.2 财务数据与估值指标除了行情数据baostock在财务数据方面也覆盖得比较全。业绩报表、盈利能力、成长能力、偿债能力、运营能力这些维度都有接口而且都是季度级别的。比如query_profit_data可以拿到净资产收益率、总资产利润率等指标。rs bs.query_profit_data(codesh.600000, year2023, quarter4)返回的字段主要有roeAvg净资产收益率、npMargin销售净利率、gpMargin销售毛利率、netProfit净利润、epsTTM每股收益等。这些数据在做基本面因子选股时非常有用。我自己的习惯是每年年报季之后跑一遍全市场财务数据更新把因子值算好存下来回测的时候直接读文件。估值数据也一样query_valuation_data可以获取每日的市盈率、市净率、市销率等指标rs bs.query_valuation_data(codesh.600000, start_date2024-01-01, end_date2024-12-31)字段包括peTTM、pbMRQ、psTTM、pcfNcfTTM等。注意这里的估值数据是每日更新的而财务数据是季度更新的两者更新频率不同使用时要做好时间上的对齐。比如你想计算“最新市盈率分位数”应该用估值数据里的peTTM每天算而不是用季度财务数据里的每股收益和当日股价手工算因为后者在财报发布前存在严重的前视偏差会直接影响回测结果的真实性。4. 实战搭建一个可复用的数据落地模块4.1 需求与整体架构单独拉一两只股票的数据很简单但在真实项目里你需要的是“全市场、多周期、可增量更新”的数据仓库。我先说下我的需求获取所有A股股票的日线数据前复权数据保存到本地SQLite数据库方便后续回测和策略研究每天收盘后可以增量更新当天数据避免重复拉取减少接口请求次数整体架构分三层股票列表获取层、日线数据拉取层、数据库写入层。4.2 建表和写入逻辑我用的存储方案是SQLite单文件、零配置、Python内置支持回测场景完全够用。先创建一张表CREATE TABLE IF NOT EXISTS daily_kline ( code TEXT NOT NULL, date TEXT NOT NULL, open REAL, high REAL, low REAL, close REAL, volume REAL, amount REAL, pctChg REAL, turn REAL, peTTM REAL, pbMRQ REAL, PRIMARY KEY (code, date) );主键用code加date天然去重后续做增量更新甚至可以直接用INSERT OR REPLACE。接下来是核心拉取函数。我做了一个小封装import baostock as bs import pandas as pd import sqlite3 def fetch_daily_kline(code, start_date, end_date): 拉取单只股票的前复权日线数据 rs bs.query_history_k_data_plus( code, date,code,open,high,low,close,preclose,volume,amount,pctChg,turn,peTTM,pbMRQ, start_datestart_date, end_dateend_date, frequencyd, adjustflag2 ) rows [] while rs.error_code 0 and rs.next(): rows.append(rs.get_row_data()) if not rows: return pd.DataFrame() df pd.DataFrame(rows, columnsrs.fields) # 转数值类型 num_cols [open, high, low, close, preclose, volume, amount, pctChg, turn, peTTM, pbMRQ] df[num_cols] df[num_cols].apply(pd.to_numeric, errorscoerce) return df这里有一个我最初踩坑的地方baostock返回的所有字段都是字符串格式如果不转换后边做计算时会出各种奇怪的类型错误。所以在封装函数里一定要用pd.to_numeric统一转数值类型日期字段也最好统一成YYYY-MM-DD字符串方便排序和去重。4.3 全市场批量拉取与断点续传全市场日线批量拉取是个体力活但也是最容易出问题的地方。baostock对高频访问是有限制的如果短时间内发起太多次请求可能出现连接中断或返回空数据的情况。我的做法是拉取前先获取股票列表然后一只一只慢慢拉每只股票之间加一个短暂休眠同时记录进度防止中途崩了从头再来。conn sqlite3.connect(ashare.db) # 获取所有股票代码 rs_list bs.query_stock_basic() stocks [] while rs_list.error_code 0 and rs_list.next(): stocks.append(rs_list.get_row_data()) stock_df pd.DataFrame(stocks, columnsrs_list.fields) # 过滤已退市股票和基金只保留当前上市状态为1的股票 stock_df stock_df[(stock_df[type] 1) (stock_df[status] 1)] codes stock_df[code].tolist() # 检查数据库里已有数据只拉缺失的部分 for code in codes: cur conn.execute(fSELECT MAX(date) FROM daily_kline WHERE code{code}) row cur.fetchone() start row[0] if row and row[0] else 2006-01-01 # 如果最新日期已经是当天就跳过 if start today: continue df fetch_daily_kline(code, start, today) if not df.empty: df.to_sql(daily_kline, conn, if_existsappend, indexFalse) time.sleep(0.5)这一段代码虽然简单但已经具备了一个数据模块的基本功能自动发现新代码、断点续传、按需增量更新。我实际跑全市场5000多只股票的日线数据第一次全量拉取大概需要两三个小时后面每天增量更新只需要几分钟。4.4 数据质量校验数据落地之后不能直接拿来用必须先做校验。我总结了一套简单的校验方法行数检查对比数据库里的记录数和baostock接口返回的记录数是否一致日期连续性用交易日历数据检查有没有漏掉交易日极端值检查检查价格、涨跌幅有没有异常值比如涨跌幅超过21%的A股个股记录创业板、科创板涨跌幅限制不同科创板可以到20%主板10%北交所30%就需要人工复核复权一致性抽查某只股票看前复权价格和baostock里另一个周期的复权价格能否对应上我遇到过一种情况某只股票在数据库里某一天的成交量为0但价格波动正常这是正常的停牌或者临时停牌不能直接当作脏数据删掉需要在策略里特殊处理。更稳妥的做法是把volume 0的日期标记出来在回测时统一过滤或跳过。5. 常见问题与排查技巧实录5.1 问题速查表我在使用baostock过程中碰到过不少问题整理成了一张速查表希望能帮你少走弯路。问题现象原因解决方法login返回错误码10001代码中重复调用login或未先logout每次仅调用一次login脚本结束前调用logoutquery接口返回空数据code参数带错了前缀或代码不合法检查是sh还是sz开头指数代码必须用sh.000001格式返回的涨跌幅不正确使用了复权价格但preclose未同步复权直接使用pctChg字段不要手工用复权close/preclose计算单次查询只返回了2000多行baostock对单次查询返回条数有限制缩小日期范围分年或分公司拉取数据库里出现重复记录没有设置主键或使用to_sql追加模式建表时设置codedate联合主键或用INSERT OR REPLACE长时间运行时连接断开请求过于频繁被服务端限制每只股票间加sleep设置超时重试机制5.2 几个值得注意的细节先说单次返回条数限制的问题。baostock官方文档虽然没有明确写“单次返回不能超过N条”但根据我的实测一次性拉取太长时间的日线数据时返回结果会被截断往往只能拿到前2000多行。解决的办法很简单把时间段拆小按年拉取。比如拉2010到2024年的数据写一个循环逐年调用接口把DataFrame拼起来。再说一个容易被忽略的点query_history_k_data_plus返回的adjustflag字段在fields里可以选择如果你设置了复权这个字段会标成对应的数字不复权是3前复权是2后复权是1。但在做数据入库和因子计算时一定要在表里单独存一列标识复权方式因为后续可能你会同时用到复权和不复权两类数据比如回测用前复权价格但计算股息率时用不复权价格。5.3 增量更新的时间判断增量更新最容易踩的坑是“今天数据还没更新”。baostock的数据更新在收盘后一般需要一点时间如果你在下午3点半就急着跑增量脚本很可能拿到的是昨天的数据或者当天的空数据。我一般的做法是判断今天不是节假日并且当前时间晚于下午5点再执行增量更新。另外即使到了5点偶尔也会有数据没更新的情况这时候最好加一个重试机制比如数据拉回来是空的就等10分钟再试一次连续3次失败就发邮件提醒人工处理。5.4 多线程并发注意事项有人可能会想既然全市场批量拉取那么慢能不能用多线程加快速度我试过结论是不建议。baostock的服务端对并发有严格限制多个线程同时访问时很容易出现大量请求超时或返回空数据效率反而更低。我测试过单线程逐只拉取5000只股票约2小时4线程同时拉取大约需要1.5小时收益非常有限但出错率明显上升。如果你确实想快一点建议控制在2个线程以内并且做好失败重试。6. 从数据到策略的延伸想法数据拿到手之后能做的事情就多了。我当前的工作流是每天收盘后自动增量更新SQLite数据库然后基于这些数据计算技术指标、更新因子库再跑一遍信号扫描把符合条件的股票列表推送到提醒服务。但我更建议你把baostock当作一个基础数据层来用而不是终点。如果你的策略做到后期需要更精细的数据比如逐笔成交、盘口快照baostock满足不了那时候可以考虑接券商柜台数据或者购买商用数据源。不过在那之前用baostock做验证和起步已经是非常划算的选择。最后分享一个我自己的使用习惯每次从baostock拉数据时我都会顺手把当天拉取时间记录到一个meta表里。这样哪天的数据是齐的、哪天的数据缺了、最后一次全量更新是什么时候一眼就能看到。数据数量和质量的可追溯性往往比数据本身还重要。到这里baostock的核心使用方法和我的实战经验就说得差不多了。这个工具库虽然低调但确实给像我一样的Python量化爱好者省了大量时间和精力。希望这篇详解对你有实际帮助。
返回列表