Working with Options

Working with Options

本文是学习 Mastering pandas for finance 一书第八章的笔记。

准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# !/anaconda3/bin/python
# -*- coding: utf-8 -*-
"""
标题 : Working with Options
作者 : 程振兴
创建日期: 2019-03-25
"""
import pandas as pd
import numpy as np
import tushare as ts
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib import rcParams

pro = ts.pro_api('你的 Tushare 密钥')
from matplotlib.font_manager import FontProperties

# 中文字体
cnfont = FontProperties(fname='/Library/Fonts/Songti.ttc', size=14)
# 英文字体
enfont = FontProperties(fname='/Users/czx/Library/Fonts/RobotoSlab-Regular.ttf', size=14)
# 解决负号'-'显示为方块的问题
rcParams['axes.unicode_minus'] = False
rcParams['savefig.dpi'] = 300 # 图片像素
rcParams['figure.dpi'] = 300 # 分辨率

#==========================================================#

from datetime import datetime

读入股票期权数据

aapl_options.csv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
aapl_options = pd.read_csv('aapl_options.csv', parse_dates = ['Expiry'])
aos = aapl_options.sort_values(['Expiry', 'Strike'])[
['Expiry', 'Strike', 'Type', 'IV', 'Bid', 'Ask', 'Underlying_Price']
]
aos['IV'] = aos['IV'].apply(lambda x: float(x.strip('%')))
aos[:5]
#> Expiry Strike Type ... Bid Ask Underlying_Price
#> 158 2015-02-27 75.0 call ... 53.60 53.85 128.79
#> 159 2015-02-27 75.0 put ... 0.00 0.01 128.79
#> 190 2015-02-27 80.0 call ... 48.65 48.80 128.79
#> 191 2015-02-27 80.0 put ... 0.00 0.01 128.79
#> 226 2015-02-27 85.0 call ... 43.65 43.80 128.79
#> [5 rows x 7 columns]

aos['Expiry'].unique()
#> array(['2015-02-27T00:00:00.000000000', '2015-03-06T00:00:00.000000000',
#> '2015-03-13T00:00:00.000000000', '2015-03-20T00:00:00.000000000',
#> '2015-03-27T00:00:00.000000000', '2015-04-02T00:00:00.000000000',
#> '2015-04-17T00:00:00.000000000', '2015-05-15T00:00:00.000000000',
#> '2015-07-17T00:00:00.000000000', '2015-10-16T00:00:00.000000000',
#> '2016-01-15T00:00:00.000000000', '2017-01-20T00:00:00.000000000'],
#> dtype='datetime64[ns]')

aos.loc[158]
#> Expiry 2015-02-27 00:00:00
#> Strike 75
#> Type call
#> IV 271.88
#> Bid 53.6
#> Ask 53.85
#> Underlying_Price 128.79
#> Name: 158, dtype: object

隐含波动率(IV)

1
2
3
4
5
6
7
8
9
calls1 = aos[(aos.Expiry == '2015-02-27') & (aos.Type == 'call')]
calls1[:5]
#> Expiry Strike Type ... Bid Ask Underlying_Price
#> 158 2015-02-27 75.0 call ... 53.60 53.85 128.79
#> 190 2015-02-27 80.0 call ... 48.65 48.80 128.79
#> 226 2015-02-27 85.0 call ... 43.65 43.80 128.79
#> 265 2015-02-27 90.0 call ... 38.65 38.80 128.79
#> 303 2015-02-27 93.0 call ... 35.65 35.80 128.79
#> [5 rows x 7 columns]

波动率偏斜:

1
2
3
4
5
ax = aos[(aos.Expiry == '2015-02-27') & (aos.Type == 'call')] \
.set_index('Strike')[['IV']].plot()
ax.axvline(calls1.Underlying_Price.iloc[0], color = 'g')
plt.savefig('wwo.svg')
plt.show()

The call option payoff calculation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def call_payoff(price_at_maturity, strike_price):
return max(0, price_at_maturity - strike_price)

