Skip to content

from for/if/else to my first option back-test function

Notifications You must be signed in to change notification settings

yuba316/OptionBackTest

Repository files navigation

Option Back-Test

from for/if/else to my first back-test function

2020/07/08 17:51(第五次更新)

更新一下两个以前实习期间复刻的策略,第一个是基于VIX捕捉放大性恐慌择时卖出虚值期权的策略,非常好理解:涨跌幅大于或者小于某一阈值的时候,同时VIX的涨跌幅也大过某一阈值,可以认为是由暴涨或暴跌带来的恐慌引起的VIX激增,卖出相应方向的虚值期权,在VIX恢复正常或者股价恢复到正常水平时平仓可赚取期权的vega价值。仅交易本月期权,到期前10天平仓换月。

VIX策略回测结果(与研报对比)

VIX策略回测结果

VIX_研报

统计量 数值
累计收益率% 359.8118
年化收益率% 92.68264431898378
年化波动率% 0.768968057332984
年化下行波动率% 0.9696144884310578
夏普比率 1.2052860120150566
索提诺比率 0.9558710747913268
最大回撤 968764.0
最大回撤百分比 0.968764
卡玛比率 95.67102443833976
胜率 0.36585365853658536
盈亏比 6.6420242295070615

第二个策略是依赖于RSI与ADX来判断趋势方向与强弱从而有选择地卖开相应的平值期权,比如说在50<RSI<80或RSI<20且ADX大于昨日时,说明标的物处于强买或超卖的现象且趋势正在变强。此时可以卖出平值看跌期权,相反地当RSI>80且ADX小于昨日时,可以认定是超买且趋势正在减弱,极有可能反转,应当卖出平值看涨期权。不过,第二种信号发出的频率比较低,平仓是在信号发生转变或者到期前5天的时候执行。

ADX策略回测结果(与研报对比)

ADX策略回测结果

ADX_研报

统计量 数值
累计收益率% 274.5916
年化收益率% 73.050972303207
年化波动率% 0.5370815290540681
年化下行波动率% 0.4700351856459356
夏普比率 1.360146799907039
索提诺比率 1.554159657277961
最大回撤 404791.5
最大回撤百分比 0.4047915
卡玛比率 180.46567752338427
胜率 0.8837209302325582
盈亏比 13.433004161063494

策略参考自:

2020/07/04 12:13(第四次更新)

5月份开始就有点进入无所事事的状态,但也开始把《期权希腊参数在交易中的应用》和《期权、期货及其他衍生产品》中和期权相关的部分给学习了一下。前者只介绍了一些简单的期权交易策略,希腊字母也只是介绍了一阶的而已(Gamma也有),但是非常实用没有过多的数学内容反而很适合新手入门,偏经验传授吧。而后者就有很多数学公式啊,第27章里就给到了BSM的替代模型: 默顿跳跃-扩散混合模型(MDJ) 和 方差-Gamm模型(VG) 。在标准的BSM模型里假设了未来资产变化的连续性以及价格分布的对数正态性,所以可改进的点就有两个: 一是资产变化过程连续但并非是几何布朗运动,二是在连续的变化上附加跳跃性 。于是我就去学习了一下这两个模型,并尝试用程序模拟出相应的股票轨道,所以本次的更新内容就有两个: 1. MDJ和VG模型下的股票轨道模拟 2. 欧式期权希腊字母(含高阶)的计算程序

默顿跳跃-扩散混合模型(MDJ)

简单来讲,这就是一个BSM模型加上单位时间内到来次数满足泊松分布的跳跃过程。我们可以用λ来刻画过去平均的跳跃次数,代入到泊松分布的计算中去。但是光知道跳跃的次数还不行,还需要假设每次跳跃的幅度都服从正态分布,而μ和σ就是可调整的参数,它影响了每次跳跃的正负向情况,也就会影响到股票收益的分布情况。

MDJ

