第三章 库存模型基础:库存周期的经济学原理、库存的领先/滞后关系、库存数据的预处理
做商品量化这些年,我越来越觉得库存数据就像商品的「脉搏」。你想想看,价格涨跌背后,其实都是库存的累积与消耗在驱动。今天这一章,咱们就聊聊库存模型的基础。说白了,就是搞清楚库存到底怎么影响价格,以及我们怎么处理那些脏兮兮的原始数据。
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 本章知识体系
下面这张图,是我自己梳理的库存模型基础框架。你可以把它当成一个检查清单,做项目时对照着来。
嗯,这一章的内容就到这里。库存模型看起来简单,但真正做好预处理、搞清楚领先滞后关系,需要大量的实践和试错。我个人建议你从一种商品开始,比如螺纹钢,把整个流程跑通,再扩展到其他品种。
公众号:蓝海资料掘金营,微信deep3321