从零搭建你的 A 股量化系统(十五):Pandas 量化必备 30 操作(上)——索引、时间序列、重采样 系列名《从零搭建你的 A 股量化系统》 专栏 S2 Python 工具链 第 3 篇 / 共 14 篇标签#量化投资 #Pandas #时间序列 #重采样 #Python #数据分析 #零基础 #实战 #A股 #散户NumPy 是发动机pandas 是装了发动机的整车。发动机再猛你也不能天天手搓数组去算某年某月某只股票涨了多少“把日线压成月线看趋势”。这些脏活累活pandas 一脚油门就帮你办了。这一篇开始我们用30 个高频操作把 pandas 在量化里最常用、也最容易被新手卡住的本事拆干净。因为一次塞 30 个太干我拆成上下两篇本篇上聚焦索引、时间序列、重采样三大块共15 个操作下篇S2-016补齐groupby / rolling / shift 与未来函数陷阱又是15 个。两篇合起来正好30 个量化必备操作。配套脚本src/s2_015_pandas_essentials.py全程用你仓库里现成的data/510300_daily.csv沪深300ETF 真实日线跑通。 你将学到为什么量化里索引Index是第一公民以及set_index/.loc/.iloc/ 布尔索引到底怎么选DatetimeIndex这套把日期当索引的思维怎么让你用df[2024]一行就榨出全年数据时间序列四大高频动作按年/月取数、时间段切片、date_range造日期、DateOffset 偏移重采样resample怎么把日线压成周线/月线/季线并顺手聚合成交量、算月收益新手最容易踩的6 个坑最阴的是df[2024]报错、resample最后一桶不全、read_csv把日期读成字符串附避雷代码。 前置知识已按第 1 篇装好ashare环境参考src/s2_013_detect_env.py确认并已pip install pandas看过 S2-014《NumPy 核心》最好——本篇默认你懂向量化pandas 底层就是它会最基础的 Python变量、列表、函数。pandas 的 API 名字很直白不懂统计学也能跟。操作地图篇块操作上本篇索引① 读 CSV 吃 BOM ② 设 DatetimeIndex ③ 选列 ④.loc标签取行 ⑤.iloc位置取行 ⑥ 布尔索引 索引对齐上本篇时间序列⑦ 按年/月取数 ⑧ 时间段切片 ⑨date_range⑩ 时间差 ⑪ DateOffset 偏移上本篇重采样⑫ 月线收盘 ⑬ 周线 OHLC ⑭ 季度聚合 ⑮ 月收益聚合下S2-016groupby/rolling/shift⑯~⑳groupby全家桶 ㉑rolling滚动 ㉒expanding㉓shift错位 ㉔pct_change㉕apply㉖ 金叉信号 ㉗ 横截面rank㉘merge㉙concat㉚ 缺失值处理 建议把这张表当书签。上篇学完索引/时间序列/重采样你就能独立做出按月看收益、按周看波动的最基础研究下篇的rolling/shift才是因子和回测的命门。一、为什么是 pandasNumPy 的ndarray只有行号没有名字。可量化数据天然带日期和字段名2024-09-30 的收盘价、开盘价……你用行号arr[1234]去取三天后就忘了 1234 是哪天。pandas 的DataFrame带标签的二维表行有索引通常是日期列有字段名open/high/low/close/volume。好处是——你不用记行号直接写df.loc[2024-09-30]两列相减会自动按日期对齐不用担心第一行对第一行的错配时间相关的活resample重采样、DateOffset偏移开箱即用。一句话NumPy 让你算得快pandas 让你找得准、读得懂。下面 15 个操作全是围绕索引和日期转。二、索引篇操作 ① ~ ⑥操作① 读 CSV先吃掉 BOM量化数据最常见的第一个坑A 股数据源AkShare、Tushare 导出的 CSV经常带BOM字节顺序标记。如果你不处理第一列名会变成\ufeffdate后面set_index(date)直接KeyError。importpandasaspd# encodingutf-8-sig 自动吃掉 BOM量化数据几乎必加dfpd.read_csv(data/510300_daily.csv,encodingutf-8-sig)print(list(df.columns))# [date, open, high, low, close, volume] ✓ 干净配套脚本跑出来标的: 510300 沪深300ETF 共 1,594 个交易日 区间: 2020-01-02 ~ 2026-07-31 列名: [open, high, low, close, volume] open high low close volume date 2020-01-02 1.673 1.693 1.672 1.683 627623614.0 2020-01-03 1.686 1.688 1.677 1.680 469673776.0 2020-01-06 1.675 1.692 1.663 1.674 666504783.0 索引类型: DatetimeIndex - datetime64[ns]操作② 把日期列设为索引DatetimeIndex——时间序列的基石read_csv读进来时date只是普通一列字符串。要把它变成会思考的日期索引两步先to_datetime解析再set_index。df[date]pd.to_datetime(df[date])# 字符串 - 真正的 Timestampdfdf.set_index(date).sort_index()# 设为索引并升序排好print(type(df.index).__name__,df.index.dtype)# DatetimeIndex datetime64[ns]⚠️ 忘记to_datetime就set_index索引会是字符串而不是日期——后面df[2024]按年取数会直接失效。这是坑⑤详见第五节。操作③ 选列单列 → Series多列 → DataFrameclose_seriesdf[close]# 单列 - Series一维oc_dfdf[[open,close]]# 多列 - DataFrame二维注意两层方括号df[close] 类型: Series 长度: 1594 df[[open,close]] 类型: DataFrame 形状: (1594, 2) 经验法则单层[]给列名结果可能是 Series 或 DataFrame双层[[]]永远返回 DataFrame。写因子时尽量用双层下游.pipe()、.assign()才不会因维度突变翻车。操作④ 按标签取行.loc[2024-09-30]索引是日期后直接用日期字符串当行号。比如取出著名的924 行情次日print(df.loc[2024-09-30])open 1.735000e00 high 1.821000e00 low 1.686000e00 close 1.818000e00 volume 6.370234e09操作⑤ 按位置取行.iloc[0]想要第几行而不是哪天用.iloc按整数位置从 0 开始print(df.iloc[0])# 最早一天 2020-01-02open 1.673000e00 high 1.693000e00 low 1.672000e00 close 1.683000e00 volume 6.276236e08.loc用标签日期、.iloc用位置第几行。混用.iloc配日期字符串会报错新手常栽在这。操作⑥ 布尔索引 索引自动对齐pandas 最香的特性想筛收盘价大于 2 元的所有交易日直接上布尔条件highdf[df[close]2.0]print(f收盘价 2.0 元:{len(high):,}/{len(df):,}天)收盘价 2.0 元的交易日: 432 / 1,594 天 (27.1%)更妙的是索引自动对齐两列做运算时pandas 按日期对齐而不是按行号。下面这行算当天开盘到收盘的涨跌幅完全不用管顺序对不对df[intraday_chg](df[close]-df[open])/df[open]open close intraday_chg date 2020-01-02 1.673 1.683 0.005977 2020-01-03 1.686 1.680 -0.003559 2020-01-06 1.675 1.674 -0.000597 这个按索引对齐是 pandas 防错的核心机制。等你下篇做多只股票拼接concat/merge时它会替你挡掉 90% 的对错行事故。三、时间序列篇操作 ⑦ ~ ⑪DatetimeIndex 设好后时间相关的活变得像说话一样自然。操作⑦ 按年 / 按月取数局部字符串索引y2024df.loc[2024]# 全年m2024_01df.loc[2024-01]# 某月print(f2024 全年交易日:{len(y2024)}天)print(f2024-01 交易日:{len(m2024_01)}天)2024 全年交易日: 242 天 2024-01 交易日: 22 天区间 2024-01-02~2024-01-31 2024-01 收盘: [1.504, 1.5, 1.489, 1.482, 1.465, 1.468, 1.462, 1.469, 1.465, 1.463, 1.472, 1.444, 1.46, 1.463, 1.437, 1.446, 1.465, 1.485, 1.483, 1.472, 1.451, 1.443]⚠️必须用.loc不能写df[2024]——后者会被当成取名为 2024 的列而KeyError。这是新手第一坑第五节展开。操作⑧ 时间段切片含两端要一段区间直接起:止两端都包含和 Python 普通切片不同这里不左闭右开q1df.loc[2024-01-01:2024-03-31]print(f2024 年一季度交易日:{len(q1)}天区间收盘{q1[close].min():.3f}~{q1[close].max():.3f})2024 年一季度交易日: 58 天 区间收盘: 1.431 ~ 1.581操作⑨pd.date_range造连续日期序列做回测框架、对齐多标的日历时常要自己造日期轴rpd.date_range(2026-01-01,2026-01-10,freqD)# 连续自然日rbpd.bdate_range(2026-01-01,2026-01-10)# 仅工作日print([d.date().isoformat()fordinr])print([d.date().isoformat()fordinrb])freqD 连续 10 天: [2026-01-01, 2026-01-02, 2026-01-03, 2026-01-04, 2026-01-05, 2026-01-06, 2026-01-07, 2026-01-08, 2026-01-09, 2026-01-10] freqB(工作日) 同区间: [2026-01-01, 2026-01-02, 2026-01-05, 2026-01-06, 2026-01-07, 2026-01-08, 2026-01-09]freq是灵魂D日、B工作日、W周、ME月末、QE季末、QE-DEC等。它和下面的resample共用同一套频率字符串。操作⑩ 时间差自然日 vs 交易日两个日期相减得到Timedelta.days拿到天数first,lastdf.index.min(),df.index.max()print(f自然日跨度:{(last-first).days}天实际交易日:{len(df)}天)首末交易日: 2020-01-02 - 2026-07-31 自然日跨度: 2402 天 实际交易日: 1594 天 - 约 242 个交易日/年 记住A 股一年约 242 个交易日不是 365。所有年化都要乘 242 或 252别用 365——这是 S1-010 讲年化波动率的底层约定。操作⑪ DateOffset 偏移取N 个自然日/交易日前targetpd.Timestamp(2024-09-30)prev_5target-pd.DateOffset(days5)# 往前 5 个自然日last_bdf.index[df.indextarget][-1]# 不晚于 target 的最近交易日print(往前5自然日:,prev_5.date(),| 最近交易日:,last_b.date())2024-09-30 往前 5 个自然日: 2024-09-25 不晚于 2024-09-30 的最近交易日: 2024-09-30DateOffset(days5)是日历偏移若要往前 5 个交易日要用BDay(5)需from pandas.tseries.offsets import BDay。做N 日前的信号时务必分清否则会混入周末/节假日。四、重采样篇操作 ⑫ ~ ⑮resample 把高频数据按时间频率降采样成低频并指定每个桶怎么聚合。它是把日线变成周/月/季线的唯一正解。操作⑫ 月线收盘价resample(ME).last()monthly_closedf[close].resample(ME).last()# ME 月末(Month End)print(monthly_close.tail(6).round(3))date 2026-02-28 2.079 2026-03-31 1.982 2026-04-30 2.116 2026-05-31 2.153 2026-06-30 2.188 2026-07-31 2.053 Freq: ME⚠️ 老教程写resample(M)新版 pandas≥2.2会报警告改用ME月末/MS月初。本文全程用新写法。操作⑬ 周线 OHLCresample(W).ohlc()一根 K 线要开高低收ohlc()一行给齐weekly_ohlcdf[close].resample(W).ohlc()print(weekly_ohlc.tail(4).round(3))open high low close date 2026-07-12 2.136 2.150 2.106 2.118 2026-07-19 2.086 2.121 2.029 2.029 2026-07-26 2.051 2.102 2.051 2.070 2026-08-02 2.090 2.090 2.035 2.053操作⑭ 季度聚合多指标resample(QE).agg({...})不同列用不同聚合函数传字典即可quarterlydf.resample(QE).agg({close:last,volume:sum})quarterly[volume](quarterly[volume]/1e8).round(2)# 成交量转「亿股」quarterly.columns[季末收盘,季度成交量(亿股)]print(quarterly.tail(4))季末收盘 季度成交量(亿股) date 2025-12-31 2.044 459.41 2026-03-31 1.982 943.85 2026-06-30 2.188 630.04 2026-09-30 2.053 336.84 注意最后一行2026-09-30样本只到 7-31所以这个季度其实只含 7 月数据是个不完整桶。真实研究里常.iloc[:-1]砍掉最后一桶或显式判断桶内天数。坑②会讲。操作⑮ 由日收益聚合月收益resample(ME).last().pct_change()把日线收盘价先压成月线再.pct_change()算月涨跌幅——这就是 S5 因子、S8 组合里月度收益表的源头monthly_retdf[close].resample(ME).last().pct_change().dropna()up(monthly_ret0).sum()print(f上涨月:{up}下跌月:{len(monthly_ret)-up})print((monthly_ret.tail(6)*100).round(2).astype(str).add(%))月度收益率序列共 78 个月去掉首月 上涨月: 42 下跌月: 36 date 2026-02-28 0.24% 2026-03-31 -4.67% 2026-04-30 6.76% 2026-05-31 1.75% 2026-06-30 1.63% 2026-07-31 -6.17% 顺便看一眼近 6 个月 4 涨 2 跌但单月波动能到 ±6%——这就是为什么要做组合、要控回撤。就这么一行resample你已经摸到了收益分布的门槛。五、新手最常踩的坑避雷#现象原因解决办法坑①df[2024]报KeyError: 2024按年/月取数是索引能力必须走.loc写df.loc[2024]别写df[2024]坑②resample最后一个月/季数据缺一半resample 会补齐到周期边界末桶只有部分数据展示前.iloc[:-1]砍掉末桶或过滤桶内天数坑③df.close取不到列或取到奇怪东西列名含空格/与 DataFrame 方法重名如df.count时属性访问失效永远用df[列名]双层写法别用df.列名坑④合并两张表后日期对不上、出现大量NaT一张是naive无时区、一张是tz-aware有时区统一用tz_localize(None)去掉时区或都加同一时区坑⑤df[2024]取数失效、排序像字符串read_csv把日期读成字符串set_index前忘了to_datetime先df[date] pd.to_datetime(df[date])再set_index坑⑥resample(M)跑出FutureWarning旧频率别名M在新版 pandas 已废弃改用ME月末/MS月初/QE季末坑① 现场演示最阴的一个90% 新手第一天就撞dfpd.read_csv(data/510300_daily.csv,encodingutf-8-sig).set_index(date)df.loc[2024]# ✅ 正确242 天df[2024]# ❌ KeyError: 2024它去找叫 2024 的列了坑⑤ 现场演示为什么必须to_datetimerawpd.read_csv(data/510300_daily.csv,encodingutf-8-sig).set_index(date)print(raw.index.dtype)# object字符串raw.loc[2024]# ❌ 字符串索引不支持按年切片cleanraw.reset_index()clean[date]pd.to_datetime(clean[date])cleanclean.set_index(date)print(clean.index.dtype)# datetime64[ns] ✅clean.loc[2024]# ✅ 242 天保命三连① 读 CSV 永远encodingutf-8-sig②set_index前永远先to_datetime③ 按年/月取数永远.loc不是[]。把这三条刻进肌肉记忆pandas 入门的坑你直接跳过一半。六、本篇小结 下篇预告小结本篇上用真实 A 股日线跑通了15 个量化必备操作全在索引 时间序列 重采样三块索引让你找得准——set_index把日期变成索引.loc按日期取、.iloc按位置取、布尔索引按条件筛且两列运算按索引自动对齐天然防错时间序列让你切得动——df.loc[2024]一行榨全年、起:止含端切片、date_range造日历、DateOffset做偏移重采样让你看得远——resample(ME/W/QE)把日线压成月/周/季线agg({...})多指标同聚合.pct_change()直接出月度收益。记住三条保命准则读 CSV 加utf-8-sig、设索引前先to_datetime、按年取数用.loc。下一篇预告专栏 S2 的第四篇《Pandas 量化必备 30 操作下groupby、rolling、shift 与未来函数陷阱》。你会学到怎么按年/股票分组算指标、rolling滚动窗口算均线、shift错位算收益率——以及为什么shift用错一步你的回测会偷看未来直接变造假。这 15 个操作⑯~⑳直接决定你后面因子和回测的真假。准备好ashare环境我们下篇见。免责声明本文所有内容仅用于量化投资技术教学与知识分享文中涉及的代码示例、跑分数据与实证结果均不构成任何投资建议或个股推荐。投资有风险入市需谨慎实际交易前请务必用自己的真实数据充分回测并充分了解相关风险。