带暗门的期权
障碍期权是带开关的看涨或看跌期权。敲出期权在价格触及障碍时失效;敲入期权只有在触及时才生效。障碍可以在起始价之上(向上)或之下(向下)。由于买方放弃了一部分收益,障碍期权比普通期权便宜,这也是它流行的原因,在结构化产品中尤其如此。和亚式期权一样,它依赖整条路径,自然的工具就是模拟。
import numpy as np
paths = np.array([[100.0, 105.0, 112.0, 108.0], # stays below 115
[100.0, 98.0, 103.0, 110.0], # stays below 115
[100.0, 120.0, 95.0, 90.0]]) # touches 115 on day 1, then falls
hit_up = paths.max(axis=1) >= 115.0 # the maximum of each path against the barrier
print(hit_up) # [False False True]
print(paths.min(axis=1) <= 96.0) # a down barrier at 96: [False False True]# hit_barrier for both directions, with an error for anything else
import numpy as np
def hit_barrier(paths, barrier, direction):
if direction == "up":
return paths.max(axis=1) >= barrier
if direction == "down":
return paths.min(axis=1) <= barrier
raise ValueError("direction must be 'up' or 'down'")
paths = np.array([[100.0, 110.0, 105.0], [100.0, 109.9, 105.0]])
print(hit_barrier(paths, 110.0, "up")) # [ True False]: touching the barrier exactly counts
try:
hit_barrier(paths, 110.0, "sideways")
except ValueError as error:
print("rejected:", error)敲出、敲入及其和
对每条路径,计算普通看涨期权的收益 max(S_T - K, 0) 以及障碍标志。敲出看涨期权在*未*触及障碍的路径上保留收益;敲入看涨期权在触及的路径上保留收益。每条路径只会是二者之一,所以逐条路径都有*敲入 + 敲出 = 普通看涨*,这是个方便的恒等式:定价较容易的那个就能顺便得到另一个,还可用来检验代码。np.where(hit, a, b) 按元素在两个数组之间选择。
import numpy as np
paths = np.array([[100.0, 105.0, 112.0, 108.0],
[100.0, 98.0, 103.0, 110.0],
[100.0, 120.0, 95.0, 130.0]])
strike, barrier = 100.0, 115.0
vanilla = np.maximum(paths[:, -1] - strike, 0.0)
hit = paths.max(axis=1) >= barrier
knock_out = np.where(hit, 0.0, vanilla)
knock_in = np.where(hit, vanilla, 0.0)
print(vanilla, knock_out, knock_in) # [ 8. 10. 30.] [ 8. 10. 0.] [ 0. 0. 30.]
print(np.allclose(knock_in + knock_out, vanilla)) # True定价与离散监测偏差
价格仍是折现后的平均收益。但要留意障碍的检验:模拟只在网格日期上看价格,而真实的连续障碍在日期*之间*也会被突破。步数少时,模拟路径常常悄悄越过障碍,导致敲出看涨期权算得太贵、敲入算得太便宜。增加步数可以减小偏差,把障碍平移约 0.5826 * sigma * sqrt(dt) 的修正也可以。
import numpy as np
def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
dt = t / steps
z = rng.standard_normal((n_paths, steps))
moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(moves, axis=1)]))
def knock_out_price(paths, strike, barrier, rate, t):
vanilla = np.maximum(paths[:, -1] - strike, 0.0)
payoff = np.where(paths.max(axis=1) >= barrier, 0.0, vanilla)
return float(np.exp(-rate * t) * payoff.mean())
rng = np.random.default_rng(1)
for steps in (12, 52, 252):
paths = simulate_paths(100.0, 0.05, 0.25, 1.0, steps, 40000, rng)
print(steps, round(knock_out_price(paths, 100.0, 130.0, 0.05, 1.0), 3)) # the price falls as monitoring gets finer障碍风险就是跳空风险在障碍附近,障碍期权的 delta 和 gamma 会跳变,价格的微小变动就可能让整个收益开启或关闭。这使得在障碍附近对冲敲出期权很困难,交易台会为此收费。还要留意谁能影响障碍附近的价格:合同会精确规定哪些价格算数(收盘价、交易所成交价),因为操纵市场的诱惑是真实存在的。
- 只要路径的最大值(向上)或最小值(向下)在任一日期触及障碍就算触碰;敲出触碰即失效,敲入触碰才生效。
- 逐条路径,敲入 + 敲出 = 普通看涨期权的收益;用
np.where(hit, a, b)选择;障碍期权比普通期权便宜。 - 在网格上模拟会漏掉日期之间的穿越,使敲出期权显得太贵:增加步数或平移障碍;留意障碍附近的跳空风险。
下一节:对偶变量,用更少的模拟获得同样的蒙特卡洛精度的办法。
实验:评分内容
- 用
hit_barrier检测触碰25 分 - 用
knock_out_call_payoff让期权失效25 分 - 用
knock_in_call_payoff让期权生效25 分 - 用
barrier_price把收益变成价格25 分