def call_payoffs(min_maturity_price, max_maturity_price, strike_price, step = 1):
maturities = np.arange(min_maturity_price, max_maturity_price + step, step)
payoffs = np.vectorize(call_payoff)(maturities, strike_price)
df = pd.DataFrame({'Strike': strike_price, 'Payoff': payoffs},
index = maturities)
df.index.name = 'Maturity Price'
return df

call_payoffs(10, 25, 15)
#> Strike Payoff
#> Maturity Price
#> 10 15 0
#> 11 15 0
#> 12 15 0
#> 13 15 0
#> 14 15 0
#> 15 15 0
#> 16 15 1
#> 17 15 2
#> 18 15 3
#> 19 15 4
#> 20 15 5
#> 21 15 6
#> 22 15 7
#> 23 15 8
#> 24 15 9
#> 25 15 10
1
2
3
4
5
6
7
8
9
10
11
12
def plot_call_payoffs(min_maturity_price, max_maturity_price, strike_price, step = 1):
pay_offs = call_payoffs(min_maturity_price, max_maturity_price, strike_price, step)
plt.ylim(pay_offs.Payoff.min() - 10, pay_offs.Payoff.max() + 10)
plt.ylabel('Payoff')
plt.xlabel('Maturity Price')
plt.title('Payoff of call option, Strike = {0}'.format(strike_price))
plt.xlim(min_maturity_price, max_maturity_price)
plt.plot(pay_offs.index, pay_offs.Payoff.values)
plt.savefig('wwo2.svg')
plt.show()

plot_call_payoffs(10, 25, 15)

The put option payoff calculation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def put_payoff(price_at_maturity, strike_price):
return max(0, strike_price - price_at_maturity)
def put_payoffs(min_maturity_price, max_maturity_price, strike_price, step = 1):
maturies = np.arange(min_maturity_price, max_maturity_price + step, step)
payoffs = np.vectorize(put_payoff)(maturies, strike_price)
df = pd.DataFrame({'Payoff': payoffs, 'Strike': strike_price}, index = maturies)
df.index.name = 'Maturity Price'
return df

def plot_put_payoffs(min_maturity_price, max_maturity_price, strike_price, step = 1):
payoffs = put_payoffs(min_maturity_price, max_maturity_price, strike_price, step)
plt.ylim(payoffs.Payoff.min() - 10, payoffs.Payoff.max() + 10)
plt.ylabel('Payoff')
plt.xlabel('Maturity Price')
plt.title('Payoff of put option, Strike = {0}'.format(strike_price))
plt.xlim(min_maturity_price, max_maturity_price)
plt.plot(payoffs.index, payoffs.Payoff.values)
plt.savefig('wwo3.svg')
plt.show()

plot_put_payoffs(10, 25, 15)

The call option profit and loss for a buyer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def call_pnl_buyer(premium, strike_price, min_maturity_price, max_maturity_price, step = 1):
payoffs = call_payoffs(min_maturity_price, max_maturity_price, strike_price)
payoffs['Premium'] = premium
payoffs['Pnl'] = payoffs.Payoff - premium
return payoffs

pnl_buyer = call_pnl_buyer(12, 15, 10, 35)
pnl_buyer
#> Strike Payoff Premium Pnl
#> Maturity Price
#> 10 15 0 12 -12
#> 11 15 0 12 -12
#> 12 15 0 12 -12
#> 13 15 0 12 -12
#> 14 15 0 12 -12
#> 15 15 0 12 -12
#> 16 15 1 12 -11
#> 17 15 2 12 -10
#> 18 15 3 12 -9
#> 19 15 4 12 -8
#> 20 15 5 12 -7
#> 21 15 6 12 -6
#> 22 15 7 12 -5
#> 23 15 8 12 -4
#> 24 15 9 12 -3
#> 25 15 10 12 -2
#> 26 15 11 12 -1
#> 27 15 12 12 0
#> 28 15 13 12 1
#> 29 15 14 12 2
#> 30 15 15 12 3
#> 31 15 16 12 4
#> 32 15 17 12 5
#> 33 15 18 12 6
#> 34 15 19 12 7
#> 35 15 20 12 8