上面的就是MDJ的股票轨道公式,观察一下,除了带的漂移项和BSM的不一样以外,就只是多了个累加的Y,Y就是每次跳跃过程中股票波动的幅度(ΔS=yS,Y=lny),服从正态分布。那么如何用程序模拟这一过程呢?先从生成最简单的布朗运动开始,它是由多个独立同分布的标准正态随机数累加而成的,用np.random.norm()生成多个正态分布随机数后,np.reshape()划分成多个轨道和多步移动,再用np.cumsum()按步数累加即可。那么接下来生成泊松过程也一样,累加后即可得到在第i步时发生了多少次跳跃N(i),而多少次跳跃就对应着多少步正态分布的累加值(因为我们假设了跳跃的幅度服从正态分布)。最终结果如下:由于我设置了λ=1,μ=-0.1,σ=0.1,所以你可以看到在一年内(252天,1步算1天)股票轨道就跳跃了1次差不多,且都是突然向下震动,如果设置不同的λ值就会有不同的效果,越大核密度估计出来的分布曲线就会越肥,μ则可以控制左右两边的肥尾现象。

默顿跳跃-扩散混合模型(MDJ)股票模拟轨道

MDJ股票轨道

收益率分布

MDJ核密度估计

方差-Gamma模型(VG)

好吧这个模型的论文我确实是有点读不太懂,但是文章中给出了这样的一句话:

VG

所以从公式上看也不难理解,就是BSM模型中的每一步(原本就是每一天)都变成了一个随机的时间变量,服从Gamma分布,也就是我们知道跳过程会发生,幅度也是正态分布,但我们不知道它什么时候会发生。这样看来我们也可以按照上面的方法去生成累计的Gamma随机数,即在一年内总共发生了多少次股票价格的变动,而多少次变动也就对应着多少个正态分布随机数的累加和。最终结果如下:Gamma分布原本是只有两个参数,shape和scale,文中是取了Gamma分布的期望和方差作为参数去计算,实际上意思是一样的。

方差-Gamma模型(VG)股票模拟轨道

VG股票轨道

BSM模型和欧式期权希腊字母

该程序计算的希腊字母仅针对欧式期权且不考虑股息率,如果要计算外汇期权、期货期权或者是带股息的期权公式,可能就要再多考虑利率q的问题。

BSM模型股票模拟轨道

BSM股票轨道

收益率分布

BSM核密度估计

一阶希腊字母对到期日与波动率的变化

希腊字母

文献参考自:

2020/04/26 22:30(第三次更新补充说明)

最近在通过刷LeetCode来熟悉C++的数据结构,所以没怎么看这个波动率交易策略。今晚认真查看了SABR的论文后发现,之前的策略在参数估计上有些失误。

  • alpha:通过计算平值期权的隐含波动率来约等于该参数(若平值期权当天不存在,则利用插值法绘制波动率曲线来求得)
  • beta:平值期权隐含波动率对数与标的价格对数的斜率,可以用OLS估计得到
  • rho/vega:最小化SABR计算得到的波动率与BSM反解得到隐含波动率的残差平方和

修改后的回测结果如下图,由于使用beta回归结果不太理想,所以这里的beta还是默认取0(beta的取值影响的是标的价格的分布情况),rho和vega的最优化范围仅限于当天的近月期权合约。这一次的回测结果就比较贴近于上一篇论坛文章中的结果,15年以后的收益变得不太明显。之前因为急着要测试回测函数的正确与否就不求甚解地按自己的意思随便写了一个,现在回过头来看真的是很不严谨,并没有实现策略原本想要实现的思想……当然两次回测的结果似乎有些许差距,值得思考的是之前随意实现的策略是不是只是一个lucky strategy?从程序返回的结果来看,似乎在优化的过程中还有些计算数值上的错误,我打算在C++的程序上再把这个过程仔细、严谨地实现一遍(因为有看到论文说可以用在高频交易上,就想接入CTP接口模拟交易一下,顺便熟悉下交易接口的使用,感觉还要克服的困难就是求解最优化的过程,这些都因为Python有现成的Package而使得实现起来太过于方便了唔……数值分析很重要哈哈),希望五一过后可以顺利完成吧。

SABR策略回测结果(更正)

SABR策略回测结果(更正)

统计量 数值
累计收益率% 92.33715
年化收益率% 23.768025211565586
年化波动率% 0.175794133683952
年化下行波动率% 0.1180603220424593
夏普比率 1.3520374493438136
索提诺比率 2.0132102640731095
最大回撤 269922.5
最大回撤百分比 0.2699225
卡玛比率 88.05499805153548
胜率 0.44805194805194803
盈亏比 1.315343200879163

方法参考自(感谢舍友提供的大量参考文献):

2020/04/15 16:58(第三次更新)

