从零搭建你的 A 股量化系统(三):Python 量化速成——用 pandas 读取、处理、可视化数据
系列名:《从零搭建你的 A 股量化系统》| 第 3 篇 / 共 100+ 篇
标签:#量化投资 #Python #pandas #matplotlib #A股 #散户 #零基础 #实战
上篇《半小时装好环境》里,我们把 Python + conda + Git 这套地基打好、自检通过了。但说实话——你那时候大概率还是"照着敲、能跑、但不知道为啥"。
量化 90% 的日常工作,不是在写算法,而是在"摆弄数据"——把乱七八糟的行情读进来、洗干净、算指标、画出来看。而干这件事,几乎全靠一个库:pandas。
这一篇,我带你补齐"看得懂、改得动"代码所需的 Python 底子,重点拿下 pandas 这个量化最核心的库,最后亲手用 pandas 把双均线策略跑成一个能看绩效的回测雏形——不仅画出收益曲线,还算出年化收益、最大回撤、交易次数。
⏱️ 时间预期:跟着做约 40~60 分钟(前半段偏"看懂",后半段偏"动手")。代码都不长,关键是边读边在编辑器里敲一遍。
一、这篇我们要搞定什么
先给你一张"本篇地图",免得看着看着迷路:
| 模块 | 你会学到 | 产出 |
|---|---|---|
| Python 速成 | 只学"为 pandas 铺路"的那点语法 | 一段看得懂的 Python |
| pandas 核心 | Series、DataFrame、读 CSV、列运算 | 能把一份行情表玩出花 |
| 可视化 | 折线图、双子图、标注买卖点 | 一张看得见的图 |
| 实战 | 用 pandas 跑通双均线 + 算出绩效 | src/s1_003_dual_ma.py + 收益曲线图 |
整篇的代码会串成下面这条流水线,记住它,后面每一步都对得上:
📌 接上环境篇:本篇跑出来的脚本,就落在第 2 篇建好的项目树
src/里——它是你量化系统src/strategies/中第一个策略文件的雏形。"建系统即写博客"的同频推进,从这里正式开始。
二、Python 速成:只学"为 pandas 铺路"的那点
很多人被"学编程"吓退,以为要先啃完一本砖头书。其实做量化,你只需要 Python 的"骨架级"知识。而且——别把它们当成孤立语法,它们每一个都是为后面写策略函数准备的。
| 知识点 | 量化里干嘛用 | 马上会用到 |
|---|---|---|
列表 list |
装一批股票代码 | ["600519","000858"] |
字典 dict |
装策略参数 | {"fast":10,"slow":30} |
函数 def |
把策略逻辑打包复用 | def ma_cross(prices): ... |
| 列表推导式 | 一行批量处理 | 批量生成标签、过滤 |
if / 布尔 |
条件分支 | 金叉才买入 |
来看一段"为 pandas 铺路"的例子——它看着是纯 Python,其实已经在召唤 pandas 了:
# 量化里最常用的一小撮 Python(重点看注释)
stocks = ["600519", "000858", "300750"] # 列表:装一批股票代码
params = {"fast": 10, "slow": 30} # 字典:装策略参数
fast, slow = params["fast"], params["slow"] # 从字典里取参数
def ma_cross(prices, f, s):
"""快线在慢线上方返回 1(持仓),否则 0 —— 这就是双均线的灵魂"""
ma_f = prices.rolling(f).mean() # 短期均线(prices 是 pandas 的 Series)
ma_s = prices.rolling(s).mean() # 长期均线
return (ma_f > ma_s).astype(int)
# 列表推导式:一行批量生成 "股票 + 参数" 的标签
labels = [f"{code} 用 {params['fast']}/{params['slow']} 双均线" for code in stocks]
print(labels)
# 输出:['600519 用 10/30 双均线', '000858 用 10/30 双均线', '300750 用 10/30 双均线']
💡看到不认识的语法,先问"它想表达什么逻辑",而不是"它为什么这么写"。逻辑通了,语法查一下就懂。量化代码里几乎不出现高阶语法(元类、装饰器、异步),放心。
现在你发现没?ma_cross 这个函数,把"快线在慢线上方就持仓"的双均线逻辑,用最清楚的方式写了出来。这正是量化策略函数的标准形态——输入行情,输出 0/1 信号,后面要做的一切都建立在它之上。
三、pandas:量化最该先学的库
如果说 Python 是发动机,那 pandas 就是量化这台车的底盘——它把"表格数据"变成了一种超级好操作的形态。A 股行情、财务报表、资金流,本质都是一张张表,而 pandas 就是为"表"而生的。
3.1 两个核心概念:Series 和 DataFrame
| 概念 | 类比 Excel | 说明 |
|---|---|---|
| Series | 一列 | 带索引的一维数据,比如"收盘价这一列" |
| DataFrame | 一整张表 | 多列组合,比如"日线行情表" |
记住一句话:DataFrame = 很多个 Series 并排站在一起。
3.2 第一步:把数据读进来(读 CSV)
真实工作里,数据往往是从文件或接口拿到的。我们先造一份"教学用"日线数据存成 CSV,再用 pandas 读回来——这正好演示"数据 → 处理"的完整链路。
import pandas as pd
import numpy as np
# 造一份 250 个交易日的模拟日线(教学用,非真实行情)
np.random.seed(7)
n = 250
dates = pd.date_range("2023-01-02", periods=n, freq="B") # B = 工作日
close = 100 + np.cumsum(np.random.randn(n)) * 1.2
close = np.maximum(close, 20.0) # 防止出现负价格
df = pd.DataFrame({
"date": dates,
"open": np.round(close + np.random.randn(n) * 0.3, 2),
"high": np.round(close + np.abs(np.random.randn(n)) * 0.8, 2),
"low": np.round(close - np.abs(np.random.randn(n)) * 0.8, 2),
"close": np.round(close, 2),
"volume": np.random.randint(1_000_000, 5_000_000, n).astype(float),
})
df.to_csv("data/sample_prices.csv", index=False) # 存成 CSV
# 用 pandas 读回来
price = pd.read_csv("data/sample_prices.csv", parse_dates=["date"])
print(price.head(3)) # 看前 3 行
print("行数:", len(price), " 列:", list(price.columns))
几个立刻要记牢的点:
pd.read_csv("路径")是你会用最频繁的函数,没有之一;parse_dates=["date"]让"日期"列被当成真正的日期(而不是字符串),后面按时间排序、画图才不会乱;price.head(3)快速预览前几行,调试数据时天天用。
3.3 索引与切片:精准"夹"出你要的数据
closes = price["close"] # 取某一列(返回 Series)
sub = price[["date", "close", "volume"]] # 取多列
first_10 = price.iloc[:10] # 按位置取前 10 行
up_days = price[price["close"] > price["open"]] # 按条件过滤:当天收涨的所有行
march = price[price["date"] >= "2023-03-01"] # 按日期范围切(date 是日期类型才能这么比)
⚠️ 新手雷区:
price["close"]和price.close都能取列,但后者在列名带空格/中文时容易翻车,一律用中括号[]最稳。
3.4 列运算 & 移动窗口:指标就是这么算出来的
量化里绝大部分指标,本质都是"对一列数做运算"。pandas 让你整列一起算,不用写 for 循环:
price["ret"] = price["close"].pct_change().fillna(0) # 每日收益率
price["range"] = price["high"] - price["low"] # 当日振幅
price["ma10"] = price["close"].rolling(10).mean() # 最近 10 天平均价 = MA10
price["ma30"] = price["close"].rolling(30).mean() # 最近 30 天平均价 = MA30
price["signal_yesterday"] = price["signal"].shift(1) # 整列上下挪一天
rolling(window).mean() 就是"滑动窗口求平均",对应均线;shift(1) 是把数据整体往后挪一天——这个技巧后面实战会保命,先有个印象。
3.5 合并与分组(先混个脸熟)
等你后面要处理"多支股票"或"行情+财务"时,会用到这两个:
merged = pd.merge(price, fundamentals, on="code", how="left") # 横向拼两表(类似 VLOOKUP)
grouped = price.groupby("code")["ret"].mean() # 按某列分组聚合
这一篇先用不上,但先记住它们存在,后面遇到"要把几张表合起来"时直接翻回来用。
四、matplotlib:让数据"看得见"
代码跑出一堆数字,人脑是读不动的。画图,是量化的"第二语言"。你只要会三招:
- 折线图
plt.plot(x, y):画价格、画均线; - 双子图
plt.subplots(2, 1, sharex=True):上面价格、下面收益,共享 X 轴; - 打标记
plt.scatter(..., marker="^"):用红色箭头标"买入"、绿色标"卖出",信号一眼看清。
具体怎么写,直接看下一节的完整脚本——那里才是它真正落地的地方。
五、实战:把双均线从"玩具"升级成回测雏形
下面这段是本篇的完整可运行脚本——它把前面所有知识点串成一条线:造数据 → 存 CSV → 读回 → 算均线 → 出信号 → 算收益 → 算绩效 → 画图。存成 src/s1_003_dual_ma.py,在 (ashare) 环境里 python src/s1_003_dual_ma.py 就能跑。
⚠️ 关于"真实"二字,先把话说清楚:脚本里的行情是
np.random模拟生成的,本篇目标是把 pandas 工作流和回测框架跑顺,不是给你真实收益预测。真数据下一篇(S1-004)用 AkShare 抓取。到时你只需把"造数据"那 6 行,换成下面这一行(示意):import akshare as ak df = ak.stock_zh_a_hist(symbol="600519", period="daily", adjust="qfq") # 真实日线其余回测代码一行都不用改——这就是先把框架练熟的价值。
# src/s1_003_dual_ma.py
# 教学示例:用 pandas 跑通双均线策略并算出绩效(数据为模拟,非真实行情,不构成投资建议)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams["font.sans-serif"] = ["SimHei"] # Windows 自带黑体
matplotlib.rcParams["axes.unicode_minus"] = False # 防止负号变成方块
# ---------- 1) 造一份教学用日线数据,存成 CSV ----------
np.random.seed(7)
n = 250
dates = pd.date_range("2023-01-02", periods=n, freq="B")
close = 100 + np.cumsum(np.random.randn(n)) * 1.2
close = np.maximum(close, 20.0)
df = pd.DataFrame({
"date": dates,
"open": np.round(close + np.random.randn(n) * 0.3, 2),
"high": np.round(close + np.abs(np.random.randn(n)) * 0.8, 2),
"low": np.round(close - np.abs(np.random.randn(n)) * 0.8, 2),
"close": np.round(close, 2),
"volume": np.random.randint(1_000_000, 5_000_000, n).astype(float),
})
df.to_csv("data/sample_prices.csv", index=False)
# ---------- 2) 用 pandas 读回来 ----------
price = pd.read_csv("data/sample_prices.csv", parse_dates=["date"])
print(price.head(3))
print("行数:", len(price), " 列:", list(price.columns))
# ---------- 3) 双均线策略(全程向量化,无 for 循环) ----------
price = price.sort_values("date").reset_index(drop=True)
price["ma10"] = price["close"].rolling(10).mean() # 短期均线
price["ma30"] = price["close"].rolling(30).mean() # 长期均线
price["signal"] = np.where(price["ma10"] > price["ma30"], 1, 0) # 金叉持仓/死叉空仓
price["position"] = price["signal"].shift(1).fillna(0) # 用"昨天"信号决定"今天"持仓
# ---------- 4) 收益与绩效(这才是"回测"该算的东西) ----------
price["ret"] = price["close"].pct_change().fillna(0)
price["buyhold"] = (1 + price["ret"]).cumprod() - 1 # 买入持有累计收益
price["strat_ret"] = price["ret"] * price["position"] # 策略每日收益
price["strat"] = (1 + price["strat_ret"]).cumprod() - 1 # 策略累计收益
# —— 绩效评价(只用 pandas/numpy,零基础也能看懂)——
years = n / 252
annual = (1 + price["strat"].iloc[-1]) ** (1 / years) - 1 # 年化收益
cum = (1 + price["strat_ret"]).cumprod()
max_dd = ((cum - cum.cummax()) / cum.cummax()).min() # 最大回撤(负数)
trades = price["signal"].diff().abs().sum() / 2 # 交易次数(一金一死=一次往返)
print(f"买入持有累计收益 : {price['buyhold'].iloc[-1]*100:6.1f}%")
print(f"双均线策略累计收益: {price['strat'].iloc[-1]*100:6.1f}%")
print(f"策略年化收益 : {annual*100:6.1f}%")
print(f"策略最大回撤 : {max_dd*100:6.1f}%")
print(f"策略交易次数 : {int(trades)} 次")
# ---------- 5) 画图 ----------
cross_up = price["signal"].diff() == 1 # 金叉当天
cross_dn = price["signal"].diff() == -1 # 死叉当天
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(11, 7), sharex=True)
ax1.plot(price["date"], price["close"], label="收盘价", color="#1f77b4", lw=1)
ax1.plot(price["date"], price["ma10"], label="MA10", color="#ff7f0e", lw=1)
ax1.plot(price["date"], price["ma30"], label="MA30", color="#2ca02c", lw=1)
ax1.scatter(price["date"][cross_up], price["close"][cross_up],
marker="^", color="#d62728", s=90, zorder=5, label="买入(金叉)")
ax1.scatter(price["date"][cross_dn], price["close"][cross_dn],
marker="v", color="#2ca02c", s=90, zorder=5, label="卖出(死叉)")
ax1.set_title("双均线策略信号(教学示例,非真实数据)")
ax1.legend()
ax2.plot(price["date"], price["buyhold"], label="买入持有", color="gray", lw=1)
ax2.plot(price["date"], price["strat"], label="双均线策略", color="#e4572e", lw=1.5)
ax2.set_title("累计收益对比")
ax2.legend()
plt.tight_layout()
plt.savefig("data/dual_ma_result.png", dpi=120)
print("图表已保存为 data/dual_ma_result.png ✅")
# plt.show() # 想看弹窗就把这行取消注释
跑完你会在终端看到几行绩效数字(你的数字会因随机种子/库版本略有不同,这是正常的),项目里多一张 data/dual_ma_result.png:
- 上图:价格 + 两条均线 + 红色"买入"箭头(金叉)、绿色"卖出"箭头(死叉);
- 下图:灰色"买入持有"曲线 vs 橙红"双均线策略"曲线,谁强谁弱一目了然;
- 终端:累计收益、年化、最大回撤、交易次数——这就是一个回测该报的四项基本功。

🎯 关键认知:这段脚本是一个完整的、可复用的分析流程:数据从文件进、指标在表里算、信号和绩效都落到了 DataFrame 的每一行。你以后换任何策略、换任何数据,这套骨架都不用变,只改中间那几行。
六、新手最常卡的 6 个坑
| 现象 | 原因 | 解决办法 |
|---|---|---|
ModuleNotFoundError: No module named 'pandas' |
忘了激活 ashare 环境 |
先 conda activate ashare,看到前缀再跑 |
read_csv 读出来中文乱码 |
文件编码不是 UTF-8 | 加 encoding="utf-8-sig" 或 encoding="gbk" |
改了 DataFrame 报 SettingWithCopyWarning |
用了"链式索引" df[mask]["col"]= |
改用 df.loc[mask, "col"] = ... 或先 .copy() |
几千行用 for 循环算,慢得像蜗牛 |
没用向量化 | 用 rolling / shift / 列运算 整列一起算 |
| 策略收益"好得不真实" | 前视偏差:用当天信号算当天收益 | 信号必须 shift(1),用"昨天信号"决定"今天持仓" |
| 图片保存是空白 / 不弹窗 | 后端或调用顺序问题 | 先 plt.savefig() 再 plt.show();无界面环境用 Agg 后端 |
⚠️ 第六个坑里的前视偏差是量化新手最容易犯的致命错误,没有之一。铁律:回测时,今天能不能持仓,只能由"昨天及之前"的信息决定。
shift(1)就是这道防火墙。
⚠️ 另一个坑(本篇专属):脚本里的收益、回撤是用模拟数据算的,只能用来验证"流程跑通了",绝不能当成某只股票的真实表现或未来预测。真数据 + 真结论,留给 S1-004。
七、本篇小结 & 下篇预告
小结:这一篇我们补齐了量化最实用的 Python 骨架(只学为 pandas 铺路的那点),拿下了 pandas 三板斧——读 CSV、做列运算与滑动窗口、按条件过滤,再用 matplotlib 把结果画成图。最后落地的那段双均线脚本,已经是一个正规回测的迷你版:数据进表 → 算指标 → 出信号 → 算收益 → 算绩效(年化/回撤/次数)→ 画图验证。
你现在手里有了三样东西:能跑的环境(第 2 篇)+ 看得懂的 Python/pandas(本篇)+ 一个能出绩效图的策略雏形(src/s1_003_dual_ma.py)。离"真金白银的分析",只差最后一块拼图——真实数据。
下一篇预告:《从零搭建你的 A 股量化系统(四):拿到真金白银的数据——用 AkShare 抓取 A 股真实行情》。下篇我会带你用国内免费库 AkShare,把本篇脚本里"模拟数据"换成真实的 A 股日 K 线,让你的双均线策略第一次跑在真数据上,并算出它在真实历史上的表现。准备好你的编辑器,我们下篇见。
免责声明:本文所有内容仅用于量化投资技术教学与知识分享。文中的行情数据为模拟生成,示例代码仅用于演示 pandas 工作流与回测框架,不构成任何投资建议或个股推荐,也不代表任何真实收益。投资有风险,入市需谨慎;实际交易前请务必用自己的真实数据充分回测,并充分了解相关风险。
更多推荐



所有评论(0)