量化工具:Python实现基于库存的基差交易信号生成

说实话,很多做基差交易的朋友,都卡在「信号生成」这一步。理论都懂——库存高,基差弱;库存低,基差强。但真要把这个逻辑写成代码,跑出可执行的交易信号,又是另一回事了。

我个人习惯,是把库存数据当作「核心因子」,然后用Python把它量化成具体的买卖信号。今天我就带你走一遍这个流程。嗯,代码不多,但每一步都有坑。

1. 核心逻辑:库存与基差的「反向联动」

先理清思路。基差 = 现货价格 - 期货价格。库存高的时候,现货往往供过于求,价格承压,基差走弱(甚至变成负的)。库存低的时候,现货紧缺,基差走强。

所以我们的信号生成逻辑很简单:

  • 库存处于历史低位 → 预期基差走强 → 做多基差(买现货,卖期货)
  • 库存处于历史高位 → 预期基差走弱 → 做空基差(卖现货,买期货)
  • 库存处于中性区间 → 观望,不产生信号

你想想看,这个逻辑听起来简单,但实际跑起来,阈值怎么设?数据怎么标准化?我一开始也踩过坑。

关键点: 库存数据必须做「历史分位数」处理,不能直接用绝对值。因为不同品种、不同年份的库存水平差异巨大。

2. 数据准备:你需要什么?

要生成信号,至少需要两类数据:

数据类别 字段 频率
库存数据 日期、品种、库存量 周度/日度
基差数据 日期、品种、基差值 日度

我在项目中遇到过一个问题:库存数据经常有缺失,尤其是节假日前后。如果你直接用空值去算分位数,结果会偏得离谱。我的做法是——先做前向填充,再算滚动分位数。

3. Python代码实现:信号生成引擎

下面这段代码,是我自己一直在用的一个简化版。它把库存数据转成「库存分位数」,然后根据分位数生成交易信号。

import pandas as pd
import numpy as np

def generate_inventory_signal(inventory_series, lookback=252, low_threshold=0.2, high_threshold=0.8):
    """
    基于库存分位数生成基差交易信号
    
    参数:
    - inventory_series: 库存时间序列 (pandas Series)
    - lookback: 滚动窗口长度,默认252个交易日
    - low_threshold: 低位阈值,库存分位数低于此值做多
    - high_threshold: 高位阈值,库存分位数高于此值做空
    
    返回:
    - signal: 1=做多基差, -1=做空基差, 0=观望
    """
    # 计算滚动分位数
    rolling_rank = inventory_series.rolling(window=lookback, min_periods=100).apply(
        lambda x: pd.Series(x).rank(pct=True).iloc[-1]
    )
    
    # 生成信号
    signal = pd.Series(0, index=inventory_series.index)
    signal[rolling_rank <= low_threshold] = 1    # 库存低位,做多基差
    signal[rolling_rank >= high_threshold] = -1  # 库存高位,做空基差
    
    return signal

# 使用示例
# inventory_data = pd.read_csv('inventory.csv', index_col='date', parse_dates=True)
# signal = generate_inventory_signal(inventory_data['inventory'])
我的习惯: lookback 参数我一般设252(一年交易日),但如果你做的是短线交易,可以缩到60或90。阈值0.2和0.8是经验值,你可以根据品种调整。

4. 信号过滤:别让噪音干扰你

直接跑上面的代码,你会发现信号切换非常频繁。今天做多,明天就变做空了。这在实际交易中根本没法执行。

我曾经吃过这个亏。2019年做螺纹钢,信号一天变三次,手续费都亏麻了。后来我加了一个「信号持久化」的过滤机制:

def filter_signal(signal, min_hold_days=5):
    """
    信号过滤:信号至少保持 min_hold_days 天不变
    """
    filtered = signal.copy()
    last_signal = 0
    hold_count = 0
    
    for i in range(len(signal)):
        if signal.iloc[i] != last_signal:
            if hold_count >= min_hold_days:
                last_signal = signal.iloc[i]
                hold_count = 0
            else:
                # 信号持续时间不够,保持原信号
                filtered.iloc[i] = last_signal
                hold_count += 1
        else:
            hold_count += 1
    
    return filtered

说白了,这个过滤就是让信号「钝化」一点。你想想看,库存变化是缓慢的,基差变化也是缓慢的,信号没必要那么灵敏。

5. 可视化:看一眼就知道信号质量

光有数字信号不够,我习惯画一张图,把库存分位数、基差、信号三条线叠在一起看。这样一眼就能判断信号是否合理。

import matplotlib.pyplot as plt

def plot_signal(inventory_pct, basis, signal):
    fig, ax1 = plt.subplots(figsize=(12, 6))
    
    ax1.plot(inventory_pct.index, inventory_pct, label='库存分位数', color='blue', alpha=0.7)
    ax1.axhline(y=0.2, color='gray', linestyle='--', alpha=0.5)
    ax1.axhline(y=0.8, color='gray', linestyle='--', alpha=0.5)
    ax1.set_ylabel('库存分位数')
    
    ax2 = ax1.twinx()
    ax2.plot(basis.index, basis, label='基差', color='orange', alpha=0.7)
    ax2.set_ylabel('基差')
    
    # 标记信号
    for i in range(len(signal)):
        if signal.iloc[i] == 1:
            ax1.axvline(x=signal.index[i], color='green', alpha=0.1)
        elif signal.iloc[i] == -1:
            ax1.axvline(x=signal.index[i], color='red', alpha=0.1)
    
    plt.title('库存分位数 vs 基差 vs 交易信号')
    plt.show()
注意: 可视化只是辅助工具。别因为图好看就盲目信任信号。我见过太多人,看到信号和历史回测曲线漂亮,就直接上实盘,结果亏得很惨。

6. 核心知识体系:一张图说清楚

下面这张SVG图,把整个信号生成的流程串起来了。从数据输入到信号输出,每一步都离不开。

基于库存的基差交易信号生成流程 数据输入 库存数据 + 基差数据 数据处理 前向填充 + 滚动分位数 信号生成 阈值判断 + 信号赋值 信号过滤 持久化过滤 + 去噪 可视化验证 三线图对比 + 信号标记 输出信号 1=做多 / -1=做空 / 0=观望 核心参数:lookback(滚动窗口) | low_threshold(低位阈值) | high_threshold(高位阈值) 过滤参数:min_hold_days(最小持仓天数)

7. 避坑指南:我踩过的三个大坑

  1. 库存数据口径不一致——我曾经同时用了两个数据源的库存,一个算的是「社会库存」,另一个算的是「厂库」,结果信号完全相反。后来我统一只用交易所公布的仓单数据。
  2. 阈值设得太死——0.2和0.8不是万能药。有些品种波动大,0.2可能一年都触发不了几次。我建议你先跑一遍历史数据,看看分位数的分布,再动态调整。
  3. 忽略基差的绝对值——库存分位数低,但基差已经很高了,这时候做多基差可能追高。我后来加了一个「基差绝对水平」的过滤,基差处于历史高位时,即使库存低也不做多。
总结一下: 基于库存的基差交易信号,核心就是「库存分位数 + 阈值判断 + 信号过滤」。代码不难,难的是参数调优和风控。别想着一步到位,先跑模拟盘跑三个月再说。

好了,这一章的内容就到这里。代码你可以直接拿去用,但记得根据自己的品种调整参数。有什么问题,欢迎交流。

公众号:蓝海资料掘金营,微信deep3321