编写一个绘制期权收益图的函数:

1
2
3
4
5
6
7
8
9
10
11
def plot_pnl(pnl_df, okind, who):
plt.ylim(pnl_df.Payoff.min() - 10, pnl_df.Payoff.max() + 10)
plt.ylabel("Profit / Loss")
plt.xlabel("Maturity Price")
plt.title('Profit and loss of {0} option, {1}, Premium = {2} Strike = {3}'.format(okind, who, pnl_df.Premium.iloc[0], pnl_df.Strike.iloc[0]))
plt.ylim(pnl_df.Pnl.min() - 3, pnl_df.Pnl.max() + 3)
plt.xlim(pnl_df.index[0], pnl_df.index[len(pnl_df.index) - 1])
plt.plot(pnl_df.index, pnl_df.Pnl)
plt.axhline(0, color = 'g')
plt.savefig('pnl_{0}.svg'.format(who))
plt.show()

1
plot_pnl(pnl_buyer, "put", "Buyer")

The call option profit and loss for the seller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def call_pnl_seller(premium, strike_price, min_maturity, max_maturity_price, step = 1):
payoffs = call_payoffs(min_maturity, max_maturity_price, strike_price)
payoffs['Premium'] = premium
payoffs['Pnl'] = premium - payoffs.Payoff
return payoffs

pnl_seller = call_pnl_seller(12, 15, 10, 35)
pnl_seller
#> Strike Payoff Premium Pnl
#> Maturity Price
#> 10 15 0 12 12
#> 11 15 0 12 12
#> 12 15 0 12 12
#> 13 15 0 12 12
#> 14 15 0 12 12
#> 15 15 0 12 12
#> 16 15 1 12 11
#> 17 15 2 12 10
#> 18 15 3 12 9
#> 19 15 4 12 8
#> 20 15 5 12 7
#> 21 15 6 12 6
#> 22 15 7 12 5
#> 23 15 8 12 4
#> 24 15 9 12 3
#> 25 15 10 12 2
#> 26 15 11 12 1
#> 27 15 12 12 0
#> 28 15 13 12 -1
#> 29 15 14 12 -2
#> 30 15 15 12 -3
#> 31 15 16 12 -4
#> 32 15 17 12 -5
#> 33 15 18 12 -6
#> 34 15 19 12 -7
#> 35 15 20 12 -8
1
plot_pnl(pnl_seller, "call", "Seller")

Combined payoff charts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def plot_combined_pnl(pnl_df):
plt.ylim(pnl_df.Payoff.min() - 10, pnl_df.Payoff.max() + 10)
plt.ylabel("Profit / Loss")
plt.xlabel("Maturity Price")
plt.title("Profti and Loss of call option Strike = {0}".format(pnl_df.Strike.iloc[0]))
plt.ylim(min(pnl_df.PnLBuyer.min(), pnl_df.PnLSeller.min()) - 3, max(pnl_df.PnLBuyer.max(), pnl_df.PnLSeller.max()) + 3)
plt.xlim(pnl_df.index[0], pnl_df.index[len(pnl_df.index) - 1])
plt.plot(pnl_df.index, pnl_df.PnLBuyer, color = 'b')
plt.plot(pnl_df.index, pnl_df.PnLSeller, color = 'r')
plt.axhline(0, color = 'g');
plt.savefig('combined.svg')
plt.show()

pnl_combined = pd.DataFrame({
'PnLBuyer': pnl_buyer.Pnl,
'PnLSeller': pnl_seller.Pnl,
'Premium': pnl_buyer.Premium,
'Strike': pnl_buyer.Strike,
'Payoff': pnl_buyer.Payoff})

