
本文含 4494 字,25 图表截屏
建议阅读 30 分钟
0
引言
本文是「硬核蹭热点系列」系列的第一篇
2020 年 4 月 20 日美国原油期货价格暴跌约 300%,收于每桶 -37.63 美元。各大财经号都开始分析表达自己的看法。看法无对错,但有利益方总是挑着对自己有利的观点看,比如多头受害者就疯狂转发【金融监管研究院】的文章,质疑为什么不帮他们平仓止损;某行员工们就疯狂转发【秦小明】的文章,表示产品结算前操作没问题;空头受益者啥也不转发,觉得这一切很美丽。
看法无对错,挑着自己想看的而去争个对错毫无意义,事实才有真假,但是内部人员又不会说,咋咋乎乎的全是没有 skin-in-the-game 的外部人员。
我能做的只是从量化金融和 Python 实现的角度来看看负油价事件带来的影响吧。在期货价格为负的第二天,芝加哥商品交易所(CME)就发了一则通知,截屏如下(注意高亮字段):

要看全文通告可点击下面的原文链接。
CME 从 2020 年 4 月 21 日起将以原油期货为标的的期权定价和估值模型从布莱尔斯科尔斯(Black-Scholes, BS)模型换成巴舍利耶(Bachelier)模型。
很明显,CME 换模型就是要考虑“标的价格可以为负”的情景了,BS 模型下的价格永远为正,而 Bachelier 模型下的价格可正可负。
本帖内容结构如下:
由于本帖蹭得是原油热点,我就按商品为资产类型来描述 BS 和 Bachelier 模型下的相关术语。

1
Black-Scholes 模型
原生资产 (商品现货价格) 的随机微分方程(SDE)如下:

其中
对于消费型商品(非投资型商品如黄金或白银),你持有现货会给你带来便利(convenience yield),但也会有存储费用(cost of carry),而上面的 q 是净便利,就是便利和费用的差。
BS 的推导见得太多了,因此简叙一下推导步骤:
首先根据伊藤公式解 S(T)

再求积分得到期权定价公式,看涨看跌期权用 ω 来区分,ω = 1 时为看涨,ω = -1 时为看跌:

通常我更喜欢把公式写成 F(t, T) 形式,ln(F(t, T)/K) 衡量着价内外状态(moneyness),而

才是 S 从 t 到 T 的真正的波动率,σ 并不是,它只是波动率系数。
路径模拟
在用代码来实现 BS 模型前,我们先来模拟 10 条不同路径的 S(T),按照以下公式,先模拟 lnS(T),再用 exp(lnS(T)) 返回到 S(T)

代码如下:
(S0, r, q, T, sigma) = (1, 0.02, 0.01, 1, 0.5)
(Nsim, Nt) = (10, 1000)
t = np.linspace(0,T,Nt)
dt = np.diff(t)
z = np.random.randn(Nsim, Nt-1)
A = (r-q-0.5*(sigma)**2)*dt + sigma*(z*np.sqrt(dt))
lnS0 = np.tile(np.log(S0),(Nsim,1))
lnS = lnS0 + np.cumsum(A, axis=1)
lnS = np.hstack( (lnS0, lnS) )
S = np.exp(lnS)用 cufflinks 来画图:
label = [ x + ' ' + str(y) for x, y in zip(['path']*Nsim, np.arange(1,Nsim+1)) ]
df = pd.DataFrame(S.T, index=t, columns=label)
df.iplot( xTitle='t',
yTitle='S',
title='Black-Scholes 模型价格路径模拟',
theme='ggplot' )
要点:S(T) 没有负值
期权定价
实现 BS 模型的代码如下:

测试一下 blackscholes() 得到一个看涨期权价格为 7.5459,记住这是用 0.1 = 10% 的波动率计算出来的结果。
(S0, K, r, q, T, sigma, omega) = (100, 95, 0.01, 0, 1, 0.1, 1)
V_BS = blackscholes(S0, K, r, q, T, sigma, omega)
V_BS7.545870893948695反解波动率
给定市场上期权的交易价格,根据 BS 模型的公式可以反解出波动率,即波动率等于多少用 BS 公式可以刚好计算出期权价格。
实现起来非常简单,用 scipy.optimize 里面的 fsolve 即可,代码如下:

用上面计算出的结果 V_BS,看是否能反解出 0.1 的波动率呢。没有问题。
get_BS_IV( V_BS, S0, K, r, q, T, omega )0.09999999999999983上面这些 Quant 都玩熟了,直到 Bachelier 模型出现,我们还是可以按照上面的思路来继续玩。
2
Bachelier 模型
原生资产 (商品现货价格) 的随机微分方程(SDE)如下:

