├── .gitignore ├── american_option.py ├── baw.py ├── european_option.py ├── historical_implied_volatility.py ├── historical_volatility.py ├── hiv.png ├── hv.png ├── iv.png ├── min1_iv.png ├── readme.md ├── sina_commodity_option_api.py ├── sina_commodity_option_spider.py ├── sina_etf_option_api.py ├── sina_future_kline_api.py ├── sina_stock_kline_api.py ├── time_line_iv.py ├── volatility_surface.py └── volatility_surface2.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # pycharm 107 | .idea/ 108 | -------------------------------------------------------------------------------- /american_option.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | from math import sqrt, exp, inf 6 | import numpy as np 7 | 8 | 9 | def _call_price(s, k, sigma, r, t, steps=100): 10 | r_ = exp(r * (t / steps)) 11 | r_reciprocal = 1.0 / r_ 12 | u = exp(sigma * sqrt(t / steps)) 13 | d = 1.0 / u 14 | u_square = u ** 2 15 | p_u = (r_ - d) / (u - d) 16 | p_d = 1.0 - p_u 17 | prices = np.zeros(steps + 1) 18 | prices[0] = s * d ** steps 19 | for i in range(1, steps + 1): 20 | prices[i] = prices[i - 1] * u_square 21 | values = np.zeros(steps + 1) 22 | for i in range(steps + 1): 23 | values[i] = max(0.0, prices[i] - k) 24 | for j in range(steps, 0, -1): 25 | for i in range(j): 26 | values[i] = (p_u * values[i + 1] + p_d * values[i]) * r_reciprocal 27 | prices[i] = d * prices[i + 1] 28 | values[i] = max(values[i], prices[i] - k) 29 | # print(values) 30 | return values[0] 31 | 32 | 33 | def _put_price(s, k, sigma, r, t, steps=100): 34 | r_ = exp(r * (t / steps)) 35 | r_reciprocal = 1.0 / r_ 36 | u = exp(sigma * sqrt(t / steps)) 37 | d = 1.0 / u 38 | u_square = u ** 2 39 | p_u = (r_ - d) / (u - d) 40 | p_d = 1.0 - p_u 41 | prices = np.zeros(steps + 1) 42 | prices[0] = s * d ** steps 43 | for i in range(1, steps + 1): 44 | prices[i] = prices[i - 1] * u_square 45 | values = np.zeros(steps + 1) 46 | for i in range(steps + 1): 47 | values[i] = max(0, k - prices[i]) 48 | for j in range(steps, 0, -1): 49 | for i in range(0, j): 50 | values[i] = (p_u * values[i + 1] + p_d * values[i]) * r_reciprocal 51 | prices[i] = d * prices[i + 1] 52 | values[i] = max(values[i], k - prices[i]) 53 | return values[0] 54 | 55 | 56 | def call_price(s, k, sigma, r, t, steps=100): 57 | return (_call_price(s, k, sigma, r, t, steps) + _call_price(s, k, sigma, r, t, steps + 1)) / 2.0 58 | 59 | 60 | def put_price(s, k, sigma, r, t, steps=100): 61 | return (_put_price(s, k, sigma, r, t, steps) + _put_price(s, k, sigma, r, t, steps + 1)) / 2.0 62 | 63 | 64 | def delta(s, k, sigma, r, t, option_type, steps=100): 65 | if t == 0.0: 66 | if s == k: 67 | return {'Call': 0.5, 'Put': -0.5}[option_type] 68 | elif s > k: 69 | return {'Call': 1.0, 'Put': 0.0}[option_type] 70 | else: 71 | return {'Call': 0.0, 'Put': -1.0}[option_type] 72 | else: 73 | price_func = {'Call': call_price, 'Put': put_price}[option_type] 74 | return (price_func(s + 0.01, k, sigma, r, t, steps=steps) - 75 | price_func(s - 0.01, k, sigma, r, t, steps=steps)) * 50.0 76 | 77 | 78 | def gamma(s, k, sigma, r, t, option_type, steps=100): 79 | if t == 0.0: 80 | return inf if s == k else 0.0 81 | price_func = {'Call': call_price, 'Put': put_price}[option_type] 82 | return (price_func(s + 0.01, k, sigma, r, t, steps=steps) + 83 | price_func(s - 0.01, k, sigma, r, t, steps=steps) - 84 | price_func(s, k, sigma, r, t, steps=steps) * 2.0) * 10000.0 85 | 86 | 87 | def theta(s, k, sigma, r, t, option_type, steps=100): 88 | price_func = {'Call': call_price, 'Put': put_price}[option_type] 89 | t_unit = 1.0 / 365.0 90 | if t <= t_unit: 91 | return price_func(s, k, sigma, r, 0.0001, steps=steps) - \ 92 | price_func(s, k, sigma, r, t, steps=steps) 93 | else: 94 | return price_func(s, k, sigma, r, t - t_unit, steps=steps) - \ 95 | price_func(s, k, sigma, r, t, steps=steps) 96 | 97 | 98 | def vega(s, k, sigma, r, t, option_type, steps=100): 99 | price_func = {'Call': call_price, 'Put': put_price}[option_type] 100 | if sigma < 0.02: 101 | return 0.0 102 | else: 103 | return (price_func(s, k, sigma + 0.01, r, t, steps=steps) - 104 | price_func(s, k, sigma - 0.01, r, t, steps=steps)) * 50.0 105 | 106 | 107 | def rho(s, k, sigma, r, t, option_type, steps=100): 108 | price_func = {'Call': call_price, 'Put': put_price}[option_type] 109 | return (price_func(s, k, sigma, r + 0.001, t, steps=steps) - 110 | price_func(s, k, sigma, r - 0.001, t, steps=steps)) * 500.0 111 | 112 | 113 | def call_iv(c, s, k, t, r=0.03, sigma_min=0.01, sigma_max=3.0, e=0.00001, steps=100): 114 | sigma_mid = (sigma_min + sigma_max) / 2.0 115 | call_min = call_price(s, k, sigma_min, r, t, steps) 116 | call_max = call_price(s, k, sigma_max, r, t, steps) 117 | call_mid = call_price(s, k, sigma_mid, r, t, steps) 118 | diff = c - call_mid 119 | if c <= call_min: 120 | return sigma_min 121 | elif c >= call_max: 122 | return sigma_max 123 | while abs(diff) > e: 124 | if c > call_mid: 125 | sigma_min = sigma_mid 126 | else: 127 | sigma_max = sigma_mid 128 | sigma_mid = (sigma_min + sigma_max) / 2.0 129 | call_mid = call_price(s, k, sigma_mid, r, t, steps) 130 | diff = c - call_mid 131 | # print(sigma_mid) 132 | return sigma_mid 133 | 134 | 135 | def put_iv(c, s, k, t, r=0.03, sigma_min=0.01, sigma_max=3.0, e=0.00001, steps=100): 136 | sigma_mid = (sigma_min + sigma_max) / 2.0 137 | put_min = put_price(s, k, sigma_min, r, t, steps) 138 | put_max = put_price(s, k, sigma_max, r, t, steps) 139 | put_mid = put_price(s, k, sigma_mid, r, t, steps) 140 | diff = c - put_mid 141 | if c <= put_min: 142 | return sigma_min 143 | elif c >= put_max: 144 | return sigma_max 145 | while abs(diff) > e: 146 | if c > put_mid: 147 | sigma_min = sigma_mid 148 | else: 149 | sigma_max = sigma_mid 150 | sigma_mid = (sigma_min + sigma_max) / 2.0 151 | put_mid = put_price(s, k, sigma_mid, r, t, steps) 152 | diff = c - put_mid 153 | return sigma_mid 154 | 155 | 156 | def my_test(): 157 | import matplotlib.pyplot as plt 158 | a = np.linspace(1.0 / 365.0, 2, 100) 159 | yc, yp = [], [] 160 | for i in a: 161 | yc.append(vega(6.0, 5.0, 0.25, 0.03, i, option_type='Call', steps=100)) 162 | yp.append(vega(6.0, 5.0, 0.25, 0.03, i, option_type='Put', steps=100)) 163 | plt.plot(yc) 164 | plt.plot(yp) 165 | plt.show() 166 | 167 | 168 | def my_test2(): 169 | # print(call_price(5.0, 5.0, 0.1, 0.03, 0.4)) 170 | # call_price(5.0, 5.0, 0.25, 0.03, 0.4, 99) 171 | print(call_iv(0.138, 3.046, 3.1, 0.5, r=0.03, sigma_min=0.01, sigma_max=1.0, e=0.00001, steps=100)) 172 | 173 | 174 | if __name__ == '__main__': 175 | my_test2() 176 | 177 | -------------------------------------------------------------------------------- /baw.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | from math import log, sqrt, exp, inf 6 | from scipy.stats import norm 7 | import scipy.optimize as opt 8 | 9 | 10 | def bsm_call(s, k, sigma, t, r, q): 11 | sqrt_t = sqrt(t) 12 | d1 = (log(s / k) + (r - q + sigma ** 2 / 2.0) * t) / (sigma * sqrt_t) 13 | d2 = d1 - sigma * sqrt_t 14 | return s * exp(-q * t) * norm.cdf(d1) - k * exp(-r * t) * norm.cdf(d2) 15 | 16 | 17 | def bsm_put(s, k, sigma, t, r, q): 18 | sqrt_t = sqrt(t) 19 | d1 = (log(s / k) + (r - q + sigma ** 2 / 2.0) * t) / (sigma * sqrt_t) 20 | d2 = d1 - sigma * sqrt_t 21 | return k * exp(-r * t) * norm.cdf(-d2) - s * exp(-q * t) * norm.cdf(-d1) 22 | 23 | 24 | def find_sx(sx, k, sigma, t, r, q, option_type): 25 | n = 2.0 * (r - q) / sigma ** 2 26 | k_ = 2.0 * r / sigma ** 2 / (1.0 - exp(-r * t)) 27 | if sx < 0.0: 28 | return inf 29 | if option_type == 'Call': 30 | q2 = (1.0 - n + sqrt((n - 1.0) ** 2 + 4.0 * k_)) / 2.0 31 | return (bsm_call(sx, k, sigma, t, r, q) + (1.0 - exp(-q * t) 32 | * norm.cdf((log(sx / k) + (r - q + sigma ** 2 / 2.0)) / (sigma * sqrt(t)))) 33 | * sx / q2 - sx + k) ** 2 34 | else: 35 | q1 = (1.0 - n - sqrt((n - 1.0) ** 2 + 4.0 * k_)) / 2.0 36 | return (bsm_put(sx, k, sigma, t, r, q) - (1.0 - exp(-q * t) 37 | * norm.cdf(-(log(sx / k) + (r - q + sigma ** 2 / 2.0)) / (sigma * sqrt(t)))) 38 | * sx / q1 + sx - k) ** 2 39 | 40 | 41 | def baw_call(s, k, sigma, t, r, q=0.0): 42 | c = bsm_call(s, k, sigma, t, r, q) 43 | sx = opt.fmin(lambda i: find_sx(i, k, sigma, t, r, q, 'Call'), s)[0] 44 | d1 = (log(sx / k) + (r - q + sigma ** 2 / 2.0)) / (sigma * sqrt(t)) 45 | n = 2.0 * (r - q) / sigma ** 2.0 46 | k_ = 2.0 * r / (sigma ** 2 * (1.0 - exp(-r * t))) 47 | q2 = (1.0 - n + sqrt((n - 1.0) ** 2 + 4.0 * k_)) / 2.0 48 | a2 = sx * (1.0 - exp(-q * t) * norm.cdf(d1)) / q2 49 | return c + a2 * (s / sx) ** q2 if s < sx else s - k 50 | 51 | 52 | def baw_put(s, k, sigma, t, r, q=0.0): 53 | p = bsm_put(s, k, sigma, t, r, q) 54 | sx = opt.fmin(lambda i: find_sx(i, k, sigma, t, r, q, 'Put'), s)[0] 55 | d1 = (log(sx / k) + (r - q + sigma ** 2 / 2.0)) / (sigma * sqrt(t)) 56 | n = 2.0 * (r - q) / sigma ** 2 57 | k_ = 2.0 * r / (sigma ** 2 * (1.0 - exp(-r * t))) 58 | q1 = (1.0 - n - sqrt((n - 1.0) ** 2 + 4.0 * k_)) / 2.0 59 | a1 = -sx * (1.0 - exp(-q * t) * norm.cdf(-d1)) / q1 60 | return p + a1 * (s / sx) ** q1 if s > sx else k - s 61 | 62 | 63 | def call_iv(c, s, k, t, r=0.03, sigma_min=0.0001, sigma_max=3.0, e=0.00001): 64 | sigma_mid = (sigma_min + sigma_max) / 2.0 65 | call_min = bsm_call(s, k, sigma_min, t, r, 0.0) 66 | call_max = bsm_call(s, k, sigma_max, t, r, 0.0) 67 | call_mid = bsm_call(s, k, sigma_mid, t, r, 0.0) 68 | diff = c - call_mid 69 | if c <= call_min: 70 | return sigma_min 71 | elif c >= call_max: 72 | return sigma_max 73 | while abs(diff) > e: 74 | if c > call_mid: 75 | sigma_min = sigma_mid 76 | else: 77 | sigma_max = sigma_mid 78 | sigma_mid = (sigma_min + sigma_max) / 2.0 79 | call_mid = bsm_call(s, k, sigma_mid, t, r, 0.0) 80 | diff = c - call_mid 81 | return sigma_mid 82 | 83 | 84 | def put_iv(c, s, k, t, r=0.03, sigma_min=0.0001, sigma_max=3.0, e=0.00001): 85 | sigma_mid = (sigma_min + sigma_max) / 2.0 86 | put_min = bsm_put(s, k, sigma_min, t, r, 0.0) 87 | put_max = bsm_put(s, k, sigma_max, t, r, 0.0) 88 | put_mid = bsm_put(s, k, sigma_mid, t, r, 0.0) 89 | diff = c - put_mid 90 | if c <= put_min: 91 | return sigma_min 92 | elif c >= put_max: 93 | return sigma_max 94 | while abs(diff) > e: 95 | if c > put_mid: 96 | sigma_min = sigma_mid 97 | else: 98 | sigma_max = sigma_mid 99 | sigma_mid = (sigma_min + sigma_max) / 2.0 100 | put_mid = bsm_put(s, k, sigma_mid, t, r, 0.0) 101 | diff = c - put_mid 102 | return sigma_mid 103 | 104 | 105 | def delta(s, k, sigma, t, r, option_type): 106 | if t == 0.0: 107 | if s == k: 108 | return 0.5 if option_type == 'Call' else -0.5 109 | elif s > k: 110 | return 1.0 if option_type == 'Call' else 0.0 111 | else: 112 | return 0.0 if option_type == 'Call' else -1.0 113 | else: 114 | price_func = baw_call if option_type == 'Call' else baw_put 115 | return (price_func(s + 0.01, k, sigma, t, r) - 116 | price_func(s - 0.01, k, sigma, t, r)) * 50.0 117 | 118 | 119 | def gamma(s, k, sigma, t, r, option_type): 120 | if t == 0.0: 121 | return inf if s == k else 0.0 122 | price_func = baw_call if option_type == 'Call' else baw_put 123 | return (price_func(s + 0.01, k, sigma, t, r) + 124 | price_func(s - 0.01, k, sigma, t, r) - 125 | price_func(s, k, sigma, t, r) * 2.0) * 10000.0 126 | 127 | 128 | def theta(s, k, sigma, t, r, option_type): 129 | price_func = baw_call if option_type == 'Call' else baw_put 130 | t_unit = 1.0 / 365.0 131 | if t <= t_unit: 132 | return price_func(s, k, sigma, 0.0001, r) - \ 133 | price_func(s, k, sigma, t, r) 134 | else: 135 | return price_func(s, k, sigma, t - t_unit, r) - \ 136 | price_func(s, k, sigma, t, r) 137 | 138 | 139 | def vega(s, k, sigma, t, r, option_type): 140 | price_func = baw_call if option_type == 'Call' else baw_put 141 | if sigma < 0.02: 142 | return 0.0 143 | else: 144 | return (price_func(s, k, sigma + 0.01, t, r) - 145 | price_func(s, k, sigma - 0.01, t, r)) * 50.0 146 | 147 | 148 | def rho(s, k, sigma, t, r, option_type): 149 | price_func = baw_call if option_type == 'Call' else baw_put 150 | return (price_func(s, k, sigma, t, r + 0.001,) - 151 | price_func(s, k, sigma, t, r - 0.001,)) * 500.0 152 | 153 | 154 | if __name__ == '__main__': 155 | # print(baw_call(2707, 2900, 0.165, 78.0 / 365, 0.03, 0.0)) 156 | # print(baw_put(2710, 2750, 0.15, 78.0 / 365, 0.03, 0.0)) 157 | print(call_iv(24.0, 2710, 2900, 78.0 / 365)) 158 | print(put_iv(92.5, 2710, 2750, 78.0 / 365)) 159 | 160 | -------------------------------------------------------------------------------- /european_option.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | from math import log, sqrt, exp 6 | import numpy as np 7 | from scipy.stats import norm 8 | 9 | 10 | # def bs_call(s, k, sigma, r, t): 11 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 12 | # d2 = d1 - sigma * np.sqrt(t) 13 | # return s * norm.cdf(d1) - k * np.exp(-r * t) * norm.cdf(d2) 14 | # 15 | # 16 | # def bs_put(s, k, sigma, r, t): 17 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 18 | # d2 = d1 - sigma * np.sqrt(t) 19 | # return k * np.exp(-r * t) * norm.cdf(-d2) - s * norm.cdf(-d1) 20 | 21 | def greeks(s, k, sigma, r, t, option_type): 22 | sqrt_t = sqrt(t) 23 | d1 = (log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * sqrt_t) 24 | d2 = d1 - sigma * sqrt_t 25 | tmp = exp(-pow(d1, 2) / 2.0) 26 | tmp2 = sqrt(2.0 * np.pi * t) 27 | tmp3 = r * k * exp(-r * t) 28 | gamma = tmp / (s * sigma * tmp2) 29 | theta_call = -(s * sigma * tmp) / (2.0 * tmp2) - tmp3 * norm.cdf(d2) 30 | vega = s * sqrt_t * tmp / sqrt(2.0 * np.pi) 31 | if option_type == 'Call': 32 | delta = norm.cdf(d1) 33 | theta = theta_call 34 | else: 35 | delta = norm.cdf(d1) - 1.0 36 | theta = theta_call + tmp3 37 | return delta, gamma, theta, vega 38 | 39 | # 40 | # def delta(s, k, sigma, r, t, option_type): 41 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 42 | # if option_type == 'Call': 43 | # return norm.cdf(d1) 44 | # else: 45 | # return norm.cdf(d1) - 1.0 46 | # 47 | # 48 | # def gamma(s, k, sigma, r, t): 49 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 50 | # return np.exp(-pow(d1, 2) / 2.0) / (s * sigma * np.sqrt(2.0 * np.pi * t)) 51 | # 52 | # 53 | # def theta(s, k, sigma, r, t, option_type): 54 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 55 | # d2 = d1 - sigma * np.sqrt(t) 56 | # theta_call = -(s * sigma * np.exp(-pow(d1, 2) / 2.0)) / (2.0 * np.sqrt(2.0 * np.pi * t)) - \ 57 | # r * k * np.exp(-r * t) * norm.cdf(d2) 58 | # if option_type == 'Call': 59 | # return theta_call 60 | # else: 61 | # return theta_call + r * k * np.exp(-r * t) 62 | # 63 | # 64 | # def vega(s, k, sigma, r, t): 65 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 66 | # return s * np.sqrt(t) * np.exp(-pow(d1, 2) / 2.0) / np.sqrt(2.0 * np.pi) 67 | # 68 | # 69 | # def rho(s, k, sigma, r, t, option_type): 70 | # d1 = (np.log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * np.sqrt(t)) 71 | # d2 = d1 - sigma * np.sqrt(t) 72 | # if option_type == 'Call': 73 | # return k * t * np.exp(-r * t) * norm.cdf(d2) 74 | # else: 75 | # return -k * t * np.exp(-r * t) * norm.cdf(-d2) 76 | 77 | 78 | def bs_call(s, k, sigma, r, t): 79 | tmp = sqrt(t) 80 | d1 = (log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * tmp) 81 | d2 = d1 - sigma * tmp 82 | return s * norm.cdf(d1) - k * exp(-r * t) * norm.cdf(d2) 83 | 84 | 85 | def bs_put(s, k, sigma, r, t): 86 | tmp = sqrt(t) 87 | d1 = (log(s / k) + (r + pow(sigma, 2) / 2.0) * t) / (sigma * tmp) 88 | d2 = d1 - sigma * tmp 89 | return k * exp(-r * t) * norm.cdf(-d2) - s * norm.cdf(-d1) 90 | 91 | 92 | def call_iv(c, s, k, t, r=0.03, sigma_min=0.01, sigma_max=1.0, e=0.00001): 93 | sigma_mid = (sigma_min + sigma_max) / 2.0 94 | call_min = bs_call(s, k, sigma_min, r, t) 95 | call_max = bs_call(s, k, sigma_max, r, t) 96 | call_mid = bs_call(s, k, sigma_mid, r, t) 97 | diff = c - call_mid 98 | if c <= call_min: 99 | return sigma_min 100 | elif c >= call_max: 101 | return sigma_max 102 | while abs(diff) > e: 103 | if c > call_mid: 104 | sigma_min = sigma_mid 105 | else: 106 | sigma_max = sigma_mid 107 | sigma_mid = (sigma_min + sigma_max) / 2.0 108 | call_mid = bs_call(s, k, sigma_mid, r, t) 109 | diff = c - call_mid 110 | # print(sigma_mid) 111 | return sigma_mid 112 | 113 | 114 | def put_iv(c, s, k, t, r=0.03, sigma_min=0.01, sigma_max=1.0, e=0.00001): 115 | sigma_mid = (sigma_min + sigma_max) / 2.0 116 | put_min = bs_put(s, k, sigma_min, r, t) 117 | put_max = bs_put(s, k, sigma_max, r, t) 118 | put_mid = bs_put(s, k, sigma_mid, r, t) 119 | diff = c - put_mid 120 | if c <= put_min: 121 | return sigma_min 122 | elif c >= put_max: 123 | return sigma_max 124 | while abs(diff) > e: 125 | if c > put_mid: 126 | sigma_min = sigma_mid 127 | else: 128 | sigma_max = sigma_mid 129 | sigma_mid = (sigma_min + sigma_max) / 2.0 130 | put_mid = bs_put(s, k, sigma_mid, r, t) 131 | diff = c - put_mid 132 | return sigma_mid 133 | 134 | 135 | # def my_test(): 136 | # call_iv(0.138, 3.046, 3.1, 0.5, r=0.03, sigma_min=0.01, sigma_max=1.0, e=0.000001) 137 | # 138 | # 139 | # def my_test2(): 140 | # import matplotlib.pyplot as plt 141 | # a = np.linspace(0, 0.8, 100) 142 | # yc, yp = [], [] 143 | # for i in a: 144 | # yc.append(vega(6.0, 5.0, i, 0.03, 0.5)) 145 | # yp.append(vega(6.0, 5.0, i, 0.03, 0.5)) 146 | # plt.plot(yc) 147 | # plt.plot(yp) 148 | # plt.show() 149 | 150 | 151 | # if __name__ == '__main__': 152 | # my_test2() 153 | 154 | -------------------------------------------------------------------------------- /historical_implied_volatility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import math 6 | import datetime 7 | from io import BytesIO 8 | from copy import deepcopy 9 | 10 | import matplotlib.pyplot as plt 11 | 12 | from sina_stock_kline_api import get_stock_day_kline 13 | from sina_future_kline_api import get_future_day_kline 14 | from sina_commodity_option_api import get_option_kline as get_future_option_day_kline 15 | from sina_etf_option_api import get_option_day_kline as get_etf_option_day_kline 16 | import european_option 17 | # import american_option 18 | import baw 19 | 20 | 21 | ETF_SPOT_CODE = { 22 | '510050': 'sh510050', 23 | '510300': 'sh510300', 24 | '159919': 'sz159919', 25 | } 26 | 27 | STOCK_SPOT_CODE = deepcopy(ETF_SPOT_CODE) 28 | STOCK_SPOT_CODE.update({ 29 | '000300': 'sh000300', 30 | }) 31 | 32 | 33 | def days_interval(date1, date2): 34 | d1 = datetime.datetime.strptime(str(date1), "%Y%m%d") 35 | d2 = datetime.datetime.strptime(str(date2), "%Y%m%d") 36 | days = abs((d1 - d2).days) 37 | return days, float(days) / 365.0 38 | 39 | 40 | def get_kline(option_code, spot_code): 41 | if spot_code in ETF_SPOT_CODE: 42 | option_kline = get_etf_option_day_kline(option_code) 43 | else: 44 | option_kline = get_future_option_day_kline(option_code) 45 | if spot_code in STOCK_SPOT_CODE: 46 | spot_kline = get_stock_day_kline(STOCK_SPOT_CODE[spot_code]) 47 | else: 48 | spot_kline = get_future_day_kline(spot_code) 49 | return option_kline, spot_kline 50 | 51 | 52 | def align_kline(option_kline, spot_kline): 53 | if not option_kline or not spot_kline: 54 | return [], [] 55 | else: 56 | if 'c' in option_kline[0]: 57 | date_key, close_key, date_func = 'd', 'c', lambda x: int(''.join(x.split('-'))) 58 | else: 59 | date_key, close_key, date_func = 'date', 'close', lambda x: int(''.join(x[:10].split('-'))) 60 | option_data = [(date_func(i[date_key]), float(i[close_key])) for i in option_kline] 61 | if 'c' in spot_kline[0]: 62 | date_key, close_key, date_func = 'd', 'c', lambda x: int(''.join(x.split('-'))) 63 | else: 64 | date_key, close_key, date_func = 'date', 'close', lambda x: int(''.join(x[:10].split('-'))) 65 | spot_data, option_data2 = [], [] 66 | len_option_data = len(option_data) 67 | index = 0 68 | for k in spot_kline: 69 | k_date = date_func(k[date_key]) 70 | op_date = option_data[index][0] 71 | while k_date > op_date: 72 | # print(f'Warning, miss option kline date: {k_date}', option_data[index]) 73 | index += 1 74 | op_date = option_data[index][0] 75 | if k_date == op_date: 76 | spot_data.append((k_date, float(k[close_key]))) 77 | option_data2.append(option_data[index]) 78 | index += 1 79 | if index >= len_option_data: 80 | break 81 | # print(option_data) 82 | # print(spot_data) 83 | return option_data2, spot_data 84 | 85 | 86 | def cal_historical_iv(option_kline, spot_kline, strike_price, expiry_date, r, option_type, exercise_type): 87 | if exercise_type == 'european': 88 | iv_func = european_option.call_iv if option_type == 'Call' else european_option.put_iv 89 | else: 90 | iv_func = baw.call_iv if option_type == 'Call' else baw.put_iv 91 | x, y, option_cp, spot_cp = [], [], [], [] 92 | for option, spot in zip(option_kline, spot_kline): 93 | x.append(str(option[0])) 94 | t = days_interval(option[0], expiry_date)[1] 95 | y.append(iv_func(option[1], spot[1], strike_price, t, r=r)) 96 | option_cp.append(option[1]) 97 | spot_cp.append(spot[1]) 98 | return x, y, option_cp, spot_cp 99 | 100 | 101 | def draw_picture(option_code, x, iv, option_cp, spot_cp, show=True): 102 | interval = math.ceil(len(x) / 20) 103 | real_x = list(range(len(x))) 104 | x_index = real_x[::-interval] 105 | x_label = x[::-interval] 106 | fig, axs = plt.subplots(2, sharex=True, gridspec_kw={'hspace': 0}, figsize=(12.0, 5.7)) 107 | axs[0].plot(iv, color='r') 108 | axs[0].set_xlim((real_x[0], real_x[-1])) 109 | axs[0].set_ylabel('Implied Volatility') 110 | axs[0].set_title(option_code) 111 | axs[0].grid() 112 | line1 = axs[1].plot(option_cp, 'blue', label='option') 113 | ax2 = axs[1].twinx() 114 | line2 = ax2.plot(spot_cp, 'orange', label='spot') 115 | axs[1].set_xticks(x_index[::-1]) 116 | axs[1].set_xticklabels(x_label[::-1], rotation=60) 117 | axs[1].set_ylabel('Price') 118 | axs[1].grid() 119 | lines = line1 + line2 120 | line_labels = [i.get_label() for i in lines] 121 | axs[1].legend(lines, line_labels, loc=0) 122 | plt.tight_layout() 123 | if show: 124 | plt.show() 125 | else: 126 | buffer = BytesIO() 127 | plt.savefig(buffer, format='png') 128 | return buffer.getvalue() 129 | 130 | 131 | def main(option_code, spot_code, strike_price, expiry_date, option_type, exercise_type): 132 | option_kline, spot_kline = get_kline(option_code, spot_code) 133 | op_k, sp_k = align_kline(option_kline, spot_kline) 134 | x, iv, option_cp, spot_cp = cal_historical_iv(op_k, sp_k, strike_price, expiry_date, 0.03, option_type, exercise_type) 135 | draw_picture(option_code, x, iv, option_cp, spot_cp) 136 | 137 | 138 | if __name__ == '__main__': 139 | # main('cu2003C51000', 'cu2003', 51000.0, '20200224', 'Call', 'european') 140 | # main('au2004P340', 'au2004', 340.0, '20200325', 'Put', 'european') 141 | # main('io2002C4050', '000300', 4050.0, '20200221', 'Call', 'european') 142 | # main('10002194', '510050', 3.1, '20200226', 'Call', 'european') 143 | # main('m2005C2800', 'm2005', 2800.0, '20200408', 'Call', 'american') 144 | main('m2005P2600', 'm2005', 2600.0, '20200408', 'Put', 'american') 145 | # main('ta2005P4800', 'ta2005', 4800.0, '20200403', 'Put', 'american') 146 | -------------------------------------------------------------------------------- /historical_volatility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import math 6 | from io import BytesIO 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from sina_stock_kline_api import get_stock_day_kline, get_ex_data 10 | from sina_future_kline_api import get_future_day_kline 11 | 12 | 13 | ETF_SPOT_MAP = { 14 | 'sh510050': 'sh000016', 15 | 'sh510300': 'sh000300', 16 | 'sz159919': 'sh000300', 17 | } 18 | 19 | 20 | def cal_stock_fluctuation(code, kline, ex): 21 | x, y = [], [] 22 | kline_data = kline[code] 23 | for index, i in enumerate(kline_data): 24 | if index > 0: 25 | y.append(math.log(i['close'] / kline_data[index - 1]['close'])) 26 | x.append(int(''.join(i['date'][:10].split('-')))) 27 | if code in ETF_SPOT_MAP: 28 | listed_date = int(''.join(kline_data[0]['date'][:10].split('-'))) 29 | ex_date = [int(''.join(i['djr'][:10].split('-'))) for i in ex[code] if i['djr']] 30 | ex_date = [i for i in ex_date if i > listed_date][::-1] 31 | ex_result = {} 32 | if ex_date: 33 | spot_kline = kline[ETF_SPOT_MAP[code]] 34 | last_date, last_close = 0, 0.0 35 | for index, i in enumerate(spot_kline): 36 | this_date = int(''.join(i['date'][:10].split('-'))) 37 | if index > 0: 38 | if last_date <= ex_date[0] < this_date: 39 | ex_result[this_date] = math.log(i['close'] / last_close) 40 | ex_date = ex_date[1:] 41 | if not ex_date: 42 | break 43 | last_date = this_date 44 | last_close = i['close'] 45 | # print(ex_result) 46 | for index, i in enumerate(x): 47 | if i in ex_result: 48 | # print(i, y[index], ex_result[i]) 49 | y[index] = ex_result[i] 50 | return x, y 51 | 52 | 53 | def get_stock_data(code): 54 | kline = {code: get_stock_day_kline(code)} 55 | if code in ETF_SPOT_MAP: 56 | kline[ETF_SPOT_MAP[code]] = get_stock_day_kline(ETF_SPOT_MAP[code]) 57 | ex = {code: get_ex_data(code)} 58 | else: 59 | ex = {code: []} 60 | return cal_stock_fluctuation(code, kline, ex) 61 | 62 | 63 | def cal_future_fluctuation(kline): 64 | x, y = [], [] 65 | if kline: 66 | last_close = float(kline[0]['c']) 67 | for k in kline[1:]: 68 | x.append(int(''.join(k['d'].split('-')))) 69 | close = float(k['c']) 70 | y.append(math.log(close / last_close)) 71 | last_close = close 72 | return x, y 73 | 74 | 75 | def get_future_data(code): 76 | return cal_future_fluctuation(get_future_day_kline(code)) 77 | 78 | 79 | def cal_historical_volatility(y, window_size): 80 | y2 = y[::-1] 81 | hv_lines, hv_cone = [], [] 82 | factor = np.sqrt(252) * 100.0 83 | for w in window_size: 84 | hv = [np.std(y2[i: i + w]) * factor for i in range(len(y2) - w + 1)] 85 | hv_lines.append(hv) 86 | # hv_cone.append((max(hv), np.percentile(hv, 75), np.median(hv), np.percentile(hv, 25), min(hv), hv[0])) 87 | return hv_lines, hv_cone 88 | 89 | 90 | def draw_picture(code, x, y, interval, window_size, show=True): 91 | hv_lines, hv_cone = cal_historical_volatility(y, window_size) 92 | x_int = list(range(len(x))) 93 | len_window = len(window_size) 94 | fig, axs = plt.subplots(2, len_window, sharey=True, gridspec_kw={'hspace': 0, 'wspace': 0}, figsize=(13, 6.4)) 95 | ylim = None 96 | for i in range(len_window): 97 | axs[0, i].hist(hv_lines[i], orientation='horizontal', bins=30, alpha=0.6, color='Orange') 98 | axs[0, i].axhline(hv_lines[i][0], color='r') 99 | axs[0, i].set_title(str(window_size[i])) 100 | if ylim is None: 101 | ylim = axs[0, i].get_ylim() 102 | else: 103 | axs[0, i].set_ylim(ylim) 104 | axs[0, i].get_xaxis().set_visible(False) 105 | axs[1, i].axis('off') 106 | axs[0, 0].set_ylabel('historical volatility(%)') 107 | axs2 = fig.subplots(2, 1, gridspec_kw={'hspace': 0, 'wspace': 0}) 108 | axs2[0].axis('off') 109 | for hv in hv_lines: 110 | x_hv = x_int[-len(hv):] 111 | axs2[1].plot(x_hv, hv[::-1]) 112 | x_hv = x_int[-len(hv_lines[0]):] 113 | axs2[1].set_xlim((min(x_hv), max(x_hv))) 114 | axs2[1].legend([str(i) for i in window_size]) 115 | xticks = x[-len(hv_lines[0]):][::-interval][::-1] 116 | xticks_index = x_hv[::-interval][::-1] 117 | axs2[1].set_xticks(xticks_index) 118 | axs2[1].set_xticklabels([str(i) for i in xticks], rotation=60) 119 | axs2[1].set_ylabel('historical volatility(%)') 120 | axs2[1].set_xlabel(f'historical volatility of {code} in different window size') 121 | plt.tight_layout() 122 | if show: 123 | plt.show() 124 | else: 125 | buffer = BytesIO() 126 | plt.savefig(buffer, format='png') 127 | return buffer.getvalue() 128 | 129 | 130 | def main(code, security_type='stock', window_size=(5, 15, 30, 50, 70, 90, 120, 150)): 131 | # import pickle, os 132 | # if os.path.isfile('cache'): 133 | # with open('cache', 'rb') as fp: 134 | # x = pickle.load(fp) 135 | # y = pickle.load(fp) 136 | # hv_lines = pickle.load(fp) 137 | # hv_cone = pickle.load(fp) 138 | # else: 139 | # x, y = get_stock_data(code) 140 | # hv_lines, hv_cone = cal_historical_volatility(y, window_size) 141 | # with open('cache', 'wb') as fp: 142 | # pickle.dump(x, fp) 143 | # pickle.dump(y, fp) 144 | # pickle.dump(hv_lines, fp) 145 | # pickle.dump(hv_cone, fp) 146 | if security_type == 'stock': 147 | x, y = get_stock_data(code) 148 | elif security_type == 'future': 149 | x, y = get_future_data(code) 150 | else: 151 | return 152 | interval = math.ceil(len(x) / 20) 153 | draw_picture(code, x, y, interval, window_size, show=True) 154 | 155 | 156 | if __name__ == '__main__': 157 | # main('sz159919') 158 | # main('sh000300') 159 | main('m2005', security_type='future', window_size=(5, 15, 30, 50, 90, 120)) 160 | 161 | -------------------------------------------------------------------------------- /hiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfl666/option_tools/0922211844573c3a6e2036b73eabe32798327508/hiv.png -------------------------------------------------------------------------------- /hv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfl666/option_tools/0922211844573c3a6e2036b73eabe32798327508/hv.png -------------------------------------------------------------------------------- /iv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfl666/option_tools/0922211844573c3a6e2036b73eabe32798327508/iv.png -------------------------------------------------------------------------------- /min1_iv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfl666/option_tools/0922211844573c3a6e2036b73eabe32798327508/min1_iv.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## ETF期权隐含波动率曲面和希腊字母 2 | 用新浪财经的ETF期权数据(目前支持50ETF期权和上交所300ETF期权) 3 | 画出的隐含波动率曲面和希腊字母,能随着行情变化更新。 4 | 5 | ![图1](https://github.com/sfl666/50ETF_option/blob/master/iv.png) 6 | 7 | ## 标的历史波动率 8 | 支持ETF期权、指数期权和商品期权。 9 | 10 | ![图2](https://github.com/sfl666/50ETF_option/blob/master/hv.png) 11 | 12 | ## 期权历史的隐含波动率 13 | 不支持深交所300ETF期权和部分不活跃的商品期权。 14 | 15 | ![图3](https://github.com/sfl666/50ETF_option/blob/master/hiv.png) 16 | 17 | ## 期权最近交易日1分钟线级别的隐含波动率 18 | 只支持上交所期权。 19 | 20 | ![图4](https://github.com/sfl666/50ETF_option/blob/master/min1_iv.png) 21 | -------------------------------------------------------------------------------- /sina_commodity_option_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import json 6 | import requests 7 | 8 | 9 | http_header = { 10 | 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " 11 | "Chrome/97.0.4692.71 Safari/537.36", 12 | 'Referer': "https://stock.finance.sina.com.cn/", 13 | } 14 | 15 | 16 | PIN_ZHONG_PARAMS = { 17 | 'io': {'product': 'io', 'exchange': 'cffex'}, 18 | 'm': {'product': 'm_o', 'exchange': 'dce'}, 19 | 'c': {'product': 'c_o', 'exchange': 'dce'}, 20 | 'i': {'product': 'i_o', 'exchange': 'dce'}, 21 | 'cf': {'product': 'cf', 'exchange': 'czce'}, 22 | 'sr': {'product': 'sr', 'exchange': 'czce'}, 23 | 'ta': {'product': 'ta', 'exchange': 'czce'}, 24 | 'ma': {'product': 'ma', 'exchange': 'czce'}, 25 | 'ru': {'product': 'ru_o', 'exchange': 'shfe'}, 26 | 'cu': {'product': 'cu_o', 'exchange': 'shfe'}, 27 | 'au': {'product': 'au_o', 'exchange': 'shfe'}, 28 | 'rm': {'product': 'rm', 'exchange': 'czce'}, 29 | } 30 | URL_T_QUOTATION = "http://stock.finance.sina.com.cn/futures/api/openapi.php/OptionService.getOptionData?" \ 31 | "type=futures&product={product}&exchange={exchange}&pinzhong={code}" 32 | URL_KLINE = "https://stock.finance.sina.com.cn/futures/api/jsonp.php//" \ 33 | "FutureOptionAllService.getOptionDayline?symbol={code}" 34 | URL_PRICE = "https://hq.sinajs.cn/etag.php?list=P_OP_{code}" 35 | URL_UNDERLYING_PRICE = "http://hq.sinajs.cn/list={code}" 36 | URL_UNDERLYING_PRICE2 = "http://hq.sinajs.cn/list=nf_{code}" 37 | URL_000300 = "http://hq.sinajs.cn/list=sh000300" 38 | 39 | 40 | def get_t_quotation(code): 41 | """获取T型报价数据""" 42 | p = ''.join(filter(str.isalpha, code)) 43 | data = requests.get(URL_T_QUOTATION.format(code=code, **PIN_ZHONG_PARAMS[p])).json()['result']['data'] 44 | up = data['up'] if 'up' in data else [] 45 | down = data['down'] if 'down' in data else [] 46 | for i in down: 47 | s = [] 48 | for j in i[-1][::-1]: 49 | if j.isdigit(): 50 | s.append(j) 51 | else: 52 | break 53 | strike_price = ''.join(s[::-1]) 54 | i.insert(-1, strike_price) 55 | return up, down 56 | 57 | 58 | def get_option_kline(code): 59 | """获取日K线数据""" 60 | return json.loads(requests.get(URL_KLINE.format(code=code)).content.split(b'(')[1].split(b')')[0]) 61 | 62 | 63 | def get_option_price(code): 64 | """获取实时行情数据""" 65 | data = requests.get(URL_PRICE.format(code=code), headers=http_header).content.split(b'"')[1].decode().split(',') 66 | return data 67 | 68 | 69 | def get_underlying_price(code): 70 | """获取标的(期货)实时行情""" 71 | return requests.get(URL_UNDERLYING_PRICE.format(code=code), headers=http_header).content.split(b'"')[1].decode('gbk').split(',') 72 | 73 | 74 | def get_underlying_price2(code): 75 | """获取标的(期货)实时行情""" 76 | return requests.get(URL_UNDERLYING_PRICE2.format(code=code), headers=http_header).content.split(b'"')[1].decode('gbk').split(',') 77 | 78 | 79 | def get_000300_price(): 80 | """获取指数000300实时行情""" 81 | return requests.get(URL_000300, headers=http_header).content.split(b'"')[1].decode('gbk').split(',') 82 | 83 | 84 | def my_test(): 85 | header = ['买量', '买价', '最新价', '卖价', '卖量', '持仓量', '涨跌(%)', '行权价', '代码'] 86 | up, down = get_t_quotation('io2002') 87 | for i in up + down: 88 | print(list(zip(header, i))) 89 | day_kline = get_option_kline('m2005C2400') 90 | print() 91 | for i in day_kline: 92 | print('日期:{d}, 开:{o}, 高:{h}, 低:{l}, 收:{c}, 成交:{v}'.format(**i)) 93 | header2 = ['买量', '买价', '最新价', '卖价', '卖量', '持仓量', '涨幅', '行权价', '昨收价', '开盘价', '涨停价', 94 | '跌停价', '申卖价五', '申卖量五', '申卖价四', '申卖量四', '申卖价三', '申卖量三', '申卖价二', 95 | '申卖量二', '申卖价一', '申卖量一', '申买价一', '申买量一', '申买价二', '申买量二', '申买价三', 96 | '申买量三', '申买价四', '申买量四', '申买价五', '申买量五', '行情时间', '主力合约标识', '状态码', 97 | '标的证券类型', '标的股票', '期权合约简称', '振幅', '最高价', '最低价', '成交量', '成交额'] 98 | price_data = get_option_price('io2002C4000') 99 | print() 100 | for i in zip(header2, price_data): 101 | print(i) 102 | header3 = ['期货名称', '现在交易时间', '开盘价', '最高价', '最低价', '(昨?)收盘价', '竞买价', '竞卖价', '最新价', 103 | '动态结算价', '昨日结算价', '买量', '卖量', '持仓量', '成交量', '交易所', '品种', '日期', '是否热门', 104 | '5天最高', '5天最低', '10天最高', '10天最低', '20天最高', '20天最低', '55天最高', '55天最低', '加权平均'] 105 | future_data = get_underlying_price('M2005') 106 | print() 107 | for i in zip(header3, future_data): 108 | print(i) 109 | header4 = ['股票名字', '今日开盘价', '昨日收盘价', '当前价格', '今日最高价', '今日最低价', '竞买价', '竞卖价', 110 | '成交的股票数', '成交金额', '买一量', '买一价', '买二量', '买二价', '买三量', '买三价', '买四量', '买四价', 111 | '买五量', '买五价', '卖一量', '卖一价', '卖二量', '卖二价', '卖三量', '卖三价', '卖四量', '卖四价', 112 | '卖五量', '卖五价', '日期', '时间'] 113 | price_000300 = get_000300_price() 114 | print() 115 | for i in zip(header4, price_000300): 116 | print(i) 117 | 118 | 119 | if __name__ == '__main__': 120 | my_test() 121 | # print(get_option_price('m2005C2400')) 122 | 123 | -------------------------------------------------------------------------------- /sina_commodity_option_spider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import requests 6 | 7 | 8 | URL = "https://stock.finance.sina.com.cn/futures/view/optionsDP.php/{product}/{exchange}" 9 | 10 | 11 | def get_instruments(product, exchange): 12 | data = requests.get(URL.format(product=product, exchange=exchange)).content 13 | data1 = data[data.find(b'option_suffix'):] 14 | data2 = data1[:data1.find(b'')].split(b'') 15 | instruments = sorted(set(i[i.rfind(b'>') + 1:].decode().lower() for i in data2[:-1]), key=lambda x: int(x[-4:])) 16 | return instruments 17 | 18 | 19 | if __name__ == '__main__': 20 | print(get_instruments('ma', 'czce')) 21 | 22 | -------------------------------------------------------------------------------- /sina_etf_option_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | from json import loads 6 | from requests import get 7 | 8 | http_header = { 9 | 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " 10 | "Chrome/97.0.4692.71 Safari/537.36", 11 | 'Referer': "https://stock.finance.sina.com.cn/", 12 | } 13 | 14 | 15 | def get_option_dates(cate='50ETF', exchange='null'): 16 | url = f"http://stock.finance.sina.com.cn/futures/api/openapi.php/StockOptionService.getStockName?" \ 17 | f"exchange={exchange}&cate={cate}" 18 | dates = get(url).json()['result']['data']['contractMonth'] 19 | return [''.join(i.split('-')) for i in dates][1:] 20 | 21 | 22 | def get_option_expire_day(date, cate='50ETF', exchange='null'): 23 | url = "http://stock.finance.sina.com.cn/futures/api/openapi.php/StockOptionService.getRemainderDay?" \ 24 | "exchange={exchange}&cate={cate}&date={year}-{month}" 25 | url2 = url.format(year=date[:4], month=date[4:], cate=cate, exchange=exchange) 26 | data = get(url2).json()['result']['data'] 27 | if int(data['remainderDays']) < 0: 28 | url2 = url.format(year=date[:4], month=date[4:], cate='XD' + cate, exchange=exchange) 29 | data = get(url2).json()['result']['data'] 30 | return data['expireDay'], int(data['remainderDays']) 31 | 32 | 33 | def get_option_codes(date, underlying='510050'): 34 | url_up = ''.join(["http://hq.sinajs.cn/list=OP_UP_", underlying, str(date)[-4:]]) 35 | url_down = ''.join(["http://hq.sinajs.cn/list=OP_DOWN_", underlying, str(date)[-4:]]) 36 | data_up = str(get(url_up, headers=http_header).content).replace('"', ',').split(',') 37 | codes_up = [i[7:] for i in data_up if i.startswith('CON_OP_')] 38 | data_down = str(get(url_down, headers=http_header).content).replace('"', ',').split(',') 39 | codes_down = [i[7:] for i in data_down if i.startswith('CON_OP_')] 40 | return codes_up, codes_down 41 | 42 | 43 | def get_option_price(code): 44 | url = "http://hq.sinajs.cn/list=CON_OP_{code}".format(code=code) 45 | data = get(url, headers=http_header).content.decode('gbk') 46 | data = data[data.find('"') + 1: data.rfind('"')].split(',') 47 | fields = ['买量', '买价', '最新价', '卖价', '卖量', '持仓量', '涨幅', '行权价', '昨收价', '开盘价', '涨停价', 48 | '跌停价', '申卖价五', '申卖量五', '申卖价四', '申卖量四', '申卖价三', '申卖量三', '申卖价二', 49 | '申卖量二', '申卖价一', '申卖量一', '申买价一', '申买量一 ', '申买价二', '申买量二', '申买价三', 50 | '申买量三', '申买价四', '申买量四', '申买价五', '申买量五', '行情时间', '主力合约标识', '状态码', 51 | '标的证券类型', '标的股票', '期权合约简称', '振幅', '最高价', '最低价', '成交量', '成交额', 52 | '分红调整标志', '昨结算价', '认购认沽标志', '到期日', '剩余天数', '虚实值标志', '内在价值', '时间价值'] 53 | result = list(zip(fields, data)) 54 | return result 55 | 56 | 57 | def get_underlying_security_price(code='sh510050'): 58 | url = "http://hq.sinajs.cn/list=" + code 59 | data = get(url, headers=http_header).content.decode('gbk') 60 | data = data[data.find('"') + 1: data.rfind('"')].split(',') 61 | fields = ['证券简称', '今日开盘价', '昨日收盘价', '最近成交价', '最高成交价', '最低成交价', '买入价', 62 | '卖出价', '成交数量', '成交金额', '买数量一', '买价位一', '买数量二', '买价位二', '买数量三', 63 | '买价位三', '买数量四', '买价位四', '买数量五', '买价位五', '卖数量一', '卖价位一', '卖数量二', 64 | '卖价位二', '卖数量三', '卖价位三', '卖数量四', '卖价位四', '卖数量五', '卖价位五', '行情日期', 65 | '行情时间', '停牌状态'] 66 | return list(zip(fields, data)) 67 | 68 | 69 | def get_option_greek_alphabet(code): 70 | url = "http://hq.sinajs.cn/list=CON_SO_{code}".format(code=code) 71 | data = get(url, headers=http_header).content.decode('gbk') 72 | data = data[data.find('"') + 1: data.rfind('"')].split(',') 73 | fields = ['期权合约简称', '成交量', 'Delta', 'Gamma', 'Theta', 'Vega', '隐含波动率', '最高价', '最低价', 74 | '交易代码', '行权价', '最新价', '理论价值'] 75 | return list(zip(fields, [data[0]] + data[4:])) 76 | 77 | 78 | def get_option_time_line(code): 79 | url = f"https://stock.finance.sina.com.cn/futures/api/openapi.php/StockOptionDaylineService.getOptionMinline?" \ 80 | f"symbol=CON_OP_{code}" 81 | data = get(url).json()['result']['data'] 82 | return data 83 | 84 | 85 | def get_option_day_kline(code): 86 | url = f"http://stock.finance.sina.com.cn/futures/api/jsonp_v2.php//StockOptionDaylineService.getSymbolInfo?" \ 87 | f"symbol=CON_OP_{code}" 88 | data = get(url).content 89 | data = loads(data[data.find(b'(') + 1: data.rfind(b')')]) 90 | return data 91 | 92 | 93 | def my_test(): 94 | dates = get_option_dates(cate='300ETF') 95 | print('期权合约月份:{}'.format(dates)) 96 | for date in dates: 97 | print('期权月份{}:到期日{} 剩余天数{}'.format(date, *get_option_expire_day(date, cate='300ETF'))) 98 | demo_code = '10002180' 99 | for date in dates: 100 | call_codes, put_codes = get_option_codes(date, underlying='510300') 101 | print('期权月份{} 看涨期权代码:{}\n期权月份{} 看跌期权代码:{}'.format(date, call_codes, date, put_codes)) 102 | demo_code = call_codes[0] 103 | for index, i in enumerate(get_option_price(demo_code)): 104 | print('期权' + demo_code, index, *i) 105 | for index, i in enumerate(get_underlying_security_price(code='sh510300')): 106 | print(index, *i) 107 | for index, i in enumerate(get_option_greek_alphabet(demo_code)): 108 | print('期权' + demo_code, index, *i) 109 | time_line = get_option_time_line(demo_code) 110 | for i in time_line[-10:]: 111 | print('时间:{i}, 价格:{p}, 成交:{v}, 持仓:{t}, 均价:{a}'.format(**i)) 112 | day_kline = get_option_day_kline(demo_code) 113 | for i in day_kline: 114 | print('日期:{d}, 开盘:{o}, 最高:{h}, 最低:{l}, 收盘:{c}, 成交:{v}'.format(**i)) 115 | 116 | 117 | if __name__ == '__main__': 118 | my_test() 119 | -------------------------------------------------------------------------------- /sina_future_kline_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import json 6 | import requests 7 | 8 | 9 | URL_KLINE = "https://stock2.finance.sina.com.cn/futures/api/jsonp.php//" \ 10 | "InnerFuturesNewService.getDailyKLine?symbol={code}" 11 | 12 | 13 | def get_future_day_kline(code): 14 | # 日期, 开, 高, 低, 收, 成交量, 持仓量 15 | data = requests.get(URL_KLINE.format(code=code)).content 16 | kline = json.loads(data[data.find(b'(') + 1: data.rfind(b')')]) 17 | # for i in kline: 18 | # print(i) 19 | # print(type(kline[0]['o'])) 20 | return kline 21 | 22 | 23 | if __name__ == '__main__': 24 | get_future_day_kline('M2005') 25 | 26 | -------------------------------------------------------------------------------- /sina_stock_kline_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | # 股票K线接口代码copy自AkShare项目https://github.com/jindaxiang/akshare 6 | import re 7 | import base64 8 | import struct 9 | import requests 10 | import execjs 11 | 12 | 13 | http_header = { 14 | 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " 15 | "Chrome/97.0.4692.71 Safari/537.36", 16 | 'Referer': "https://stock.finance.sina.com.cn/", 17 | } 18 | 19 | 20 | hk_js_decode = """ 21 | function d(t) { 22 | var e, i, n, r, a, o, s, l = (arguments, 23 | 864e5), u = 7657, c = [], h = [], d = ~(3 << 30), f = 1 << 30, 24 | p = [0, 3, 5, 6, 9, 10, 12, 15, 17, 18, 20, 23, 24, 27, 29, 30], m = Math, g = function () { 25 | var l, u; 26 | for (l = 0; 64 > l; l++) 27 | h[l] = m.pow(2, l), 28 | 26 > l && (c[l] = v(l + 65), 29 | c[l + 26] = v(l + 97), 30 | 10 > l && (c[l + 52] = v(l + 48))); 31 | for (c.push("+", "/"), 32 | c = c.join(""), 33 | i = t.split(""), 34 | n = i.length, 35 | l = 0; n > l; l++) 36 | i[l] = c.indexOf(i[l]); 37 | return r = {}, 38 | e = o = 0, 39 | a = {}, 40 | u = w([12, 6]), 41 | s = 63 ^ u[1], 42 | { 43 | _1479: T, 44 | _136: _, 45 | _200: S, 46 | _139: k, 47 | _197: _mi_run 48 | }["_" + u[0]] || function () { 49 | return [] 50 | } 51 | }, v = String.fromCharCode, b = function (t) { 52 | return t === {}._ 53 | }, N = function () { 54 | var t, e; 55 | for (t = y(), 56 | e = 1; ;) { 57 | if (!y()) 58 | return e * (2 * t - 1); 59 | e++ 60 | } 61 | }, y = function () { 62 | var t; 63 | return e >= n ? 0 : (t = i[e] & 1 << o, 64 | o++, 65 | o >= 6 && (o -= 6, 66 | e++), 67 | !!t) 68 | }, w = function (t, r, a) { 69 | var s, l, u, c, d; 70 | for (l = [], 71 | u = 0, 72 | r || (r = []), 73 | a || (a = []), 74 | s = 0; s < t.length; s++) 75 | if (c = t[s], 76 | u = 0, 77 | c) { 78 | if (e >= n) 79 | return l; 80 | if (t[s] <= 0) 81 | u = 0; 82 | else if (t[s] <= 30) { 83 | for (; d = 6 - o, 84 | d = c > d ? d : c, 85 | u |= (i[e] >> o & (1 << d) - 1) << t[s] - c, 86 | o += d, 87 | o >= 6 && (o -= 6, 88 | e++), 89 | c -= d, 90 | !(0 >= c);) 91 | ; 92 | r[s] && u >= h[t[s] - 1] && (u -= h[t[s]]) 93 | } else 94 | u = w([30, t[s] - 30], [0, r[s]]), 95 | a[s] || (u = u[0] + u[1] * h[30]); 96 | l[s] = u 97 | } else 98 | l[s] = 0; 99 | return l 100 | }, x = function (t) { 101 | var e, i, n; 102 | for (t > 1 && (e = 0), 103 | e = 0; t > e; e++) 104 | r.d++, 105 | n = r.d % 7, 106 | (3 == n || 4 == n) && (r.d += 5 - n); 107 | return i = new Date, 108 | i.setTime((u + r.d) * l), 109 | i 110 | }, S = function () { 111 | var t, i, a, o, l; 112 | if (s >= 1) 113 | return []; 114 | for (r.d = w([18], [1])[0] - 1, 115 | a = w([3, 3, 30, 6]), 116 | r.p = a[0], 117 | r.ld = a[1], 118 | r.cd = a[2], 119 | r.c = a[3], 120 | r.m = m.pow(10, r.p), 121 | r.pc = r.cd / r.m, 122 | i = [], 123 | t = 0; o = { 124 | d: 1 125 | }, 126 | y() && (a = w([3])[0], 127 | 0 == a ? o.d = w([6])[0] : 1 == a ? (r.d = w([18])[0], 128 | o.d = 0) : o.d = a), 129 | l = { 130 | day: x(o.d) 131 | }, 132 | y() && (r.ld += N()), 133 | a = w([3 * r.ld], [1]), 134 | r.cd += a[0], 135 | l.close = r.cd / r.m, 136 | i.push(l), 137 | !(e >= n) && (e != n - 1 || 63 & (r.c ^ t + 1)); t++) 138 | ; 139 | return i[0].prevclose = r.pc, 140 | i 141 | }, _ = function () { 142 | var t, i, a, o, l, u, c, h, d, f, p; 143 | if (s > 2) 144 | return []; 145 | for (c = [], 146 | d = { 147 | v: "volume", 148 | p: "price", 149 | a: "avg_price" 150 | }, 151 | r.d = w([18], [1])[0] - 1, 152 | h = { 153 | day: x(1) 154 | }, 155 | a = w(1 > s ? [3, 3, 4, 1, 1, 1, 5] : [4, 4, 4, 1, 1, 1, 3]), 156 | t = 0; 7 > t; t++) 157 | r[["la", "lp", "lv", "tv", "rv", "zv", "pp"][t]] = a[t]; 158 | for (r.m = m.pow(10, r.pp), 159 | s >= 1 ? (a = w([3, 3]), 160 | r.c = a[0], 161 | a = a[1]) : (a = 5, 162 | r.c = 2), 163 | r.pc = w([6 * a])[0], 164 | h.pc = r.pc / r.m, 165 | r.cp = r.pc, 166 | r.da = 0, 167 | r.sa = r.sv = 0, 168 | t = 0; !(e >= n) && (e != n - 1 || 7 & (r.c ^ t)); t++) { 169 | for (l = {}, 170 | o = {}, 171 | f = r.tv ? y() : 1, 172 | i = 0; 3 > i; i++) 173 | if (p = ["v", "p", "a"][i], 174 | (f ? y() : 0) && (a = N(), 175 | r["l" + p] += a), 176 | u = "v" == p && r.rv ? y() : 1, 177 | a = w([3 * r["l" + p] + ("v" == p ? 7 * u : 0)], [!!i])[0] * (u ? 1 : 100), 178 | o[p] = a, 179 | "v" == p) { 180 | if (!(l[d[p]] = a) && (s > 1 || 241 > t) && (r.zv ? !y() : 1)) { 181 | o.p = 0; 182 | break 183 | } 184 | } else 185 | "a" == p && (r.da = (1 > s ? 0 : r.da) + o.a); 186 | r.sv += o.v, 187 | l[d.p] = (r.cp += o.p) / r.m, 188 | r.sa += o.v * r.cp, 189 | l[d.a] = b(o.a) ? t ? c[t - 1][d.a] : l[d.p] : r.sv ? ((m.floor((r.sa * (2e3 / r.m) + r.sv) / r.sv) >> 1) + r.da) / 1e3 : l[d.p] + r.da / 1e3, 190 | c.push(l) 191 | } 192 | return c[0].date = h.day, 193 | c[0].prevclose = h.pc, 194 | c 195 | }, T = function () { 196 | var t, e, i, n, a, o, l; 197 | if (s >= 1) 198 | return []; 199 | for (r.lv = 0, 200 | r.ld = 0, 201 | r.cd = 0, 202 | r.cv = [0, 0], 203 | r.p = w([6])[0], 204 | r.d = w([18], [1])[0] - 1, 205 | r.m = m.pow(10, r.p), 206 | a = w([3, 3]), 207 | r.md = a[0], 208 | r.mv = a[1], 209 | t = []; a = w([6]), 210 | a.length;) { 211 | if (i = { 212 | c: a[0] 213 | }, 214 | n = {}, 215 | i.d = 1, 216 | 32 & i.c) 217 | for (; ;) { 218 | if (a = w([6])[0], 219 | 63 == (16 | a)) { 220 | l = 16 & a ? "x" : "u", 221 | a = w([3, 3]), 222 | i[l + "_d"] = a[0] + r.md, 223 | i[l + "_v"] = a[1] + r.mv; 224 | break 225 | } 226 | if (32 & a) { 227 | o = 8 & a ? "d" : "v", 228 | l = 16 & a ? "x" : "u", 229 | i[l + "_" + o] = (7 & a) + r["m" + o]; 230 | break 231 | } 232 | if (o = 15 & a, 233 | 0 == o ? i.d = w([6])[0] : 1 == o ? (r.d = o = w([18])[0], 234 | i.d = 0) : i.d = o, 235 | !(16 & a)) 236 | break 237 | } 238 | n.date = x(i.d); 239 | for (o in { 240 | v: 0, 241 | d: 0 242 | }) 243 | b(i["x_" + o]) || (r["l" + o] = i["x_" + o]), 244 | b(i["u_" + o]) && (i["u_" + o] = r["l" + o]); 245 | for (i.l_l = [i.u_d, i.u_d, i.u_d, i.u_d, i.u_v], 246 | l = p[15 & i.c], 247 | 1 & i.u_v && (l = 31 - l), 248 | 16 & i.c && (i.l_l[4] += 2), 249 | e = 0; 5 > e; e++) 250 | l & 1 << 4 - e && i.l_l[e]++, 251 | i.l_l[e] *= 3; 252 | i.d_v = w(i.l_l, [1, 0, 0, 1, 1], [0, 0, 0, 0, 1]), 253 | o = r.cd + i.d_v[0], 254 | n.open = o / r.m, 255 | n.high = (o + i.d_v[1]) / r.m, 256 | n.low = (o - i.d_v[2]) / r.m, 257 | n.close = (o + i.d_v[3]) / r.m, 258 | a = i.d_v[4], 259 | "number" == typeof a && (a = [a, a >= 0 ? 0 : -1]), 260 | r.cd = o + i.d_v[3], 261 | l = r.cv[0] + a[0], 262 | r.cv = [l & d, r.cv[1] + a[1] + !!((r.cv[0] & d) + (a[0] & d) & f)], 263 | n.volume = (r.cv[0] & f - 1) + r.cv[1] * f, 264 | t.push(n) 265 | } 266 | return t 267 | }, k = function () { 268 | var t, e, i, n; 269 | if (s > 1) 270 | return []; 271 | for (r.l = 0, 272 | n = -1, 273 | r.d = w([18])[0] - 1, 274 | i = w([18])[0]; r.d < i;) 275 | e = x(1), 276 | 0 >= n ? (y() && (r.l += N()), 277 | n = w([3 * r.l], [0])[0] + 1, 278 | t || (t = [e], 279 | n--)) : t.push(e), 280 | n--; 281 | return t 282 | }; 283 | return _mi_run = function () { 284 | var t, i, a, o; 285 | if (s >= 1) 286 | return []; 287 | for (r.f = w([6])[0], 288 | r.c = w([6])[0], 289 | a = [], 290 | r.dv = [], 291 | r.dl = [], 292 | t = 0; t < r.f; t++) 293 | r.dv[t] = 0, 294 | r.dl[t] = 0; 295 | for (t = 0; !(e >= n) && (e != n - 1 || 7 & (r.c ^ t)); t++) { 296 | for (o = [], 297 | i = 0; i < r.f; i++) 298 | y() && (r.dl[i] += N()), 299 | r.dv[i] += w([3 * r.dl[i]], [1])[0], 300 | o[i] = r.dv[i]; 301 | a.push(o) 302 | } 303 | return a 304 | } 305 | , 306 | g()() 307 | } 308 | """ 309 | 310 | zh_sina_a_stock_hist_url = "https://finance.sina.com.cn/realstock/company/{}/hisdata/klc_kl.js" 311 | js_code = execjs.compile(hk_js_decode) 312 | 313 | 314 | def get_stock_day_kline(code): 315 | res = requests.get(zh_sina_a_stock_hist_url.format(code)) 316 | dict_list = js_code.call('d', res.text.split("=")[1].split(";")[0].replace('"', "")) 317 | return dict_list 318 | 319 | 320 | URL_EX = "http://stock.finance.sina.com.cn/fundInfo/api/openapi.php/FundPageInfoService.tabfh?symbol={code}&format=json" 321 | 322 | 323 | def get_ex_data(code): 324 | tmp = code[2:] if code[:2] in ('sh', 'sz') else code 325 | data = requests.get(URL_EX.format(code=tmp)).json()['result']['data']['fhdata'] 326 | # print(data) 327 | return data 328 | 329 | 330 | def get_1minutes(t=(('9:30:00', '11:30:00'), ('13:00:00', '15:00:00'))): 331 | # t = [('9:30:00', '11:30:00'), ('13:00:00', '15:00:00')] 332 | res, result = [], [] 333 | for begin, end in t: 334 | b = begin.split(':') 335 | b = int(b[0]) * 60 + int(b[1]) 336 | e = end.split(':') 337 | e = int(e[0]) * 60 + int(e[1]) 338 | res.extend(list(range(b, e + 1))) 339 | for index, i in enumerate(res): 340 | hour, minute = divmod(i, 60) 341 | result.append(':'.join([str(hour).rjust(2, '0'), str(minute).rjust(2, '0'), '00'])) 342 | return result 343 | 344 | 345 | def get_stock_time_line(code): 346 | """股票分时线(1分钟线)""" 347 | minutes = get_1minutes() 348 | content = requests.get(f'http://hq.sinajs.cn/list=ml_{code}', headers=http_header).content.decode() 349 | patt = re.compile(r'\"(.*)\"') 350 | m = patt.search(content) 351 | start = m.start() + 1 352 | end = m.end() - 1 353 | result = [] 354 | n = 0 355 | while start < end: 356 | tmp = (content[start:start+16]) 357 | start += 16 358 | b = base64.b64decode(tmp) 359 | avg = struct.unpack(' option_time and index < len_option_line - 1: 39 | index += 1 40 | # print('#########', len(option_line), len(spot_line), index) 41 | option_time = time_str_to_int(option_line[index]['i']) 42 | if spot_time == option_time: 43 | times.append(i[3]) 44 | spot_price.append(i[1] if i[1] > 0.00001 else math.nan) 45 | tmp_price = float(option_line[index]['p']) 46 | option_price.append(tmp_price if tmp_price > 0.00001 else math.nan) 47 | return times, option_price, spot_price 48 | 49 | 50 | def cal_iv(option_price, spot_price, k, t, option_type): 51 | iv_func = european_option.call_iv if option_type == 'Call' else european_option.put_iv 52 | return [iv_func(i, j, k, t) if (i > 0.00001 and j > 0.00001) else math.nan for i, j in zip(option_price, spot_price)] 53 | 54 | 55 | def draw_picture(times, option_price, spot_price, iv, option_code, show=True): 56 | interval = math.ceil(len(times) / 20.0) 57 | x = list(range(len(times))) 58 | fig, axs = plt.subplots(2, sharex=True, gridspec_kw={'hspace': 0}, figsize=(12.0, 5.7)) 59 | axs[0].plot(iv, color='r') 60 | axs[0].set_xlim((x[0], x[-1])) 61 | axs[0].set_ylabel('Implied Volatility') 62 | axs[0].set_title(option_code) 63 | axs[0].grid() 64 | line1 = axs[1].plot(option_price, 'blue', label='option') 65 | ax2 = axs[1].twinx() 66 | line2 = ax2.plot(spot_price, 'orange', label='spot') 67 | axs[1].set_xticks(x[::-interval][::-1]) 68 | axs[1].set_xticklabels(times[::-interval][::-1], rotation=60) 69 | axs[1].set_ylabel('Price') 70 | axs[1].grid() 71 | lines = line1 + line2 72 | line_labels = [i.get_label() for i in lines] 73 | axs[1].legend(lines, line_labels, loc=0) 74 | plt.tight_layout() 75 | if show: 76 | plt.show() 77 | else: 78 | buffer = BytesIO() 79 | plt.savefig(buffer, format='png') 80 | return buffer.getvalue() 81 | 82 | 83 | def main(option_code, spot_code, k, t, option_type, show=True): 84 | if spot_code in SPOT_CODE_MAP: 85 | spot_code = SPOT_CODE_MAP[spot_code] 86 | else: 87 | return None 88 | times, option_price, spot_price = align_line(*get_data(option_code, spot_code)) 89 | if times: 90 | iv = cal_iv(option_price, spot_price, k, t, option_type) 91 | return draw_picture(times, option_price, spot_price, iv, option_code, show) 92 | else: 93 | return None 94 | 95 | 96 | if __name__ == '__main__': 97 | main('10002235', '510050', 3.0, 238.0 / 365.0, 'Call') 98 | 99 | -------------------------------------------------------------------------------- /volatility_surface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | # python3 6 | 7 | from time import sleep 8 | from threading import Thread, Lock 9 | 10 | from requests import get, exceptions 11 | from numpy import polyfit, polyval, meshgrid, array, nan 12 | import matplotlib.pyplot as plt 13 | import matplotlib.gridspec as gridspec 14 | from mpl_toolkits.mplot3d import Axes3D 15 | 16 | import sina_etf_option_api 17 | 18 | 19 | http_header = { 20 | 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " 21 | "Chrome/97.0.4692.71 Safari/537.36", 22 | 'Referer': "https://stock.finance.sina.com.cn/", 23 | } 24 | 25 | COLORS = ['blue', 'yellow', 'lime', 'red', 'purple', 'slategray', 'tomato', 'orange', 'darkred', 'aqua'] 26 | global_ax_lines_call = [{'ax': None, 'lines': []} for _ in range(5)] 27 | global_ax_lines_put = [{'ax': None, 'lines': []} for _ in range(5)] 28 | update_picture_lock = Lock() 29 | ELEV = 30 30 | AZIM = 120 31 | 32 | 33 | def requests_get(all_codes): 34 | url = "http://hq.sinajs.cn/list={codes}".format(codes=all_codes) 35 | while True: 36 | try: 37 | data = get(url, headers=http_header).content.decode('gbk').strip().split('\n') 38 | break 39 | except (exceptions.ConnectionError, exceptions.ConnectTimeout) as e: 40 | print('连接出错,10秒后重试') 41 | print(e) 42 | sleep(10) 43 | return [i.split(',') for i in data] 44 | 45 | 46 | def get_codes(cate, exchange, underlying, dividend): 47 | while True: 48 | try: 49 | dates = sorted(sina_etf_option_api.get_option_dates(cate=cate, exchange=exchange)) 50 | call, put = [], [] 51 | for date in dates: 52 | call_codes, put_codes = sina_etf_option_api.get_option_codes(date, underlying=underlying) 53 | call.append(['CON_SO_' + i for i in call_codes]) 54 | put.append(['CON_SO_' + i for i in put_codes]) 55 | all_codes = ','.join([','.join(i) for i in call] + [','.join(i) for i in put]) 56 | data = requests_get(all_codes) 57 | if dividend: 58 | codes_tmp = [i[0][11:26] for i in data] # 考虑分红 59 | else: 60 | codes_tmp = [i[0][11:26] for i in data if not i[0].endswith('A')] # 不考虑分红 61 | for i in range(len(call)): 62 | call[i] = [j for j in call[i] if j in codes_tmp] 63 | put[i] = [j for j in put[i] if j in codes_tmp] 64 | break 65 | except (exceptions.ConnectionError, exceptions.ConnectTimeout) as e: 66 | print('连接出错,10秒后重试') 67 | print(e) 68 | sleep(10) 69 | return call, put, ','.join(codes_tmp), dates 70 | 71 | 72 | def get_data(call, put, all_codes): 73 | implied_volatility, strike_price, vega, theta, gamma, delta = [], [], [], [], [], [] 74 | for line in requests_get(all_codes): 75 | implied_volatility.append(float(line[9])) 76 | vega.append(float(line[8])) 77 | strike_price.append(float(line[13])) 78 | theta.append(float(line[7])) 79 | gamma.append(float(line[6])) 80 | delta.append(float(line[5])) 81 | call_implied_volatility, call_strike_price, call_vega, call_theta, call_gamma, call_delta = [], [], [], [], [], [] 82 | put_implied_volatility, put_strike_price, put_vega, put_theta, put_gamma, put_delta = [], [], [], [], [], [] 83 | b = 0 84 | for i in call: 85 | len_i = len(i) 86 | call_implied_volatility.append(implied_volatility[b:b + len_i]) 87 | call_strike_price.append(strike_price[b:b + len_i]) 88 | call_vega.append(vega[b:b + len_i]) 89 | call_theta.append(theta[b:b + len_i]) 90 | call_gamma.append(gamma[b:b + len_i]) 91 | call_delta.append(delta[b:b + len_i]) 92 | b += len_i 93 | for i in put: 94 | len_i = len(i) 95 | put_implied_volatility.append(implied_volatility[b:b + len_i]) 96 | put_strike_price.append(strike_price[b:b + len_i]) 97 | put_vega.append(vega[b:b + len_i]) 98 | put_theta.append(theta[b:b + len_i]) 99 | put_gamma.append(gamma[b:b + len_i]) 100 | put_delta.append(delta[b:b + len_i]) 101 | b += len_i 102 | return call_strike_price, [call_delta, call_gamma, call_theta, call_vega, call_implied_volatility], \ 103 | put_strike_price, [put_delta, put_gamma, put_theta, put_vega, put_implied_volatility] 104 | 105 | 106 | def knockout_small_value(x, y): 107 | length = len(x) 108 | new_x = [x[i] for i in range(length) if y[i] > 0.01] 109 | new_y = [i for i in y if i > 0.01] 110 | return new_x, new_y 111 | 112 | 113 | def fit(call_x, call_y, put_x, put_y): 114 | xx = set() 115 | for i in call_x: 116 | xx |= set(i) 117 | xx = sorted(xx) 118 | call_y2, put_y2 = [], [] 119 | for i in range(len(call_x)): 120 | if xx == call_x[i]: 121 | call_y2.append(call_y[i]) 122 | else: 123 | new_x, new_y = knockout_small_value(call_x[i], call_y[i]) 124 | tmp = polyval(polyfit(new_x, new_y, 2), xx) 125 | tmp[tmp < 0.0] = 0.0 126 | tmp_y, index_y = [], 0 127 | for index, j in enumerate(xx): 128 | if j in call_x[i]: 129 | tmp_y.append(call_y[i][index_y]) 130 | index_y += 1 131 | else: 132 | tmp_y.append(tmp[index]) 133 | call_y2.append(tmp_y) 134 | if xx == put_x[i]: 135 | put_y2.append(put_y[i]) 136 | else: 137 | new_x, new_y = knockout_small_value(put_x[i], put_y[i]) 138 | tmp = polyval(polyfit(new_x, new_y, 2), xx) 139 | tmp[tmp < 0.0] = 0.0 140 | tmp_y, index_y = [], 0 141 | for index, j in enumerate(xx): 142 | if j in put_x[i]: 143 | tmp_y.append(put_y[i][index_y]) 144 | index_y += 1 145 | else: 146 | tmp_y.append(tmp[index]) 147 | put_y2.append(tmp_y) 148 | return xx, call_y2, put_y2 149 | 150 | 151 | def not_fit(call_x, call_y, put_x, put_y): 152 | xx = set() 153 | for i in call_x: 154 | xx |= set(i) 155 | xx = sorted(xx) 156 | call_y2, put_y2 = [], [] 157 | for i in range(len(call_x)): 158 | if xx == call_x[i]: 159 | call_y2.append(call_y[i]) 160 | else: 161 | tmp_y, index_y = [], 0 162 | for index, j in enumerate(xx): 163 | if j in call_x[i]: 164 | tmp_y.append(call_y[i][index_y]) 165 | index_y += 1 166 | else: 167 | tmp_y.append(nan) 168 | call_y2.append(tmp_y) 169 | if xx == put_x[i]: 170 | put_y2.append(put_y[i]) 171 | else: 172 | tmp_y, index_y = [], 0 173 | for index, j in enumerate(xx): 174 | if j in put_x[i]: 175 | tmp_y.append(put_y[i][index_y]) 176 | index_y += 1 177 | else: 178 | tmp_y.append(nan) 179 | put_y2.append(tmp_y) 180 | return xx, call_y2, put_y2 181 | 182 | 183 | def update(call_codes, put_codes, all_codes, x, y, yy, surf_call, surf_put, ax_iv_sf_call, ax_iv_sf_put, is_fit): 184 | azim = AZIM 185 | while True: 186 | # sleep(5) # 每隔5秒刷新一次 187 | sleep(10) 188 | with update_picture_lock: 189 | call_x, call_ys, put_x, put_ys = get_data(call_codes, put_codes, all_codes) 190 | if is_fit: 191 | xx, call_y2, put_y2 = fit(call_x, call_ys[-1], put_x, put_ys[-1]) 192 | else: 193 | xx, call_y2, put_y2 = not_fit(call_x, call_ys[-1], put_x, put_ys[-1]) 194 | surf_call.remove() 195 | azim += 15 196 | if azim > 360: 197 | azim -= 360 198 | ax_iv_sf_call.view_init(ELEV, azim) 199 | # surf_call = ax_iv_sf_call.plot_surface(x, y, array(call_y2), rstride=1, cstride=1, cmap='rainbow') 200 | surf_call = ax_iv_sf_call.plot_wireframe(x, y, array(call_y2), rstride=1, cstride=1) 201 | surf_put.remove() 202 | ax_iv_sf_put.view_init(ELEV, azim) 203 | # surf_put = ax_iv_sf_put.plot_surface(x, y, array(put_y2), rstride=1, cstride=1, cmap='rainbow') 204 | surf_put = ax_iv_sf_put.plot_wireframe(x, y, array(put_y2), rstride=1, cstride=1) 205 | for index in range(5): 206 | for i in yy: 207 | global_ax_lines_call[index]['ax'].lines.remove(global_ax_lines_call[index]['lines'][i]) 208 | global_ax_lines_put[index]['ax'].lines.remove(global_ax_lines_put[index]['lines'][i]) 209 | global_ax_lines_call[index]['lines'] = [] 210 | global_ax_lines_put[index]['lines'] = [] 211 | for index in range(5): 212 | for i in yy: 213 | global_ax_lines_call[index]['lines'].append(global_ax_lines_call[index]['ax'].plot(call_x[i], array(call_ys[index][i]), COLORS[i])[0]) 214 | global_ax_lines_put[index]['lines'].append(global_ax_lines_put[index]['ax'].plot(put_x[i], array(put_ys[index][i]), COLORS[i])[0]) 215 | plt.draw() 216 | 217 | 218 | def main(cate, exchange, underlying, dividend=True, is_fit=True): 219 | call_codes, put_codes, all_codes, dates = get_codes(cate, exchange, underlying, dividend) 220 | dates_label = ',,'.join(dates).split(',') 221 | call_x, call_ys, put_x, put_ys = get_data(call_codes, put_codes, all_codes) 222 | if is_fit: 223 | xx, call_y2, put_y2 = fit(call_x, call_ys[-1], put_x, put_ys[-1]) 224 | else: 225 | xx, call_y2, put_y2 = not_fit(call_x, call_ys[-1], put_x, put_ys[-1]) 226 | yy = list(range(len(call_y2))) 227 | x, y = meshgrid(xx, yy) 228 | fig = plt.figure(figsize=(12, 5.7)) 229 | fig.canvas.mpl_connect('button_press_event', lambda event: update_picture_lock.acquire()) 230 | fig.canvas.mpl_connect('button_release_event', lambda event: update_picture_lock.release()) 231 | gs = gridspec.GridSpec(3, 6, figure=fig) 232 | ylabels = ['Delta', 'Gamma', 'Theta', 'Vega', 'Implied Volatility'] 233 | call_gs = [gs[2:3, :1], gs[2:3, 1:2], gs[2:3, 2:3], gs[1:2, 2:3], gs[:1, 2:3]] 234 | put_gs = [gs[2:3, 3:4], gs[2:3, 4:5], gs[2:3, 5:6], gs[1:2, 5:6], gs[:1, 5:6]] 235 | # --------------------------------------------------------------------------------------------------- 236 | for index in range(5): 237 | call_ax = fig.add_subplot(call_gs[index]) 238 | for i in yy: 239 | line, = call_ax.plot(call_x[i], call_ys[index][i], COLORS[i]) 240 | global_ax_lines_call[index]['lines'].append(line) 241 | call_ax.set_xlabel('Strike Price') 242 | call_ax.set_ylabel(ylabels[index]) 243 | call_ax.legend(dates, fontsize='xx-small') 244 | global_ax_lines_call[index]['ax'] = call_ax 245 | put_ax = fig.add_subplot(put_gs[index]) 246 | for i in yy: 247 | line, = put_ax.plot(put_x[i], put_ys[index][i], COLORS[i]) 248 | global_ax_lines_put[index]['lines'].append(line) 249 | put_ax.set_xlabel('Strike Price') 250 | put_ax.set_ylabel(ylabels[index]) 251 | put_ax.legend(dates, fontsize='xx-small') 252 | global_ax_lines_put[index]['ax'] = put_ax 253 | ax_iv_sf_call = fig.add_subplot(gs[:2, :2], projection='3d') 254 | ax_iv_sf_call.view_init(ELEV, AZIM) 255 | # surf_call = ax_iv_sf_call.plot_surface(x, y, array(call_y2), rstride=1, cstride=1, cmap='rainbow') 256 | # print(x.shape, y.shape, array(call_y2).shape) 257 | surf_call = ax_iv_sf_call.plot_wireframe(x, y, array(call_y2), rstride=1, cstride=1, cmap='rainbow') 258 | ax_iv_sf_call.set_yticklabels(dates_label) 259 | ax_iv_sf_call.set_xlabel('Strike Price') 260 | ax_iv_sf_call.set_ylabel('Expiration Date') 261 | ax_iv_sf_call.set_zlabel('Implied Volatility') 262 | ax_iv_sf_call.set_title('Call Option') 263 | ax_iv_sf_put = fig.add_subplot(gs[:2, 3:5], projection='3d') 264 | ax_iv_sf_put.view_init(ELEV, AZIM) 265 | # surf_put = ax_iv_sf_put.plot_surface(x, y, array(put_y2), rstride=1, cstride=1, cmap='rainbow') 266 | surf_put = ax_iv_sf_put.plot_wireframe(x, y, array(put_y2), rstride=1, cstride=1, cmap='rainbow') 267 | ax_iv_sf_put.set_yticklabels(dates_label) 268 | ax_iv_sf_put.set_xlabel('Strike Price') 269 | ax_iv_sf_put.set_ylabel('Expiration Date') 270 | ax_iv_sf_put.set_zlabel('Implied Volatility') 271 | ax_iv_sf_put.set_title('Put Option') 272 | plt.tight_layout() 273 | thread = Thread(target=update, args=(call_codes, put_codes, all_codes, x, y, yy, surf_call, surf_put, ax_iv_sf_call, ax_iv_sf_put, is_fit)) 274 | thread.setDaemon(True) 275 | thread.start() 276 | plt.show() 277 | 278 | 279 | if __name__ == '__main__': 280 | category = '50ETF' 281 | underlying_security = '510050' 282 | # category = '300ETF' 283 | # underlying_security = '510300' 284 | main(cate=category, exchange='null', underlying=underlying_security, dividend=False, is_fit=True) 285 | -------------------------------------------------------------------------------- /volatility_surface2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: shifulin 3 | Email: shifulin666@qq.com 4 | """ 5 | import time 6 | from threading import Thread, Lock 7 | from collections import namedtuple 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import matplotlib.gridspec as gridspec 11 | from mpl_toolkits.mplot3d import Axes3D 12 | 13 | from sina_commodity_option_api import get_000300_price, get_t_quotation 14 | import european_option 15 | 16 | # EUROPEAN_OPTION = {'io', 'cu', 'au'} 17 | OptionInfo = namedtuple('OptionInfo', ['x', 'y', 'k', 't', 'type']) 18 | 19 | 20 | class VolSurface(object): 21 | def __init__(self, expiry_date_map): 22 | self.spot = 'io' 23 | self.expiry_date_map = expiry_date_map 24 | self.x = np.array([[[]]]) 25 | self.y = np.array([[[]]]) 26 | self.data = np.array([[[]]]) 27 | self.code_to_info = {} 28 | self.strike_prices = [] 29 | self.expiry_dates = [] 30 | self.x_index = [] 31 | self.spot_price = 0.0 32 | self.update_picture_lock = Lock() 33 | self.elev = 30 34 | self.azim = 120 35 | self.colors = ['blue', 'yellow', 'lime', 'red', 'purple', 'slategray', 'tomato', 'orange', 'darkred', 'aqua'] 36 | self.init() 37 | self.lines = {} 38 | self.update_price(self.get_spot_price(), self.get_option_price()) 39 | 40 | @staticmethod 41 | def get_spot_price(): 42 | return float(get_000300_price()[3]) 43 | 44 | def get_option_price(self): 45 | result = [] 46 | for i in self.expiry_date_map: 47 | up, down = get_t_quotation(self.spot + i) 48 | result.extend(up) 49 | result.extend(down) 50 | return result 51 | 52 | def update_price(self, spot_price, option_price): 53 | self.spot_price = spot_price 54 | for i in option_price: 55 | price = (float(i[1]) + float(i[3])) / 2.0 56 | x, y, k, t, option_type = self.code_to_info[i[-1]] 57 | if option_type == 'Call': 58 | index = 0 59 | iv_func = european_option.call_iv 60 | else: 61 | index = 5 62 | iv_func = european_option.put_iv 63 | self.data[x, y, index] = price 64 | iv = iv_func(price, spot_price, k, t) 65 | delta, gamma, theta, vega = european_option.greeks(spot_price, k, iv, 0.03, t, option_type) 66 | self.data[x, y, index] = delta 67 | self.data[x, y, index + 1] = gamma 68 | self.data[x, y, index + 2] = theta 69 | self.data[x, y, index + 3] = vega 70 | self.data[x, y, index + 4] = iv 71 | 72 | def init(self): 73 | strike_prices, expiry_dates = set(), set() 74 | data = self.get_option_price() 75 | for i in data: 76 | strike_prices.add(i[7]) 77 | expiry_dates.add(i[-1][2:6]) 78 | self.strike_prices = sorted(strike_prices, key=lambda i: float(i)) 79 | self.expiry_dates = sorted(expiry_dates, key=lambda i: int(i)) 80 | self.x_index = [float(i) for i in self.strike_prices] 81 | strike_prices = {j: i for i, j in enumerate(self.strike_prices)} 82 | expiry_dates = {j: i for i, j in enumerate(self.expiry_dates)} 83 | for i in data: 84 | x = strike_prices[i[7]] 85 | y = expiry_dates[i[-1][2:6]] 86 | k = float(i[7]) 87 | t = float(self.expiry_date_map[i[-1][2:6]]) / 365.0 88 | option_type = 'Call' if 'C' in i[-1] else 'Put' 89 | self.code_to_info[i[-1]] = OptionInfo(x, y, k, t, option_type) 90 | self.x, self.y = np.meshgrid(list(range(len(expiry_dates))), [float(i) for i in strike_prices]) 91 | self.data = np.ones(self.x.shape + (10, )) 92 | for i in self.code_to_info.values(): 93 | self.data[i.x, i.y, :] = 0.0 94 | self.data[self.data > 0.5] = np.nan 95 | 96 | def start_update_picture(self): 97 | fig = plt.figure(figsize=(12, 5.7)) 98 | fig.canvas.mpl_connect('button_press_event', lambda event: self.update_picture_lock.acquire()) 99 | fig.canvas.mpl_connect('button_release_event', lambda event: self.update_picture_lock.release()) 100 | gs = gridspec.GridSpec(3, 6, figure=fig) 101 | call_gs = [gs[2:3, :1], gs[2:3, 1:2], gs[2:3, 2:3], gs[1:2, 2:3], gs[:1, 2:3]] 102 | put_gs = [gs[2:3, 3:4], gs[2:3, 4:5], gs[2:3, 5:6], gs[1:2, 5:6], gs[:1, 5:6]] 103 | ylabels = ['Delta', 'Gamma', 'Theta', 'Vega', 'Implied Volatility'] 104 | m, n, _ = self.data.shape 105 | for index in range(5): 106 | call_ax = fig.add_subplot(call_gs[index]) 107 | tmp = [] 108 | for i in range(n): 109 | line, = call_ax.plot(self.x_index, self.data[:, i, index], self.colors[i]) 110 | tmp.append(line) 111 | self.lines[call_ax] = (index, tmp) 112 | call_ax.set_xlabel('Strike Price') 113 | call_ax.set_ylabel(ylabels[index]) 114 | call_ax.legend(self.expiry_dates, fontsize='xx-small') 115 | put_ax = fig.add_subplot(put_gs[index]) 116 | tmp = [] 117 | for i in range(n): 118 | line, = put_ax.plot(self.x_index, self.data[:, i, index + 5], self.colors[i]) 119 | tmp.append(line) 120 | self.lines[put_ax] = (index + 5, tmp) 121 | put_ax.set_xlabel('Strike Price') 122 | put_ax.set_ylabel(ylabels[index]) 123 | put_ax.legend(self.expiry_dates, fontsize='xx-small') 124 | ax_iv_sf_call = fig.add_subplot(gs[:2, :2], projection='3d') 125 | self.surf_call = ax_iv_sf_call.plot_wireframe(self.x, self.y, self.data[:, :, 4], rstride=1, cstride=1) 126 | ax_iv_sf_call.set_yticklabels(self.expiry_dates) 127 | ax_iv_sf_call.set_xlabel('Strike Price') 128 | ax_iv_sf_call.set_ylabel('Expiration Date') 129 | ax_iv_sf_call.set_zlabel('Implied Volatility') 130 | ax_iv_sf_call.set_title('Call Option') 131 | ax_iv_sf_call.view_init(self.elev, self.azim) 132 | ax_iv_sf_put = fig.add_subplot(gs[:2, 3:5], projection='3d') 133 | self.surf_put = ax_iv_sf_put.plot_wireframe(self.x, self.y, self.data[:, :, 9], rstride=1, cstride=1) 134 | ax_iv_sf_put.set_yticklabels(self.expiry_dates) 135 | ax_iv_sf_put.set_xlabel('Strike Price') 136 | ax_iv_sf_put.set_ylabel('Expiration Date') 137 | ax_iv_sf_put.set_zlabel('Implied Volatility') 138 | ax_iv_sf_put.set_title('Put Option') 139 | ax_iv_sf_put.view_init(self.elev, self.azim) 140 | 141 | def update_picture(): 142 | while True: 143 | with self.update_picture_lock: 144 | self.azim += 15 145 | if self.azim > 360: 146 | self.azim -= 360 147 | for k, v in self.lines.items(): 148 | index, lines = v 149 | [k.lines.remove(lines[i]) for i in range(n)] 150 | tmp = [] 151 | for i in range(n): 152 | line, = k.plot(self.x_index, self.data[:, i, index], self.colors[i]) 153 | tmp.append(line) 154 | self.lines[k] = (index, tmp) 155 | self.surf_call.remove() 156 | self.surf_call = ax_iv_sf_call.plot_wireframe(self.x, self.y, self.data[:, :, 4], rstride=1, cstride=1) 157 | ax_iv_sf_call.view_init(self.elev, self.azim) 158 | self.surf_put.remove() 159 | self.surf_put = ax_iv_sf_put.plot_wireframe(self.x, self.y, self.data[:, :, 9], rstride=1, cstride=1) 160 | ax_iv_sf_put.view_init(self.elev, self.azim) 161 | plt.draw() 162 | plt.tight_layout() 163 | time.sleep(5) 164 | self.update_price(self.get_spot_price(), self.get_option_price()) 165 | t = Thread(target=update_picture) 166 | t.setDaemon(True) 167 | t.start() 168 | plt.show() 169 | 170 | def draw_picture(self): 171 | pass 172 | 173 | 174 | def main(): 175 | expiry_date_map = { 176 | '2002': 33, 177 | '2003': 61, 178 | '2004': 89, 179 | '2006': 152, 180 | '2009': 243, 181 | '2012': 334, 182 | } 183 | vs = VolSurface(expiry_date_map) 184 | vs.start_update_picture() 185 | 186 | 187 | if __name__ == '__main__': 188 | main() 189 | --------------------------------------------------------------------------------