pnl_combined
#> PnLBuyer PnLSeller Premium Strike Payoff
#> Maturity Price
#> 10 -12 12 12 15 0
#> 11 -12 12 12 15 0
#> 12 -12 12 12 15 0
#> 13 -12 12 12 15 0
#> 14 -12 12 12 15 0
#> 15 -12 12 12 15 0
#> 16 -11 11 12 15 1
#> 17 -10 10 12 15 2
#> 18 -9 9 12 15 3
#> 19 -8 8 12 15 4
#> 20 -7 7 12 15 5
#> 21 -6 6 12 15 6
#> 22 -5 5 12 15 7
#> 23 -4 4 12 15 8
#> 24 -3 3 12 15 9
#> 25 -2 2 12 15 10
#> 26 -1 1 12 15 11
#> 27 0 0 12 15 12
#> 28 1 -1 12 15 13
#> 29 2 -2 12 15 14
#> 30 3 -3 12 15 15
#> 31 4 -4 12 15 16
#> 32 5 -5 12 15 17
#> 33 6 -6 12 15 18
#> 34 7 -7 12 15 19
#> 35 8 -8 12 15 20

plot_combined_pnl(pnl_combined)

The put option and loss for a buyer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def put_pnl_buyer(premium, strike_price, min_maturity_price, max_maturity_price, step = 1):
payoffs = put_payoffs(min_maturity_price, max_maturity_price, strike_price)
payoffs['Premium'] = premium
payoffs['Strike'] = strike_price
payoffs['Pnl'] = payoffs.Payoff - payoffs.Premium
return payoffs

pnl_put_buyer = put_pnl_buyer(2, 15, 10, 30)
pnl_put_buyer
#> Payoff Strike Premium Pnl
#> Maturity Price
#> 10 5 15 2 3
#> 11 4 15 2 2
#> 12 3 15 2 1
#> 13 2 15 2 0
#> 14 1 15 2 -1
#> 15 0 15 2 -2
#> 16 0 15 2 -2
#> 17 0 15 2 -2
#> 18 0 15 2 -2
#> 19 0 15 2 -2
#> 20 0 15 2 -2
#> 21 0 15 2 -2
#> 22 0 15 2 -2
#> 23 0 15 2 -2
#> 24 0 15 2 -2
#> 25 0 15 2 -2
#> 26 0 15 2 -2
#> 27 0 15 2 -2
#> 28 0 15 2 -2
#> 29 0 15 2 -2
#> 30 0 15 2 -2
1
plot_pnl(pnl_put_buyer, "put", "Buyer2")

The put option profit and loss for the seller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def put_pnl_seller(premium, strike_price, min_maturity_price, max_maturity_price, step = 1):
payoffs = put_payoffs(min_maturity_price, max_maturity_price, strike_price)
payoffs['Premium'] = premium
payoffs['Strike'] = strike_price
payoffs['Pnl'] = payoffs.Premium - payoffs.Payoff
return payoffs

pnl_put_seller = put_pnl_seller(2, 15, 10, 30)
pnl_put_seller
#> Payoff Strike Premium Pnl
#> Maturity Price
#> 10 5 15 2 -3
#> 11 4 15 2 -2
#> 12 3 15 2 -1
#> 13 2 15 2 0
#> 14 1 15 2 1
#> 15 0 15 2 2
#> 16 0 15 2 2
#> 17 0 15 2 2
#> 18 0 15 2 2
#> 19 0 15 2 2
#> 20 0 15 2 2
#> 21 0 15 2 2
#> 22 0 15 2 2
#> 23 0 15 2 2
#> 24 0 15 2 2
#> 25 0 15 2 2
#> 26 0 15 2 2
#> 27 0 15 2 2
#> 28 0 15 2 2
#> 29 0 15 2 2
#> 30 0 15 2 2

plot_pnl(pnl_put_seller, "put", "Seller2")

Black-Scholes using Mibian

1
2
3
4
5
6
7
8
9
10
# 首先我们查看 2015年1月15日的两只期权的数据
aos[aos.Expiry == '2016-01-15'][:2]
#> Expiry Strike Type ... Bid Ask Underlying_Price
#> 0 2016-01-15 34.29 call ... 94.10 94.95 128.79
#> 1 2016-01-15 34.29 put ... 0.01 0.07 128.79

# 从 2015年1月15日到2016年1月15日一共324天
from datetime import date
date(2016, 1, 15) - date(2015, 2, 25)
#> datetime.timedelta(days=324)