其中
上面大多参数含义和 BS 模型中的一样, 只有 σ 不再是瞬时波动率,而是波动率了,注意 SDE 的扩散项(diffusion term)只有 σ,没有 S(t) 了,那么这里 σ 指的是一个波动率的绝对数目,而不是比率。但是漂移项(drift term)还带 S(t),这点非常重要,因为 r 和 q 还是一个比率的概念,而不是一个具体数目的概念。
Bachelier 的推导虽然很早就有了,但可能大家没怎么关注,因此详叙一下推导步骤:
首先通用线性 SDE 的解 S(T)

想看推导的就看篮框里的内容,不想看的跳过篮框。


再用等距性质 (Isometry property) 得到

接下来再求积分得到期权定价公式,同样看涨看跌期权用 ω 来区分,ω = 1 时为看涨,ω = -1 时为看跌:

这时把 F(t, T) - K 衡量着价内外状态(moneyness),同理而 v 才是 S 从 t 到 T 的真正的波动率,σ 并不是,它只是波动率系数。
想看上面公式推导的就看篮框里的内容,不想看的跳过篮框。



硬核推导完了,接下来就来看看 Bachelier 下的路径模拟和期权定价。
路径模拟

代码如下:
(S0, r, q, T, sigma) = (1, 0.02, 0.01, 10, 0.5)
(Nsim, Nt) = (10, 1000)
t = np.linspace(0,T,Nt)
dt = np.diff(t)
z = np.random.randn(Nsim, Nt-1)
A = np.exp((r-q)*t)
if r == q:
B = sigma*(z*np.sqrt(dt)) / A[1:]
else:
B = sigma*(z*np.sqrt( (np.exp(2*(r-q)*dt)-1) / (2*(r-q)) )) / A[1:]
discS0 = np.tile( S0/A[0], (Nsim,1) )
discS = discS0 + np.cumsum(B, axis=1)
discS = np.hstack( (discS0, discS) )
S = discS * A用 cufflinks 来画图:
label = [ x + ' ' + str(y) for x, y in zip(['path']*Nsim, np.arange(1,Nsim+1)) ]
df = pd.DataFrame(S.T, index=t, columns=label)
df.iplot( xTitle='t',
yTitle='S',
title='Bachelier 模型价格路径模拟',
theme='ggplot' )
要点:S(T) 有负值
其实要模拟出负值,S 要小,σ 要大。这不正是 4 月 20 日晚上发生的情况吗?其他情况下模拟出负价格的概率也不大。
期权定价
实现 Bachelier 模型的代码如下:

用 blackscholes() 模型同样的参数(虽然不合理,等下讲)带入 bachelier() 得到一个看涨期权价格为 5.9453。
(S0, K, r, q, T, sigma, omega) = (100, 95, 0.01, 0, 1, 0.1, 1)V_BL = bachelier(S0, K, r, q, T, sigma, omega)V_BL
5.945265793829019不合理的点在于 BS 和 Bachelier 中的波动率含义不一样:
因此输入波动率为 0.1*S0 才能得到和 BS 匹配的结果,来试试
V_BL = bachelier(S0, K, r, q, T, sigma*S0, omega)V_BL
7.630422479463851果然和 BS 的家国 7.5459 很接近。那其实我们在反解 Bachelier 模型下的波动率时,可以近似用
BS 波动率 / S0
接下来我们来看看 Bachelier 是否能计算当 S0 为负时的期权价格,来假设模拟 20 日当天的情况,S0 = -37。期权刚发行时一般都按照当时的远期价格,假设 K = 20,来运行代码:
(S0, K) = (-37, 20)
V_BL = bachelier(S0, K, r, q, T, sigma*np.abs(S0), omega)
V_BL1.246987875662463e-54价格几乎为零,合理!
要点
Always Know Your Shit
When Playing Your Toys
反解波动率
给定市场上期权的交易价格,根据 Bachelier 模型的公式可以反解出波动率,即波动率等于多少用 Bachelier 公式可以刚好计算出期权价格。
实现起来非常简单,也用 scipy.optimize 里面的 fsolve 即可,代码如下:

现在我们用 V_BS 价格来反解出 Bachelier 模型的波动率。
(S0, K, r, q, T, omega) = (100, 95, 0.01, 0, 1, 1)
BS_sigma = 0.1
BS_price = blackscholes(S0, K, r, q, T, BS_sigma, omega)
BL_price = BS_price
BL_sigma = get_BL_IV( BL_price, S0, K, r, q, T, omega )
BL_sigma9.744178790421072用和近似解得到的 0.1*100 = 10 相比接近,合理。3
总结
BS 和 Bachelier 最大的不同就是
Bachelier 模型可以计算负期货价格下的期权价值,但我们上面一直在讲现货价格的 SDE。其实期货价格的 SDE 更简单,没有漂移项只有扩散项,上面所有公式还是能用,把 r 和 q 设定为零就行。
现在我在思考一个问题,用 shifted-lognormal 模型(即 S(t) - c 服从 lognormal 分布)可不可以?
Stay Tuned!