按照我认为对的方式花了1.5天的时间写完了新的回测函数,又花了不到1天的时间把舍友说的交易波动率策略给复刻了出来(没想到写第二遍的时候效率还蛮高的,而且函数体结构简便了很多,至少没有那么多个if/else了……不过因为多了个list来存储多个仓位,在array和list之间的转换费了我不少功夫,也是主要debug的地方)。
先复刻了之前那个均线策略,结果和用初版回测函数测出来的结果一样,证明函数逻辑大概率是对的。第二个回测结果是复刻了舍友提到的SABR波动率套利策略,详细的交易规则可以参考第二个链接,除了加了一点点加仓操作(也只是为了测试加仓功能是否有效而已)外,其他思路基本一样……

到目前为止,近期我应该就不会再动这个回测函数了,想先看看期权交易方面的书,积累一下基础知识唔,有空就看看论文啥的(理想是一周一篇啦能不能实现还得看进度和理解力了唔)……有遇到想实现的策略再跑上来试试看。其实我不是很喜欢用类,但如果你非要我写成一个类,把它封装起来,也是可以的,只是我觉得就目前来讲,这个程序还欠缺很多东西,未来我可能还会不断地往里面加一些函数,再把它和DataBase.py联动起来,等到那时再来写成一个大类,还算有点价值,现在,它还只是一个小函数而已哈哈哈。
P.S.:加入标的物、期货和多因子选股的回测函数想到有兴趣了再写吧哈哈哈……就先酱紫啦……

MA策略回测结果(用BackTest_2.py回测)

MA策略回测结果(用BackTest_2.py回测)

SABR策略回测结果(用BackTest_2.py回测)

SABR策略回测结果(用BackTest_2.py回测)

统计量 数值
累计收益率% 187.491
年化收益率% 36.951519978401734
年化波动率% 0.42192176050832697
年化下行波动率% 0.3273047525752135
夏普比率 0.8757908085585092
索提诺比率 1.1289637467121838
最大回撤 783397.4999999998
最大回撤百分比 0.7833974999999997
卡玛比率 47.168289378510586
胜率 0.528158295281583
盈亏比 1.2218476548245674

策略参考自:

2020/04/12 21:01(第二次更新)

啊我错了……这可真是一个失败的回测函数……我把问题给复杂化了……
如果今天开仓,交易了多个不同的合约,我根本就不用管到底是看涨还是看跌呀……
所以输入值应该是这样的:

  • signal(int): 用来判断开平仓和不操作
  • price(list): 按顺序存放每一个交易合约的当前价格
  • position(list): 按顺序存放今天的合约交易方向(买还是卖)
  • volume(list): 按顺序存放每张交易合约的成交手数

这样写,根本就不需要去思考开的是空头仓还是多头仓,交易的是看涨看跌还是双边了。
诶,还是自己太菜了……打算下周结束前写完,顺带把舍友的交易波动率策略给实现了……

2020/04/12 08:53(第一次更新)

写在前面的话

其实是上学期(大四上)在实习的时候有写过一个卖期权的策略(后来还成了我的毕业论文),那时候就觉得期权回测好难写啊,一堆问题要考虑……然后就东补西凑终于把这个策略给写出来了,按照我发小的话说就是:

“在垃圾堆上扔垃圾……”

结果这次疫情在家,就觉得必须得把所有的东西都整理一遍(因为我强迫症老是发作,浑身难受……),不能老像个垃圾场……而且这个策略的回测函数完全没有可延展性(就是说只能用在这个策略上,换一个就不行了……)。所以我决定了,要自己写一个回测函数,想想也挺简单的,不就一个for循环嘛,到点了开仓,到期了平仓换月,轻轻松松就能搞定……但是直到我写完我才意识到,这是真的不容易呀……

输入与输出

动笔之前,我们最先应该明确的就是,我们需要什么?我们已经知道,我们的回测函数大概是一个for循环,到点了自动开平仓,所以每天都要有一个if语句来判断说今天的程序到底应该执行什么样的操作:

if 开仓:
    空头 or 多头:
        看涨 or 看跌 or 双边:
            保证金 or 期权费 or 手续费
elif 不操作:
    空头:
        计算收益率
        保证金是否满足
    多头:
        计算收益率
    是否加减仓:
elif 平仓:
    计算收益率
    手续费