再使用 BS 公式计算一只 S = 128.79, T = 324, K = 34.29, sigma = 57.23 , r = 1% 的期权的价格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import mibian
c = mibian.BS([128.79, 34.29, 1, 324], 57.23)
# call 的价格
c.callPrice
#> 94.87897008945622

# put 的价格
c.putPrice
#> 0.0759345929965427

# 还可以计算隐含波动率
c = mibian.BS([128.79, 34.29, 1, 324], callPrice = 94.87897008945622)
c.impliedVolatility
#> 57.22999572753906

Charting option price change over time

1
2
3
4
5
6
7
8
9
10
11
12
13
df = pd.DataFrame({'DaysToExpiry': np.arange(364, 0, -1)})
df
#> DaysToExpiry
#> 0 364
#> 1 363
#> 2 362
#> 3 361
#> .. ...
#> 360 4
#> 361 3
#> 362 2
#> 363 1
#> [364 rows x 1 columns]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
calc_call = lambda r: mibian.BS([128.79, 34.29, 1, r.DaysToExpiry], volatility = 57.23).callPrice
calc_put = lambda r: mibian.BS([128.79, 34.29, 1, r.DaysToExpiry], volatility = 57.23).putPrice
df['CallPrice'] = df.apply(calc_call, axis = 1)
df['PutPrice'] = df.apply(calc_put, axis = 1)
df
#> DaysToExpiry CallPrice PutPrice
#> 0 364 94.961733 1.214723e-01
#> 1 363 94.959518 1.201870e-01
#> 2 362 94.957310 1.189092e-01
#> 3 361 94.955109 1.176389e-01
#> .. ... ... ...
#> 361 3 94.502818 1.114374e-144
#> 362 2 94.501879 1.426973e-215
#> 363 1 94.500939 0.000000e+00
#> [364 rows x 3 columns]
1
2
3
df[['CallPrice']].plot()
plt.savefig('callpriceovertime.svg')
plt.show()

1
2
3
df[['PutPrice']].plot()
plt.savefig('putpriceovertime.svg')
plt.show()

The Greeks

  • Delta: This is the rate of change of the option value with respect to a change in the price of the underlying security.
  • Vega: This is the rate of changing of the option value with respect to a change in the volatility of the underlying security.
  • Theta: This is the rate of change of the option value with respect to the time to expiry.
  • Rho: This is the rate of change of the option value with respect the interest rate.
  • Gamma: This is the rate of change of the Delta Greek with respect to a change in the price of the underlying security.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
greeks = pd.DataFrame()
delta = lambda r: mibian.BS([r.Price, 60, 1, 180], volatility = 30).callDelta
gamma = lambda r: mibian.BS([r.Price, 60, 1, 180], volatility=30).gamma
theta = lambda r: mibian.BS([r.Price, 60, 1, 180], volatility = 30).callTheta
vega = lambda r: mibian.BS([r.Price, 60, 1, 180], volatility = 30).vega
greeks['Price'] = np.arange(10, 70)
greeks['Delta'] = greeks.apply(delta, axis = 1)
greeks['Gamma'] = greeks.apply(gamma, axis =1)
greeks['Theta'] = greeks.apply(theta, axis = 1)
greeks['Vega'] = greeks.apply(vega, axis = 1)
greeks[:5]
#> Price Delta Gamma Theta Vega
#> 0 10 2.734513e-17 1.102292e-16 -1.366303e-18 1.630788e-17
#> 1 11 1.152379e-15 4.001131e-15 -6.002666e-17 7.162573e-16
#> 2 12 2.940076e-14 8.884484e-14 -1.586711e-15 1.892760e-14
#> 3 13 4.989827e-13 1.323809e-12 -2.775518e-14 3.309886e-13
#> 4 14 6.049640e-12 1.419521e-11 -3.452715e-13 4.116221e-12
1
2
3
greeks[['Delta', 'Gamma', 'Theta', 'Vega']].plot()
plt.savefig('greeks.svg')
plt.show()

# Python

评论

程振兴

程振兴 @czxa.top
截止今天,我已经在本博客上写了659.4k个字了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×