第三章 库存模型基础:库存周期的经济学原理、库存的领先/滞后关系、库存数据的预处理

做商品量化这些年,我越来越觉得库存数据就像商品的「脉搏」。你想想看,价格涨跌背后,其实都是库存的累积与消耗在驱动。今天这一章,咱们就聊聊库存模型的基础。说白了,就是搞清楚库存到底怎么影响价格,以及我们怎么处理那些脏兮兮的原始数据。

3.1 库存周期的经济学原理

库存周期,也叫基钦周期。经济学家发现,企业的库存行为大概每3-4年就会循环一次。我个人习惯把这个周期拆成四个阶段:

  • 主动补库:需求旺盛,企业觉得未来能卖更多,主动增加库存。价格往往上涨。
  • 被动补库:需求开始下滑,但生产惯性还在,库存被动积压。价格开始承压。
  • 主动去库:企业意识到需求不行了,开始减产、降价清库存。价格下跌最猛。
  • 被动去库:需求回暖,但企业还没反应过来,库存被快速消耗。价格触底反弹。

我在项目中遇到过好几次这样的情况:很多人只看价格涨跌就追进去,结果正好撞上被动补库的拐点,被套得死死的。其实,库存数据早就给出了信号。

核心观点:库存是价格的「滞后指标」,但库存的变化率往往是价格的「领先指标」。

为什么会这样?因为企业调整生产需要时间。当需求突然增加时,库存会先下降,然后企业才慢慢增产。这个时间差,就是我们做量化交易的机会窗口。

3.2 库存的领先/滞后关系

嗯,这里要注意一个关键点:不同商品的库存与价格关系是不一样的。

商品类型 库存与价格关系 典型例子
工业品 库存领先价格约1-2个月 螺纹钢、铜
农产品 库存与价格同步或滞后 大豆、玉米
能源品 库存领先价格约2-4周 原油、动力煤

我曾经犯过一个错误:拿铜的库存模型直接套用到豆粕上,结果回测一塌糊涂。后来才明白,农产品有生长周期,库存数据受天气影响极大,跟工业品的逻辑完全不同。

怎么判断领先滞后关系?我个人习惯用互相关函数(CCF)来算。简单说,就是把库存序列和价格序列错开几个时间点,看哪个相关系数最大。

import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import ccf

# 假设 inventory 和 price 是两个 pandas Series
# 计算库存对价格的领先滞后关系
def find_lag(inventory, price, max_lag=20):
    ccf_values = ccf(inventory, price, adjusted=False)
    # 取前 max_lag 个滞后值
    lags = np.arange(1, max_lag+1)
    best_lag = lags[np.argmax(np.abs(ccf_values[1:max_lag+1]))]
    return best_lag

# 我一般会画个热力图,直观看到不同滞后期的相关性

小技巧:别只看一个滞后期。我建议你算一个「领先区间」,比如滞后1-3个月都显著相关,那说明这个关系是稳健的。

3.3 库存数据的预处理

说到数据预处理,这才是真正考验功力的地方。原始库存数据,说白了就是一团乱麻。我见过太多人直接拿原始数据跑模型,结果全是噪音。

3.3.1 去噪

库存数据里有很多「假信号」。比如某周因为港口罢工,库存突然暴增,但下周就恢复正常了。这种异常值如果不处理,模型会误以为库存周期发生了变化。

我常用的去噪方法有两种:

  • 移动平均:简单粗暴,但有效。窗口期我一般选4周或12周,看商品特性。
  • HP滤波:把趋势和周期分离出来。我个人习惯用λ=1600(季度数据)或λ=14400(月度数据)。
from scipy.signal import savgol_filter
from statsmodels.tsa.filters.hp_filter import hpfilter

# 方法1:Savitzky-Golay 滤波(保留更多细节)
def denoise_sg(series, window=5, order=2):
    return savgol_filter(series, window, order)

# 方法2:HP滤波(分离趋势和周期)
cycle, trend = hpfilter(inventory, lamb=1600)
# 我一般用 cycle 作为去噪后的库存变化信号

避坑指南:我曾经用移动平均去噪,窗口选太大,结果把真正的拐点也磨平了。后来我改用自适应窗口——波动大的时候窗口小,波动小的时候窗口大。效果好了很多。

3.3.2 插值

库存数据经常有缺失。比如节假日不公布数据,或者某些机构突然停止更新。直接扔掉这些缺失值?太浪费了。我建议用插值法补全。

常用的插值方法:

  • 线性插值:适合短期缺失(1-2个数据点)
  • 前向填充:适合库存变化缓慢的商品(比如铜)
  • 多项式插值:适合有明显趋势的数据,但别用太高阶,容易过拟合
# pandas 自带的插值就很够用
df['inventory'].interpolate(method='linear', inplace=True)
# 如果缺失太多,我会先用前向填充,再用线性插值补剩下的
df['inventory'].fillna(method='ffill', inplace=True)
df['inventory'].interpolate(method='linear', inplace=True)

3.3.3 对齐

不同数据源的库存数据,频率可能不一样。有的每周更新,有的每月更新。对齐的时候,我建议降频而不是升频。说白了,把高频数据转成低频,比把低频数据强行拆成高频要靠谱得多。

举个例子:你有周度库存数据和日度价格数据。别把库存插值成日度,那会引入太多人为假设。正确的做法是把价格也降频到周度,然后对齐。

# 把日度价格降频到周度,取每周五的收盘价
price_weekly = price.resample('W-FRI').last()
# 然后跟周度库存对齐
aligned_data = pd.concat([inventory_weekly, price_weekly], axis=1).dropna()

我的经验:数据预处理花的时间,至少占整个项目的一半。别急着跑模型,先把数据洗干净。你想想看,垃圾进垃圾出,模型再花哨也没用。

3.4 本章知识体系

下面这张图,是我自己梳理的库存模型基础框架。你可以把它当成一个检查清单,做项目时对照着来。

库存模型基础框架 库存周期经济学 领先/滞后关系 数据预处理 四个阶段 主动补库 → 被动补库 主动去库 → 被动去库 库存变化率是领先指标 分析方法 互相关函数 (CCF) 分商品类型判断 工业品 vs 农产品 vs 能源 三步走 去噪:移动平均 / HP滤波 插值:线性 / 前向填充 对齐:降频对齐 干净的、对齐的库存数据 可用于建模和回测

嗯,这一章的内容就到这里。库存模型看起来简单,但真正做好预处理、搞清楚领先滞后关系,需要大量的实践和试错。我个人建议你从一种商品开始,比如螺纹钢,把整个流程跑通,再扩展到其他品种。


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