所以我们知道,我们要有:1. 判断开平仓;2. 判断多空头;3. 判断看涨看跌;共3个信号变量,还要有看涨看跌期权每日的保证金,共5个变量。如果考虑加减仓的话,又要多一个信号变量。除此之外,你可能还想设置每次开仓、加减仓的仓位大小,又要再多两个变量……(算了我还是直接放输入端的DataFrame格式吧):

def OptionBT(signalDf,depositDf,Capital=1000000,pct=0.8,Fee=2.5,Rde=0.2,Point=10000):
    
    # input:
    # signalDf[DataFrame]: [trade_date, Call_close, Put_close, Call_volume, Put_volume, signal, direction, position, pct]
    # -- signal[int]: -2: 空头平仓, -1: 多头平仓, 0: 不操作, 1: 多头开仓, 2: 空头开仓
    # -- direction[int]: 0: Put, 1: Call, 2: 双边
    # -- position[int]: -1: 减仓, 0: 不操作, 1: 加仓
    # -- pct[float]: 加仓或减仓的比例,仓位变更数量为pct*volume
    # depositDf[DataFrame]: [trade_date, Call_dep, Put_dep]
    # Capital[float]: 本金(元)
    # pct[float]: 开仓百分比
    # Fee[float]: 手续费(元/手)
    # Rde[float]: 保证金上浮比率
    # Point[int]: 期权价格点位比例(元/点)
    
    # output:
    # recordDf[DataFrame]: [trade_date, profit, deposit, signal, position, direction, log, volume, turnover, fee, opfee, capital, strategy]

总共13个需要输出的变量,所以我们就需要在for循环的底端,每次都对这13个list append当天的值,而每天的值,都会因今天的开平仓状态不同而有所变化。如,今天是平仓,记得将所有的变量都恢复为初始值,否则下一天不操作的话,数据会继承前一天的值,未清零的值会让本不该进行任何操作的今天进行错误的操作(好吧我知道说得有点绕,下面讲计算每日收益的时候会解释为什么必须这么做……)。

每日收益的计算

说实话我一开始也很懵期权每日收益的计算(一张几毛钱为啥会有那么大的收益波动?哦原来我忘记乘点数一万了……)。简单来讲就是:

(Callt – Callt-1)×Volume×Point

是的就是这么简单,但是你需要思考一个问题:连续两天的收益=第一天收益+第二天的收益?
答案是否定的,什么时候才成立呢?当你每天都开盘开仓、收盘平仓时,连续两天的收益才会等于两天各自的收益相加。而你交易期权或者是股票,是买入并持有,即便在你平仓以前它可能会跌到你连裤衩都不剩,但只要你打死都不卖掉,它就不会成为你的损失……
但这部分损失不能就这么算了呀,会出现这种情况还是你的决策失误,我们算策略的累计收益曲线就是要假设每一天都平仓了,算要是我今天就把期权或者是股票给卖了了,我能赚多少钱?
所以你还需要一个额外的list来记录你开仓时的期权价格Call0,而正确的每日收益计算公式应该是:

(Callt – Call0)×Volume×Point

来填补上一节最后留下来的坑了,为什么平仓的时候要把所有变量都恢复初始值:
看上面的每日收益计算公式,今天的收益要怎么算,取决于你之前开的仓位是多头还是空头,是交易看涨期权、看跌期权还是双边交易。所以相当于是,不操作的每一天,你都要继承前一天的数据,即list.append(list[-1]),这样才能继承到你上一次开仓时的信息,才能知道你开的是啥子仓?今天的收益该怎么算?
如果你平仓后没有恢复初始值的话,第二天又恰好是不操作,它将继承你上一次开仓的信息,也就是你昨天已经平掉了的仓位信息,继续计算收益!而其实今天的收益应该是0,因为你平仓后又还没有开仓,所以需要对所有的变量进行初始值的恢复,相当于是洗掉上一次开仓时的数据,告诉未来:“我已经准备好了,可以给我新的仓位数据了。在那之前,我将保持没有仓位信息的初始值状态!”

