对期权价格计算的实现方式的思考
写个期权定价的东西难吗?你如果随便搜几下,网上就能找到上千篇的期权价格计算的代码实现文章,功能都一样,看似可以满足需求。如果真的用起来(就是动手了,实操了,不是小打小闹!),你会发现…
1. 函数输入参数列表冗长?
2. 各种标志位(option_type="看涨"、"看跌")?
3. 代码里面一堆if...else...?
4. 手里一堆持仓,怎么办?
5. 怎么做压力测试?
6. 情景分析?
7. 有各种期权结构,但都写在一个文件里。有好几个做开发的人,任务分不出去?
……
因为没人教,又找不到人问,所以吃了很多苦,绕了很多弯路。
但也让我理解得更透彻,更深刻。
一、前人留下的版本
1. 一个文件。4000+行。一半以上代码冗余。
2. 标志位Type,str输入类型,可输入"Price"、“Delta”、“Gamma”、“Vega”、“Theta”、“ALL”。根据不同的Type,返回不同结果。麻烦的是,如果写的“ALL”,返回数据类型将是一个list。
3. 基本是一个函数一种结构,也有其实没用到,但不得不有的全局变量,读取交易日历。
4. ……
之后,演变路径基本是这样的。
二、OOP版本。v1。
定义了VanillaCallOption类、VanillaPutOption类、BinaryCallOption类、BinaryPutOption类。但是,缺点
1. 期权合约要素作为了构造函数的输入参数(非常不好,Python不允许函数重载overload)。
2. 期权合约要素有很多是共有属性,可以复用。
三、OOP版本。v2。
对上个版本的改进。 用上继承、运算符重载。
1. 定义了 BasicOption类,作为基本期权类,封装了“我认为”的公共属性,也声明(没办法,Python里就写成了定义)了一堆“我认为”的公共函数(如计算价格 calc_price,计算delta calc_delta等)
这样,每种结构的期权,都去extends基类,并override函数,把价格和风险指标的实现方式改成自己特有的公式,就可以了。
如果我有10个人,每人分出去1种结构,比起一个文件全部写完,我们的开发速度可以是10倍。当然,我想多了,在这里也只有我一个人了……
import numpy as np
from scipy.stats import norm
from greeks import Greeks # 需要读者自己创建相关类,具体定义在后面class BasicOption:def __init__(self, s, k, t, v, r, d):self.s = s # 标的物价格self.k = k # 行权价self.t = t # 剩余期限时间self.v = v # 波动率self.r = r # 利率self.d = d # 分红self.greeks = None # 希腊值self.price = 0 # 价值# 检查期权是否到期self.is_expired = self.t <= 1e-7@staticmethoddef blsd(x, u, v, t):return (np.log(x) + u * t) / (v * (t ** 0.5))@staticmethoddef blsd_1(s, k, t, v, r, d):return BasicOption.blsd(s / k, r - d + 0.5 * v ** 2, v, t)@staticmethoddef blsd_2(s, k, t, v, r, d):return BasicOption.blsd(s / k, r - d - 0.5 * v ** 2, v, t)@staticmethoddef nd_1(s, k, t, v, r, d):return norm.cdf(BasicOption.blsd_1(s, k, t, v, r, d))def calc_price(self):"""计算价格,返回数字:return:"""return 0def calc_delta(self):"""计算delta,返回数字:return:"""return 0def calc_gamma(self):return 0def calc_vega(self):return 0def calc_theta(self):return 0def calc_rho(self):return 0def calc_greeks(self):"""计算greeks,返回greeks:return:"""delta = self.calc_delta()gamma = self.calc_gamma()vega = self.calc_vega()theta = self.calc_theta()self.greeks = Greeks(delta, gamma, vega, theta, rho=0)return self.greeksdef calc_price_and_greeks(self):self.calc_price()self.calc_greeks()
2. 定义了Greeks类,作为期权风险指标的类,还重载了运算符。可以见前文《pyhton:运算符重载(期权Greeks相加)》。看起来很炫酷。
class Greeks:def __init__(self, delta=0.0, gamma=0.0, vega=0.0, theta=0.0, rho=0.0):""":param delta: 标准delta:param gamma: 标准gamma:param vega: :param theta::param rho:"""self.delta = deltaself.gamma = gammaself.vega = vegaself.theta = thetaself.rho = rhodef __add__(self, other):""":param other: 只能是greeks:return:"""self.delta += other.deltaself.gamma += other.gammaself.vega += other.vegaself.theta += other.thetaself.rho += other.rhoreturn selfdef __sub__(self, other):""":param other: :return: """self.delta -= other.deltaself.gamma -= other.gammaself.vega -= other.vegaself.theta -= other.thetaself.rho -= other.rhoreturn selfdef __mul__(self, other):""":param other: 只能是标量:return:"""self.delta *= otherself.gamma *= otherself.vega *= otherself.theta *= otherself.rho *= otherreturn selfdef __truediv__(self, other):""":param other: 只能是标量:return:"""self.delta /= otherself.gamma /= otherself.vega /= otherself.theta /= otherself.rho /= otherreturn selfdef __str__(self):s = """delta: {delta}gamma: {gamma}vega: {vega}theta: {theta}rho: {rho}""".format(delta=self.delta,gamma=self.gamma,vega=self.vega,theta=self.theta,rho=self.rho)return sif __name__ == '__main__':delta = 0.5gamma = 0.1vega = 0.3theta = 0.5rho = 0.2greeks_1 = Greeks(delta, gamma, vega, theta, rho)greeks_2 = Greeks(delta, gamma, vega, theta, rho)print(greeks_1 - greeks_2)
3. 那么,欧式看涨期权的解析解实现就应该这么写。
"""
标准欧式看涨
Vanilla Call Function Based On Black-Scholes Model
"""import numpy as np
from scipy.stats import norm
from basic_option import BasicOption
from greeks import Greeksclass VanillaCallOption(BasicOption):def __init__(self, s, k, t, v, r, d):BasicOption.__init__(self, s, k, t, v, r, d)def calc_price(self):if self.is_expired:price = np.maximum(self.s - self.k, 0)else:price = self.s * np.exp(-self.d * self.t) * norm.cdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t)) - self.k * np.exp(-self.r * self.t) * norm.cdf(self.blsd(self.s / self.k, self.r - self.d - 0.5 * self.v * self.v, self.v, self.t))self.price = pricereturn self.pricedef calc_delta(self):if self.is_expired:delta = 0else:delta = np.exp(-self.d * self.t) * norm.cdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t))return deltadef calc_gamma(self):if self.is_expired:gamma = 0else:gamma = np.exp(-self.d * self.t) * norm.pdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t)) / (self.s * self.v * (self.t ** 0.5))return gammadef calc_vega(self):if self.is_expired:vega = 0else:vega = self.s * np.exp(-self.d * self.t) * norm.pdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t)) * (self.t ** 0.5)return vegadef calc_theta(self):if self.is_expired:theta = 0else:theta = self.d * self.s * np.exp(-self.d * self.t) * norm.cdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t)) - np.exp(-self.d * self.t) * self.s * norm.pdf(self.blsd(self.s / self.k, self.r - self.d + 0.5 * self.v * self.v, self.v, self.t)) * self.v / (2 * (self.t ** 0.5)) - self.r * self.k * np.exp(-self.r * self.t) * norm.cdf(self.blsd(self.s / self.k, self.r - self.d - 0.5 * self.v * self.v, self.v, self.t))return thetadef calc_rho(self):return 0if __name__ == '__main__':s = 1k = 1r = 0.05d = 0.05t = 1v = 0.2option = VanillaCallOption(s, k, t, v, r, d)price = option.calc_price()print('price: {price}'.format(price=price))greeks = option.calc_greeks()print('delta: {delta}'.format(delta=greeks.delta))print('gamma: {gamma}'.format(gamma=greeks.gamma))print('vega: {vega}'.format(vega=greeks.vega))print('theta: {theta}'.format(theta=greeks.theta))
输出结果
price: 0.07577082146427272
delta: 0.5135001229824934
gamma: 1.8879647164532514
vega: 0.3775929432906503
theta: -0.033970753255851395
但是,缺点
1. 我发现,“行权价”k这个属性,不应该放在BasicOption里面,因为有些期权不止这个属性。比如垂直价差(vertical spread)。需要高、低行权价。尽管用了继承,但这样写下去,好像会多出一堆无关紧要的成员变量。
2. 运算符重载,算了,不要搞这些花里胡哨的了。
3. 同一种结构的期权,其价格的计算可能可以有不同的实现方式,如解析解、偏微分方程(扩散方程)的数值解法(如有限差分)、蒙特卡洛模拟,各种方法的价格应该一致。
这样写下去,如果我要求 看涨期权分别用 解析解、有限差分、蒙特卡洛分别计算 其价格,那我要新建一个 VanillaCallOptionFde 类、一个 VanillaCallOptionMc 类。它们分别继承 BasicOptionFde类 和 BasicOptionMc类,而后面这两个类,需要都继承BasicOption类。
其中,BasicOptionFde类 属于有限差分的公共基类,目前所接触到的期权价格计算有一定的规律。设置边界条件、从后往前迭代计算。BasicOptionMc类 属于蒙特卡洛的公共基类,具有生成标的物随机价格曲线的功能。
但是,我的需求仅仅是成员函数的实现方式的不同,我用有限差分求解欧式看涨期权的时候,有限差分计算类本身,不需要有 行权价、剩余期限等期权条款作为成员变量,也不需要有 标的物价格、波动率等市场环境作为成员变量。这是一种负担。
4. 计算公式里面,变量就变量,别用成员变量,加self.好难看啊!你拿到标准的、原生态的公式,还要全部替换成 self.xxx累不累?
于是,我在去掉了构造函数必填的参数列表,参考了“策略模式”[1]、面向对象“多态”,也参考了quantlib[2]这种成熟库的写法,有了目前正在写的第三个版本。
四、OOP版本。v3。
参考[1]和[2]就能写出来。等后面又有情怀,慢慢开源吧…
Ref.
[1] 策略模式
[2] quantlib官网
对期权价格计算的实现方式的思考相关推荐
- .NetCore中三种注入方式的思考
.NetCore中三种注入方式的思考 原文:.NetCore中三种注入方式的思考 该篇内容由个人博客点击跳转同步更新!转载请注明出处! .NetCore彻底诠释了"万物皆可注入"这 ...
- 《面向对象的思考过程(原书第4版)》一 第2章 如何以面向对象的方式进行思考...
本节书摘来自华章出版社<面向对象的思考过程(原书第4版)>一书中的第2章,[美] 马特·魏斯费尔德(Matt Weisfeld) 著黄博文 译更多章节内容可以访问云栖社区"华章计 ...
- 阮一峰老师博客爬取与博客文章存储持久化方式的思考
阮一峰老师博客爬取与博客文章存储持久化方式的思考 前言 博客文章存储持久化思考 文本形式存储 html形式存储 pdf形式存储 博客爬取思路 爬取思路一 爬取思路二 个人选择 pdf存储 结尾 前言 ...
- 传统登录实现方式问题思考
传统登录实现方式在应付分布式.微服务场景时存在的问题: 1. 每个微服务都要进行登录校验,十分麻烦,我们需要的是单点登录 2. 会话保持问题 3. 认证方式单一,无法适应各种认证场景(扫码,指纹... ...
- 谷牛期权长期投资策略的实践与思考
把谷牛期权作为策略构建的主要品种,已经有半年多的时间,从期初的顺利,到中期的掉坑,再到后续的爬坑,也算是经历了一个小周期,在这里把投资中的体会和想法做个分享. 小周期经历了三个阶段,如下图: 第一阶段 ...
- 【数据结构与算法】数组动态分配方式的思考
概述 之前编写的顺序表: 在我写的第一个顺序表里面,内部维护的数组长度是定长的,为100,不存在扩容和缩容的问题.然而如果顺序表储存了较少元素,会造成空间的较多冗余:如果顺序表内部数组空间满,会发生上 ...
- Delphi更高效率的编程方式的思考【一】
我想还是有必要花点时间来整理一下思路的,就是说有必要写一些什么吧. 博客园有一点不好的地方是:没有自己的客户端,我不喜欢安装那些乱七八糟的程序. 虽然博客园提供了一个居于微软的软件,但是我不喜欢使用它 ...
- 换一种方式去思考--microsoft for win server03
微软的东西被很多人鄙视. 原因是蔽塞,但大部分是无奈. 域环境下实现的强大功能是其他系统所不能比拟的. 沿用这样的战略思路.只要小盖开心,可以让全球的MS系统计算机组建成微软计算机群的大军. 这也是 ...
- 惊艳面试官-Java中关于随机数生成8种方式的思考
Java中生成随机数常用的有下面这8种写法:简而言之,名称带安全的未必安全,名字简洁的未必简单. Math.random() Random ThreadLocalRandom SecureRandom ...
最新文章
- 堪比当年的LSTM,Transformer引燃机器学习圈:它是万能的
- python计算特征的统计值并文本输出
- js脚本 处理js注入
- 微信开发:微信js_sdk 分享,前端部分(二)
- preg_grep用法
- linux下源码安装cmake
- CSPNOIP2020总结
- 不出现php version网页_php冷知识 - 从命令行参数列表中获取选项
- python replace函数后面的数字的含义
- Memcached 教程 | 菜鸟教程
- JavaScript 框架这一年:React、Angular 们正在互相渗透
- compile函数 java_正则表达式--关于Java中Pattern.compile函数的相关解释
- ios开发网络学习AFN框架的使用一:get和post请求
- matlab中 晶闸管整流桥导通角_逆变角如何设置,matlab仿真模型作业
- viper4android蓝牙耳机,蝰蛇音效app下载-蝰蛇音效官方版(ViPER4Android FX)下载v2.7.1.0 安卓版-单机手游网...
- BLP模型(Bell-La Padula模型)
- Java实现两个csv文件的对比_比较 csv 文件中数据差异
- 关于广州“开四停四“违法逻辑实现
- java下雪_下雪屏保java,基础
- mailgun_用Mailgun邮寄出去!