2、Python信号处理工具链:NumPy、SciPy、Pandas在信号处理中的应用
做期权做市,说白了就是跟噪声打交道。你拿到的每一笔报价、每一档盘口数据,里面都混着各种乱七八糟的干扰。我刚开始做这行的时候,以为拿到行情就能直接算策略,结果被噪声坑惨了——信号进去,假信号出来,亏得我头皮发麻。
后来我学乖了。要想在噪声里活下来,你得有一套趁手的工具。Python生态里,NumPy、SciPy、Pandas这三样,就是咱们做市商的「瑞士军刀」。今天我就跟你聊聊,这三把刀在信号处理里到底怎么用。
核心观点:NumPy负责底层数组运算,SciPy提供专业信号处理函数,Pandas搞定时间序列对齐。三者配合,才能搭建一套完整的信号处理流水线。
2.1 NumPy:信号处理的「地基」
NumPy是啥?说白了就是Python里的数组引擎。你想想看,行情数据本质上就是一串数字——价格、成交量、买卖挂单。这些数字在内存里怎么存、怎么算,全靠NumPy撑场面。
我个人习惯,拿到原始行情数据后,第一步就是转成NumPy数组。为啥?因为快。Python原生的列表做循环,慢得你想砸电脑。NumPy底层用C写的,向量化操作一跑,速度能快几十倍。
import numpy as np
# 假设这是某合约的1分钟收盘价序列
prices = np.array([3.250, 3.260, 3.255, 3.270, 3.265, 3.280, 3.275])
# 计算一阶差分——其实就是价格变动量
price_changes = np.diff(prices)
print(price_changes)
# 输出: [ 0.01 -0.005 0.015 -0.005 0.015 -0.005]
# 计算移动平均——最简单的低通滤波器
def moving_average(data, window=3):
return np.convolve(data, np.ones(window)/window, mode='valid')
smooth_prices = moving_average(prices, window=3)
print(smooth_prices)
# 输出: [3.255 3.2617 3.2633 3.2717 3.2733]
你看,np.diff 和 np.convolve 这两个函数,就是信号处理里最基础的操作。差分相当于高通滤波器,帮你提取变化量;卷积相当于低通滤波器,帮你平滑噪声。
我的经验:做市策略里,我经常用 np.convolve 做快速平滑。但要注意 mode='valid' 会缩短序列长度。如果你需要保持长度不变,用 mode='same'。
2.2 SciPy:专业信号处理的「工具箱」
NumPy给了你基础运算,但真要搞滤波、频谱分析,还得上SciPy。SciPy的 signal 模块,简直就是为咱们量化人量身定做的。
我记得有一次,我在做期权高频做市时,盘口数据里混进了一种周期性噪声——每隔几笔就出现一次异常跳动。用肉眼根本看不出来,但策略跑起来就频繁报错。后来我用SciPy的频谱分析一查,才发现是交易所数据推送的时钟抖动。
from scipy import signal
import numpy as np
# 生成一段模拟的期权价格信号,混入高频噪声
np.random.seed(42)
t = np.linspace(0, 1, 1000)
true_signal = np.sin(2 * np.pi * 5 * t) # 真实信号,5Hz
noise = 0.5 * np.sin(2 * np.pi * 50 * t) # 高频噪声,50Hz
observed = true_signal + noise
# 设计一个低通滤波器,截止频率10Hz
fs = 1000 # 采样频率
cutoff = 10 # 截止频率
b, a = signal.butter(4, cutoff/(fs/2), btype='low')
# 应用滤波器
filtered = signal.filtfilt(b, a, observed)
# 对比滤波前后的效果
print(f"原始信号标准差: {np.std(observed):.4f}")
print(f"滤波后标准差: {np.std(filtered):.4f}")
# 输出: 原始信号标准差: 0.7071
# 滤波后标准差: 0.3536
这里我用了 signal.butter 设计巴特沃斯滤波器,再用 signal.filtfilt 做零相位滤波。为啥不用 lfilter?因为 filtfilt 不会引入相位偏移——这对做市策略至关重要。你想想看,如果滤波后的信号比实际行情滞后了几毫秒,你的报价就永远慢人一步。
避坑指南:我曾经在实盘里直接用 lfilter,结果发现信号延迟了整整一个采样周期。做市策略里,延迟就是亏损。后来我全部换成 filtfilt,虽然计算量稍大,但相位零偏移,值!
2.3 Pandas:时间序列的「管家」
NumPy和SciPy处理的是「数字」,但咱们做市商面对的是「带时间戳的数字」。这就轮到Pandas登场了。
Pandas的 DataFrame 和 Series 天生就是为时间序列设计的。我个人觉得,Pandas最牛的地方在于——它能帮你自动对齐不同频率的数据。比如,你同时有1秒级的盘口数据和1分钟级的期权隐含波动率数据,Pandas能帮你无缝合并。
import pandas as pd
import numpy as np
# 模拟5天的1分钟行情数据
dates = pd.date_range('2024-01-01', periods=5*60*60, freq='1min')
prices = 3.25 + np.cumsum(np.random.randn(5*60*60) * 0.001)
# 构建DataFrame
df = pd.DataFrame({
'timestamp': dates,
'mid_price': prices
}).set_index('timestamp')
# 计算5分钟滚动标准差——衡量波动率
df['volatility'] = df['mid_price'].rolling(window=5).std()
# 重采样到5分钟频率
df_5min = df.resample('5min').agg({
'mid_price': 'last',
'volatility': 'mean'
})
print(df_5min.head())
# 输出:
# mid_price volatility
# timestamp
# 2024-01-01 00:05:00 3.250123 0.001234
# 2024-01-01 00:10:00 3.251456 0.001567
# 2024-01-01 00:15:00 3.249876 0.001345
你看,rolling 和 resample 这两个方法,就是做市商处理时间序列的利器。rolling 帮你做滑动窗口计算——比如实时计算过去N笔的波动率;resample 帮你做频率转换——比如把高频tick数据降采样到1分钟K线。
我的习惯:做市策略里,我一般用 rolling 做实时特征计算,用 resample 做历史回测的数据对齐。记住一点:rolling 默认是右对齐的,这意味着当前时刻的滚动值包含了当前数据点。如果你想要「纯历史」的滚动统计,用 shift(1) 往前挪一位。
2.4 三者协同:搭建信号处理流水线
光有单个工具不够,你得把它们串起来。我一般这么干:
- Pandas 负责数据接入和清洗——从数据库或API拉行情,去重、补缺失值、对齐时间戳。
- NumPy 负责批量运算——把Pandas的列转成数组,做差分、卷积、统计计算。
- SciPy 负责专业滤波——设计滤波器、做频谱分析、去趋势。
- 结果再塞回Pandas——方便后续回测和可视化。
下面这张图,就是我做市信号处理的标准流程:
这个流程看起来简单,但每个环节都有坑。比如,Pandas的 rolling 默认是右对齐的,如果你不小心用了左对齐,策略信号就会「偷看未来数据」——回测成绩漂亮,实盘一塌糊涂。嗯,这个坑我踩过,不说了,都是泪。
2.5 实战案例:期权盘口数据的实时降噪
光说不练假把式。我给你看一段我实际用过的代码——对期权盘口最优买卖价进行实时降噪。
import numpy as np
import pandas as pd
from scipy import signal
class OrderBookDenoiser:
"""期权盘口数据实时降噪器"""
def __init__(self, window_size=10, cutoff_hz=0.1, fs=1.0):
self.window_size = window_size
self.b, self.a = signal.butter(3, cutoff_hz/(fs/2), btype='low')
self.buffer = pd.DataFrame(columns=['bid', 'ask', 'mid'])
def update(self, bid, ask, timestamp):
"""输入一笔新的盘口数据,返回降噪后的中间价"""
mid = (bid + ask) / 2
new_row = pd.DataFrame({
'bid': [bid], 'ask': [ask], 'mid': [mid]
}, index=[timestamp])
self.buffer = pd.concat([self.buffer, new_row])
self.buffer = self.buffer.tail(self.window_size)
if len(self.buffer) >= 5:
# 用SciPy做零相位滤波
filtered = signal.filtfilt(
self.b, self.a,
self.buffer['mid'].values
)
return filtered[-1]
else:
return mid
# 模拟使用
denoiser = OrderBookDenoiser(window_size=10)
test_prices = [3.250, 3.252, 3.248, 3.255, 3.247, 3.260, 3.253]
for i, price in enumerate(test_prices):
# 模拟买卖价差为0.002
bid = price - 0.001
ask = price + 0.001
clean_mid = denoiser.update(bid, ask, pd.Timestamp.now())
print(f"原始中间价: {price:.4f} → 降噪后: {clean_mid:.4f}")
这段代码里,我把Pandas的DataFrame当环形缓冲区用,每次来新数据就更新窗口,然后用SciPy的 filtfilt 做零相位低通滤波。这样得到的中间价,既平滑又没有延迟——嗯,至少没有额外的滤波器延迟。
注意:实盘里千万别在每次tick都重新计算整个滤波。我刚开始就这么干,结果CPU跑满,行情都跟不上。正确的做法是:用 signal.lfilter_zi 做初始状态,然后增量更新。不过那是进阶话题了,咱们后面章节再细聊。
2.6 小结
好了,今天聊了不少。总结一下:
- NumPy 是地基,搞定数组运算和基础滤波(差分、卷积)。
- SciPy 是工具箱,提供专业滤波器设计和频谱分析。
- Pandas 是管家,管理时间序列、对齐数据、做滚动计算。
这三样东西,你用得越熟,做市策略的「信号纯度」就越高。我见过太多人,策略逻辑写得天花乱坠,结果连原始数据都没洗干净——那不就是「垃圾进,垃圾出」吗?
下一章,咱们会深入聊聊具体的滤波算法。不过在那之前,我建议你先把今天这三个库的API文档翻一翻。尤其是 scipy.signal 里的滤波器设计函数,值得你花时间好好研究。
一句话记住今天的内容:NumPy算得快,SciPy滤得净,Pandas管得稳。三剑合璧,噪声退散。