其他需要注意的点

  1. 保证金的计算:每天都要注意自己的保证金账户是否足够cover你的空头仓位?如果不够,你是准备多交保证金呢?还是直接采取强制平仓措施?这些都由你的回测函数来决定。(期权保证金的计算公式:干货来啦!期权保证金说明白,当然你还可以自己设定交易所的保证金上浮比率)。
  2. 手续费的计算:权利仓的手续费是双边收取的,义务仓的手续费只在平仓时收取,当然不只是平仓,由于保证金不足时导致的强制平仓和减仓操作,都要记得计算手续费(手续费的多少可以由自己设定)。
  3. 怎样实现加减仓:这个我不想讲,因为我为了实现这个功能,耗费的时间几乎等于我写完整个不带加减仓功能的回测函数的时间。希望大家好好去思考这个问题,因为思考完它基本上就是把整个函数实现的逻辑都考虑了一遍,对大家理解这个回测的过程还是很有帮助的,所以我决定不讲。它需要考虑的点真的很多,比如说:怎么实现多个仓位不同开仓价格和开仓数量,这是在计算每日收益时必须用到的两个变量,所以你必须想办法用一个list来记录他俩……还有,保证金不够的时候,该平哪个仓?一个仓不够平该怎么平下一个仓?
  4. 每次开仓时,若未给定开仓数量,要怎么计算最大的可开仓数:int(capital/deposit),用你的本金除以保证金或期权费再取整即可(才怪嘞……您又忘了手续费的事情,我们必须保证你的仓位,开得起来,也平得下去,所以还要再多加一步,自己想要怎么写……)。

策略测试

我们就拿一个简单的策略来测试一下这个回测函数的效果吧~

  • 回测周期:2015/04/22~2020/04/10(emmm有点长,不过没关系,只是测试而已……)
  • 本金:100w
  • 开仓比例:80%(就是每次拿80%的本金来开仓,不一定是80w,你会赚钱的嘛……)
  • 手续费:2.5元/张
  • 保证金上浮:20%(即每次按规定算完保证金价格后还要再乘以1.2)
  • 交易合约:50ETF期权
  • 交易规则:(看好了啊!)
  1. 距离期权到期日还有5个交易日的时候,平仓,第二天换下个月到期的合约开仓
  2. 开仓日,50ETF昨日收盘价在其40日均线上方3%,信号为1,下方3%,信号为-1,上下3%之间,信号为0;1则卖开认沽期权,-1卖开认购期权,0则双边卖开(即构建跨式价差期权空头策略)
  3. 开仓后的第5天,开始计算过去5天的信号,若过去5天内,信号有超半数不同,平仓,第二天按新的信号开仓(这里的意思是,过去5天内,50ETF的昨收价已经穿过了均价线,为了及时纠正卖开期权的方向,避免到期时蒙受巨大的损失,我们必须将仓位平掉,换正确的方向重新卖开期权合约)
  4. 开仓后,每天计算交易期权的隐含波动率(这个大家也可以思考下怎么计算比较快,我用的二分法),若昨日的隐含波动率波幅大于5%,则加仓10%,反之小于-5%,减仓10%(其实这个没啥卵用,我只是想测试一下回测函数的加减仓功能是否正常而已……)

MA策略回测结果(用BackTest.py回测)

MA策略回测结果(用BackTest.py回测)

统计量 数值
累计收益率% 143.89175
年化收益率% 28.93690840220386
年化波动率% 0.4232228472323804
年化下行波动率% 0.47926907425729337
夏普比率 0.6837274639456167
索提诺比率 0.6037716588963388
最大回撤 1683591.0
最大回撤百分比 1.683591
卡玛比率 17.187611719356934
胜率 0.71
盈亏比 1.2352869181067099

写在后面的话

好吧一觉醒来,我忽然意识到这个回测函数还是有很多的不足的,比如说,我今天想卖一份认购,买一份认沽外加一份underlying构成衣领策略,上面的回测函数就实现不了了(诶好烦啊我怎么这么菜啊……)。昨天和舍友聊到一个交易波动率的策略,就是一份多头加一份空头的仓位,这让我很头疼,理论上讲,你可能同时买卖多个相同方向但虚实值档位不同的期权……这就需要你有更多的数据结构空间来存储你的多个仓位。我打算下星期把函数给完善了,顺带把这个策略给写完吧(希望可以实现……)。
P.S.:这个策略为什么中间会有那么大的一段回撤?你看在2018年1月的时候,策略开了认沽仓,不巧,50ETF大跌,平仓在2月份,所以咯……还有2020年年初的事情就不用我说了吧……其实我倒是非常惊讶于2018年以前的策略表现,毕竟这个策略逻辑还挺简单的,居然会有比较稳定的收益哇~?
策略参考自:

About

from for/if/else to my first option back-test function

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages