第三章:Python量化基础:NumPy、Pandas、Matplotlib在量化中的应用
做量化这行,说白了就是跟数据打交道。你想想看,每天面对成千上万只股票、分钟级甚至 tick 级的数据流,如果没有趁手的工具,光靠 Python 原生的列表和循环,那效率简直不敢想。
我个人习惯把 NumPy、Pandas、Matplotlib 称为「量化三件套」。这三样东西,几乎贯穿了我从策略研发到实盘上线的每一个环节。今天咱们就聊聊,这三把刀在量化里到底怎么用。
3.1 NumPy:高性能数值计算的基石
NumPy 的核心是 ndarray,也就是 N 维数组。它比 Python 原生的 list 快多少?我做过测试,同样是对 1000 万个数做加法,NumPy 比 for 循环快了将近 50 倍。为什么会这样?因为 NumPy 底层是 C 语言实现的,而且利用了 CPU 的向量化指令。
核心要点:在量化中,我们几乎所有的价格、成交量、因子数据,最终都会转换成 NumPy 数组来处理。
3.1.1 创建数组的几种方式
import numpy as np
# 从列表创建
prices = np.array([10.5, 10.8, 10.3, 11.0, 10.9])
# 创建全零数组(比如初始化仓位)
positions = np.zeros(100)
# 创建等差数列(比如模拟时间轴)
time_axis = np.linspace(0, 1, 1000)
# 随机数(蒙特卡洛模拟常用)
np.random.seed(42) # 固定种子,保证可复现
simulated_returns = np.random.randn(1000) * 0.02 # 模拟日收益率
嗯,这里要注意:np.random.seed() 这个函数,我在做回测时经常用它。如果不固定种子,每次跑出来的结果都不一样,你根本没法判断策略改进到底有没有效果。
3.1.2 向量化运算——量化提速的关键
我曾经接手过一个策略,原版代码里用 for 循环计算移动平均线,处理 5000 只股票的历史数据,跑一次要 40 分钟。我改成向量化运算后,时间直接压缩到 2 分钟以内。
# 向量化计算收益率
prices = np.array([100, 102, 101, 105, 108])
returns = (prices[1:] - prices[:-1]) / prices[:-1]
# 向量化条件筛选
mask = returns > 0.01 # 筛选涨幅超过1%的交易日
positive_returns = returns[mask]
# 矩阵运算(多因子模型常用)
factors = np.random.randn(100, 5) # 100个交易日,5个因子
weights = np.array([0.2, 0.3, 0.1, 0.25, 0.15])
factor_score = factors @ weights # 矩阵乘法
我的经验:写量化代码时,只要看到 for 循环,就要条件反射地问自己:能不能用向量化代替?这往往是性能瓶颈所在。
3.2 Pandas:时间序列分析的瑞士军刀
NumPy 处理的是「数」,Pandas 处理的是「带标签的数据」。在量化里,这个标签通常是时间戳和股票代码。我个人觉得,Pandas 的 DataFrame 是量化工程师最常用的数据结构,没有之一。
3.2.1 DataFrame 的基本操作
import pandas as pd
# 创建时间序列数据
dates = pd.date_range('2024-01-01', periods=5, freq='D')
data = {
'open': [10.0, 10.2, 10.1, 10.5, 10.3],
'high': [10.3, 10.4, 10.3, 10.7, 10.5],
'low': [9.9, 10.0, 9.8, 10.2, 10.1],
'close': [10.1, 10.3, 10.0, 10.6, 10.4],
'volume': [10000, 12000, 9000, 15000, 11000]
}
df = pd.DataFrame(data, index=dates)
3.2.2 量化中常用的 Pandas 操作
我记得有一次做因子分析,需要计算每只股票过去 20 天的滚动波动率。如果用循环写,代码又臭又长。用 Pandas 的 rolling 方法,一行搞定。
# 滚动计算(移动平均、波动率等)
df['ma5'] = df['close'].rolling(window=5).mean()
df['volatility'] = df['close'].pct_change().rolling(window=20).std()
# 移位操作(计算滞后因子)
df['return_1d'] = df['close'].pct_change()
df['return_1d_lag'] = df['return_1d'].shift(1) # 前一天的收益率
# 分组聚合(多股票处理)
# 假设 df 包含多只股票,列名为 'stock_code'
# df.groupby('stock_code')['close'].apply(lambda x: x.pct_change())
# 重采样(将分钟数据转为日线)
# df.resample('D').agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'})
避坑指南:我曾经在实盘回测中犯过一个错误——用 shift() 时没注意索引对齐。结果策略信号里混入了未来数据,回测曲线漂亮得不像话,实盘却一塌糊涂。记住:shift(1) 是把当前行的数据往后挪一行,千万别搞反了。
3.2.3 处理缺失值
真实数据里,停牌、涨跌停、数据缺失都是家常便饭。Pandas 提供了灵活的处理方式。
# 检查缺失值
df.isnull().sum()
# 向前填充(用上一个有效值填充)
df.fillna(method='ffill', inplace=True)
# 删除缺失值过多的列
df.dropna(thresh=len(df) * 0.8, axis=1, inplace=True) # 保留80%以上非空数据的列
3.3 Matplotlib:让数据说话
做量化的人,眼睛对数字的敏感度远不如对图形的敏感度。一张好的图表,能让你瞬间发现数据中的异常和规律。我个人习惯用 Matplotlib 做快速可视化,虽然它不如 Plotly 那样交互性强,但胜在轻量和灵活。
3.3.1 基础图表绘制
import matplotlib.pyplot as plt
# 设置中文字体(避免乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 绘制价格走势
plt.figure(figsize=(12, 6))
plt.plot(df.index, df['close'], label='收盘价', linewidth=2)
plt.plot(df.index, df['ma5'], label='5日均线', linestyle='--')
plt.title('股票价格走势')
plt.xlabel('日期')
plt.ylabel('价格')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
3.3.2 量化专用图表
嗯,这里要重点说说 K 线图。虽然 Matplotlib 没有内置的 K 线绘制函数,但我们可以用 mplfinance 库(基于 Matplotlib 的扩展)来画。
# 安装:pip install mplfinance
import mplfinance as mpf
# 绘制K线图
mpf.plot(df, type='candle', volume=True, style='charles',
title='日K线图', ylabel='价格', ylabel_lower='成交量')
我的习惯:在策略研发阶段,我通常会画三张图:净值曲线、最大回撤区间、因子收益分布。这三张图能快速告诉我策略的「健康状况」。
3.4 三件套的协同工作流
在实际项目中,这三个库通常是配合使用的。我画了一张图,展示它们之间的关系:
3.5 实战案例:计算并可视化一个简单的动量因子
光说不练假把式。咱们用真实数据走一遍完整的流程。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 1. 模拟生成数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=252, freq='D')
# 模拟股价随机游走
price = 100 * np.exp(np.cumsum(np.random.randn(252) * 0.02))
df = pd.DataFrame({'close': price}, index=dates)
# 2. 计算动量因子(过去20天收益率)
df['momentum'] = df['close'].pct_change(20)
# 3. 生成交易信号:动量 > 0 做多,否则做空
df['signal'] = np.where(df['momentum'] > 0, 1, -1)
# 4. 计算策略收益
df['daily_return'] = df['close'].pct_change()
df['strategy_return'] = df['signal'].shift(1) * df['daily_return']
df['cumulative_return'] = (1 + df['strategy_return']).cumprod()
# 5. 可视化
plt.figure(figsize=(14, 8))
plt.subplot(3, 1, 1)
plt.plot(df.index, df['close'], label='价格', color='blue')
plt.title('价格走势')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(3, 1, 2)
plt.plot(df.index, df['momentum'], label='动量因子', color='orange')
plt.axhline(y=0, color='red', linestyle='--', alpha=0.5)
plt.title('动量因子')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(3, 1, 3)
plt.plot(df.index, df['cumulative_return'], label='策略净值', color='green')
plt.title('策略累计收益')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 输出绩效指标
total_return = df['cumulative_return'].iloc[-1] - 1
sharpe = np.sqrt(252) * df['strategy_return'].mean() / df['strategy_return'].std()
print(f"总收益率: {total_return:.2%}")
print(f"夏普比率: {sharpe:.2f}")
关键点:注意代码中 df['signal'].shift(1) 这一步。如果不 shift,你就是在用当天的信号交易当天的收益——这属于未来函数,实盘里根本做不到。我见过太多新手在这里栽跟头。
3.6 避坑指南与个人经验
做了这么多年量化,有些坑是拿真金白银换来的教训:
- 索引对齐问题:Pandas 的运算默认按索引对齐。如果你有两个 DataFrame,一个索引是日期,另一个是整数索引,直接做运算会得到 NaN。我建议养成好习惯:操作前先检查索引是否一致。
- 内存管理:处理全市场 5000 只股票的分钟数据,DataFrame 可能会占用几十 G 内存。我的做法是:用
dtype指定数据类型,比如价格用float32而不是float64,能省一半内存。 - 可视化性能:画几万根 K 线时,Matplotlib 会卡成 PPT。我一般先对数据做降采样,比如只画最近 500 根 K 线,或者用
plot(kind='line')代替 K 线图。
曾经踩过的坑:有一次我在回测中用了 pd.concat() 合并多个股票的数据,没注意索引重复的问题。结果回测结果异常得好,后来发现是因为数据被重复计算了。从那以后,我每次合并数据都会加一句 df = df[~df.index.duplicated()] 去重。
好了,关于量化三件套的基础应用,今天就聊到这里。这些工具就像厨师手里的刀——刀工好不好,直接决定了菜品的上限。多练、多用、多踩坑,慢慢就熟练了。