Python 量化金融第二版(四)

原文:zh.annas-archive.org/md5/25334ce178953792df9684c784953114

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:期权与期货

在现代金融中,期权理论(包括期货和远期合约)及其应用发挥着重要作用。许多交易策略、公司激励计划和对冲策略都包含各种类型的期权。例如,许多高管激励计划都基于股票期权。假设一家位于美国的进口商刚刚从英国订购了一台机器,三个月后需支付 1000 万英镑。进口商面临货币风险(或汇率风险)。如果英镑对美元贬值,进口商将受益,因为他/她用更少的美元买入 1000 万英镑。相反,如果英镑对美元升值,那么进口商将遭受损失。进口商可以通过几种方式避免或减少这种风险:立即购买英镑、进入期货市场按今天确定的汇率买入英镑,或者购买一个有固定行使价格的认购期权。在本章中,我们将解释期权理论及其相关应用。特别地,以下主题将被涵盖:

  • 如何对冲货币风险以及市场普遍的短期下跌

  • 认购期权和认沽期权的支付和盈亏函数及其图形表示

  • 欧洲期权与美洲期权

  • 正态分布、标准正态分布和累积正态分布

  • 布莱克-斯科尔斯-默顿期权模型(有/无股息)

  • 各种交易策略及其可视化呈现,例如备兑认购期权、跨式期权、蝶式期权和日历差价期权

  • 德尔塔、伽马以及其他希腊字母

  • 认购期权和认沽期权的平价关系及其图形表示

  • 一步和两步二叉树模型的图形表示

  • 使用二叉树方法定价欧洲和美洲期权

  • 隐含波动率、波动率微笑和偏斜

  • 期权理论是金融理论的重要组成部分。很难想象一位金融学学生无法理解它。然而,深入理解这一理论是非常具有挑战性的。许多金融专业的学生认为期权理论就像火箭科学一样复杂,因为它涉及到如何解决各种微分方程。为了满足尽可能多的读者需求,本章避免了复杂的数学推导。

    一种期权将赋予期权买方在未来以今天确定的固定价格买入或卖出某物的权利。如果买方有权在未来购买某物,则称为认购期权。如果期权买方有权卖出某物,则称为认沽期权。由于每个交易涉及两方(买方和卖方),买方支付以获得某项权利,而卖方则收到现金流入以承担义务。与期权不同,期货合约赋予买卖双方权利和义务。与期权中买方向卖方支付初始现金流不同,期货合约通常没有初始现金流。远期合约与期货合约非常相似,只有少数例外。在本章中,这两种合约(期货和远期合约)没有做区分。远期合约比期货合约更容易分析。如果读者希望进行更深入的分析,应参考其他相关教材。

    介绍期货

    在讨论与期货相关的基本概念和公式之前,我们先回顾一下连续复利利率的概念。在第三章,货币的时间价值,我们学到以下公式可以用来估算给定现值的未来价值:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_19.jpg

    在这里,FV 是未来价值,PV 是现值,R 是有效期利率,n 是期数。例如,假设年利率APR)为 8%,按半年复利计算。如果我们今天存入 $100,两年后的未来价值是多少?以下代码显示了结果:

    import scipy as ps
    pv=100
    APR=0.08
    rate=APR/2.0
    n=2
    nper=n*2
    fv=ps.fv(rate,nper,0,pv)
    print(fv)
    

    输出如下所示:

    -116.985856
    

    未来价值是 $116.99。在前面的程序中,有效半年利率为 4%,因为年利率为 8%,并按半年复利计算。在期权理论中,无风险利率和股息收益率定义为连续复利率。推导有效利率(或年利率)与连续复利率之间的关系是很容易的。估算给定现值的未来价值的第二种方法如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_20.jpg

    在这里,Rc 是连续复利率,T 是年份数。换句话说,在应用公式(1)时,我们可以有许多组合,例如年有效利率和年份数、有效月利率和月数等。然而,公式(2)并非如此,它只有一对:连续复利率和年份数。为了推导一个有效利率与其对应的连续复利率之间的关系,我们推荐以下简单方法:选择 $1 作为当前值,1 年作为投资期限。然后应用前两个公式并将其设为相等。假设我们知道有效半年利率为前述案例中的 4%。那么其等效的 Rc 是多少?

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_21.jpg

    我们将它们等同,得到以下方程:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_22.jpg

    对前一个方程两边取自然对数,我们得到以下解:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_23.jpg

    对前述方法进行简单的推广,我们得出以下公式,将有效利率转换为对应的连续复利利率:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_24.jpg

    这里,m 是每年的复利频率:m=1, 2, 4, 12, 52, 365 分别对应年复利、半年复利、季复利、月复利、周复利和日复利。Reffective 是 APR 除以m。如果给定一个带有相关复利频率的 APR,我们可以使用以下等效转换公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_25.jpg

    另一方面,从给定的连续利率推导出有效利率的公式是相当简单的:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_26.jpg

    为了验证前面的方程,请查看以下代码:

    import scipy as sp
    Rc=2*log(1+0.04)
    print(sp.exp(Rc/2)-1
    0.040000000000000036
    

    类似地,我们得出以下公式来估算从Rc计算得到的 APR:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_27.jpg

    对于期货合约,我们以之前的例子为例,假设一个美国进口商将在三个月后支付 1000 万英镑。通常,汇率有两种表示方式:第一种货币与第二种货币的比值,或者相反的比值。我们假设美国为国内,英国为外国,汇率以美元/英镑表示。假设今天的汇率为 1 英镑 = 1.25 美元,国内利率为 1%,外国利率(在英国)为 2%。以下代码展示了我们今天需要多少英镑和美元:

    import scipy as sp
    amount=5
    r_foreign=0.02
    T=3./12.
    exchangeRateToday=1.25
    poundToday=5*sp.exp(-r_foreign*T)
    print("Pound needed today=", poundToday)
    usToday=exchangeRateToday*poundToday
    print("US dollar needed today", usToday)
    ('Pound needed today=', 4.9750623959634117)
    ('US dollar needed today', 6.2188279949542649)
    

    结果显示,为了满足三个月后支付 500 万英镑的需求,我们今天需要 497.5 万英镑,因为我们可以将 497.5 万英镑存入银行以赚取额外的利息(按 1%的利率)。如果进口商没有英镑,他们可以花费 621.88 万美元来购买今天所需的英镑。或者,进口商可以通过购买期货合约(或几个期货合约)来锁定一个固定的汇率,在三个月后购买英镑。此处给出的远期汇率(未来汇率)如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_28.jpg

    这里,F 是期货价格(在本例中是今天确定的未来汇率),S0 是现货价格(在本例中是今天的汇率),Rd 是国内的连续复利无风险利率,Rf 是外国的连续复利存款利率,T 是年化期限。以下 Python 程序显示了今天的期货价格:

    import scipy as sp
    def futuresExchangeRate(s0,rateDomestic,rateForeign,T):
        futureEx=s0*sp.exp((rateDomestic-rateForeign)*T)
    return futureEx
    
    # input area
    
    s0=1.25
    rHome=0.01
    rForeigh=0.02
    T=3./12.
    #
    futures=futuresExchangeRate(s0,rHome,rForeigh,T)
    print("futures=",futures)
    

    输出如下:

    ('futures=', 1.246878902996825)
    

    根据结果,三个月后的汇率应该是每英镑 1.2468789 美元。换句话说,美元应该会相对于英镑贬值。其原因基于两种利率。以下是基于无套利原则的逻辑。假设我们今天有 1.25 美元。我们有两种选择:将其存入美国银行享受 2%的利息,或将其兑换成 1 英镑并存入外资银行,享受 1%的利息。进一步假设,如果未来的汇率不是 1.246879,我们将面临套利机会。假设期货价格(汇率)为 1.26 美元,表示英镑相对于美元被高估了。套利者将低买高卖,也就是做空期货。假设我们有一个三个月到期的 1 英镑的义务。以下是套利策略:借入 1.25 美元(USD),并在三个月后以 1.26 美元的期货价格卖出 1 英镑。三个月后,我们的套利利润如下:

    import scipy as sp
    obligationForeign=1.0           # how much to pay in 3 months
    f=1.26                          # future price
    s0=1.25                         # today's exchange rate 
    rHome=0.01
    rForeign=0.02
    T=3./12.
    todayObligationForeign=obligationForeign*sp.exp(-rForeign*T)
    usBorrow=todayObligationForeign*s0  
    costDollarBorrow=usBorrow*sp.exp(rHome*T)
    profit=f*obligationForeign-costDollarBorrow
    print("profit in USD =", profit)
    

    输出结果如下:

    ('profit in USD =', 0.013121097003174764)
    

    利润为 0.15 美元。如果期货价格低于 1.246878902996825,套利者将采取相反的头寸,即做多期货合约。对于到期日前没有股息支付的股票,我们有以下期货价格:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_29.jpg

    这里,F是期货价格,S0是当前股票价格,Rf是持续复利的无风险利率,yield 是持续复利的股息收益率。对于已知到期日前的离散股息,我们有以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_30.jpg

    在这里,*PV(D)*是到期日前所有股息的现值。期货可以作为对冲工具或投机工具。假设某个共同基金经理担心市场可能在短期内出现负面波动。进一步假设他/她的投资组合与市场投资组合(如标准普尔 500 指数)正相关。因此,他/她应该做空标准普尔 500 指数的期货。以下是相关的公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_31.jpg

    这里,n是做多或做空的期货合约数量,βtarget是目标贝塔值,βp是当前投资组合的贝塔值,Vp是投资组合的价值,VF是一个期货合约的价值。如果n小于(大于)零,表示做空(做多)头寸。以下是一个例子。假设 John Doe 今天管理着一只价值 5000 万美元的投资组合,他的投资组合与标准普尔 500 指数的贝塔值为 1.10。他担心市场可能在未来六个月内下跌。由于交易成本,他/她无法出售其投资组合或其一部分。假设短期内他的目标贝塔值为零。每个标准普尔 500 指数点的价格是 250 美元。由于今天标准普尔 500 指数为 2297.41 点,一个期货合约的价值为 5,743,550 美元。John 应当做空(或做多)的合约数量如下:

    import scipy as ps
    # input area
    todaySP500index=2297.42
    valuePortfolio=50e6    
    betaPortfolio=1.1
    betaTarget=0
    #
    priceEachPoint=250  
    contractFuturesSP500=todaySP500index*priceEachPoint
    n=(betaTarget-betaPortfolio)*valuePortfolio/contractFuturesSP500
    print("number of contracts SP500 futures=",n)
    

    输出结果如下:

    ('number of contracts SP500 futures=', -95.75959119359979)
    

    负值表示做空头寸。John Doe 应该做空 96 份 S&P500 期货合约。这与常识一致,因为投资组合与 S&P500 指数正相关。以下程序展示了当 S&P500 指数下跌 97 点时,是否对冲的盈亏情况:

    # input area
    
    import scipy as sp
    sp500indexToday=2297.42
    valuePortfolio=50e6    
    betaPortfolio=1.1
    betaTarget=0
    sp500indexNmonthsLater=2200.0
    #
    priceEachPoint=250  
    contractFuturesSP500=sp500indexToday*priceEachPoint
    n=(betaTarget-betaPortfolio)*valuePortfolio/contractFuturesSP500
    mySign=sp.sign(n)
    n2=mySign*sp.ceil(abs(n))
    print("number of contracts=",n2)
    # hedging result
    v1=sp500indexToday
    v2=sp500indexNmonthsLater
    lossFromPortfolio=valuePortfolio*(v2-v1)/v1
    gainFromFutures=n2*(v2-v1)*priceEachPoint
    net=gainFromFutures+lossFromPortfolio
    print("loss from portfolio=", lossFromPortfolio)
    print("gain from futures contract=", gainFromFutures)
    print("net=", net)
    

    相关输出如下所示:

    ('number of contracts=', -96.0)
    ('loss from portfolio=', -2120204.403200113)
    ('gain from futures contract=', 2338080.0000000019)
    ('net=', 217875.59679988865)
    

    从最后三行可以知道,如果不对冲,投资组合的损失将为$212 万。另一方面,在做空 96 份 S&P500 期货合约后,在 S&P500 指数下跌 98 点的六个月后,净损失仅为$217,876。通过不同的潜在 S&P500 指数水平,我们可以找出其相关的对冲和不对冲结果。这样的对冲策略通常被称为投资组合保险。

    看涨和看跌期权的回报和盈亏函数

    一个期权赋予其买方在未来以预定价格(行使价)购买(看涨期权)或出售(看跌期权)某物给期权卖方的权利。例如,如果我们购买一个欧式看涨期权,以 X 美元(如$30)在三个月后获得某个股票,那么在到期日我们的回报将按照以下公式计算:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_32.jpg

    这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_56.jpg是到期日(T)的股票价格,行使价为 X(此例中 X=30)。假设三个月后股票价格为$25。我们不会行使看涨期权以$30 的价格购买股票,因为我们可以在公开市场上以$25 购买相同的股票。另一方面,如果股票价格为$40,我们将行使我们的权利以获取$10 的回报,即以$30 买入股票并以$40 卖出股票。以下程序展示了看涨期权的回报函数:

    >>>def payoff_call(sT,x):
            return (sT-x+abs(sT-x))/2
    

    应用payoff函数是直接的:

    >>> payoff_call(25,30)
    0
    >>> payoff_call(40,30)
    10
    

    第一个输入变量,即到期时的股票价格T,也可以是一个数组:

    >> import numpy as np
    >> x=20
    >> sT=np.arange(10,50,10)
    >>> sT
    array([10, 20, 30, 40])
    >>> payoff_call(s,x)
    array([  0.,   0.,  10.,  20.])
    >>>
    

    为了创建图形化展示,我们有以下代码:

    import numpy as np
    import matplotlib.pyplot as plt
    s = np.arange(10,80,5)
    x=30
    payoff=(abs(s-x)+s-x)/2
    plt.ylim(-10,50)
    plt.plot(s,payoff)
    plt.title("Payoff for a call (x=30)")
    plt.xlabel("stock price")
    plt.ylabel("Payoff of a call")
    plt.show()
    

    这里显示了图形:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_01.jpg

    看涨期权卖方的回报与买方相反。需要记住的是,这是一个零和游戏:你赢了,我输了。例如,一位投资者以$10 的行使价卖出三个看涨期权。当股价在到期时为$15 时,期权买方的回报是$15,而期权卖方的总损失也是$15。如果看涨期权的溢价(期权价格)为 c,则看涨期权买方的盈亏函数是其回报与初始投资(c)之间的差异。显然,期权溢价提前支付与到期日回报的现金流时间不同。在这里,我们忽略了货币的时间价值,因为到期通常相当短。

    对于看涨期权买方:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_33.jpg

    对于看涨期权卖方:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_34.jpg

    以下图表展示了看涨期权买方和卖方的盈亏函数:

    import scipy as sp
    import matplotlib.pyplot as plt
    s = sp.arange(30,70,5)
    x=45;c=2.5
    y=(abs(s-x)+s-x)/2 -c
    y2=sp.zeros(len(s))
    plt.ylim(-30,50)
    plt.plot(s,y)
    plt.plot(s,y2,'-.')
    plt.plot(s,-y)
    plt.title("Profit/Loss function")
    plt.xlabel('Stock price')
    plt.ylabel('Profit (loss)')
    plt.annotate('Call option buyer', xy=(55,15), xytext=(35,20),
                 arrowprops=dict(facecolor='blue',shrink=0.01),)
    plt.annotate('Call option seller', xy=(55,-10), xytext=(40,-20),
                 arrowprops=dict(facecolor='red',shrink=0.01),)
    plt.show()
    

    这里展示了一个图形表示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_02.jpg

    看跌期权赋予其买方在未来以预定价格 X 向看跌期权卖方出售证券(商品)的权利。其盈亏函数如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_35.jpg

    这里,ST 是到期时的股票价格,X 是行使价格(执行价格)。对于看跌期权买方,盈亏函数如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_87.jpg

    卖出看跌期权的盈亏函数正好相反:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_88.jpg

    以下是看跌期权买方和卖方盈亏函数的相关程序和图形:

    import scipy as sp
    import matplotlib.pyplot as plt
    s = sp.arange(30,70,5)
    x=45;p=2;c=2.5
    y=c-(abs(x-s)+x-s)/2 
    y2=sp.zeros(len(s)) 
    x3=[x, x]
    y3=[-30,10]
    plt.ylim(-30,50)
    plt.plot(s,y) 
    plt.plot(s,y2,'-.') 
    plt.plot(s,-y) 
    plt.plot(x3,y3)
    plt.title("Profit/Loss function for a put option") 
    plt.xlabel('Stock price')
    plt.ylabel('Profit (loss)')
    plt.annotate('Put option buyer', xy=(35,12), xytext=(35,45), arrowprops=dict(facecolor='red',shrink=0.01),)
    plt.annotate('Put option seller', xy=(35,-10), xytext=(35,-25), arrowprops=dict(facecolor='blue',shrink=0.01),)
    plt.annotate('Exercise price', xy=(45,-30), xytext=(50,-20), arrowprops=dict(facecolor='black',shrink=0.01),)
    plt.show()
    

    该图形如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_03.jpg

    欧洲期权与美国期权

    欧洲期权只能在到期日行使,而美国期权可以在到期日前或到期日当天任何时间行使。由于美国期权可以持有至到期,其价格(期权溢价)应该高于或等于相应的欧洲期权价格:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_38.jpg

    一个重要的区别是,对于欧洲期权,我们有一个封闭解,即布莱克-斯科尔斯-梅顿期权模型。然而,对于美国期权,我们没有封闭解。幸运的是,我们有几种方法可以定价美国期权。后续章节中,我们将展示如何使用二叉树方法,也称为 CRR 方法,来定价美国期权。

    理解现金流、期权类型、权利和义务

    我们知道,对于每个商业合同,我们有买方和卖方两个方面。期权合同也是如此。看涨期权买方将预付现金(现金流出)以获得一个权利。由于这是一个零和博弈,看涨期权卖方将享有预付现金流入,并承担相应的义务。

    下表列出了这些头寸(买方或卖方)、初始现金流的方向(流入或流出)、期权买方的权利(买或卖)以及期权卖方的义务(即满足期权卖方的需求):

    买方(多头头寸) 卖方(空头头寸) 欧洲期权 美国期权
    看涨期权 以预定价格购买证券(商品)的权利 以预定价格出售证券(商品)的义务 只能在到期日行使 可以在到期日前或到期日当天任何时间行使
    看跌期权 在预定价格下出售证券的权利 购买的义务
    现金流 前期现金流出 前期现金流入

    表 10.1 多头、空头头寸,初始现金流,以及权利与义务

    非分红股票的 Black-Scholes-Merton 期权模型

    Black-Scholes-Merton 期权模型是一个封闭式解,用于为一个没有分红支付的股票定价欧式期权。如果我们使用 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_54.jpg 或今天的价格,X 为执行价格,r 为连续复利的无风险利率,T 为到期年数,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_55.jpg 为股票的波动率,则欧式看涨期权 © 和看跌期权 § 的封闭式公式为:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_39.jpg

    在这里,N() 是累积分布标准正态分布。以下 Python 代码表示前述方程,用于评估一个欧式看涨期权:

    from scipy import log,exp,sqrt,stats
    def bs_call(S,X,T,r,sigma):
        d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
    

    在前面的程序中,stats.norm.cdf() 是累积正态分布,也就是 Black-Scholes-Merton 期权模型中的 N()。当前股票价格为 40 美元,行使价格为 42 美元,到期时间为六个月,无风险利率为 1.5%(连续复利),基础股票的波动率为 20%(连续复利)。基于前述代码,欧式看涨期权的价值为 1.56 美元:

    >>>c=bs_call(40.,42.,0.5,0.015,0.2) 
    >>>round(c,2)
    1.56
    

    生成我们自己的模块 p4f

    我们可以将许多小的 Python 程序组合成一个程序,例如 p4f.py。例如,前面的 Python 程序中包含的 bs_call() 函数。这样的一组程序提供了多个好处。首先,当我们使用 bs_call() 函数时,不需要重新输入那五行代码。为了节省空间,我们仅展示了 p4f.py 中包含的几个函数。为了简洁起见,我们去除了每个函数中的所有注释。这些注释是为了帮助未来的用户在调用 help() 函数时,例如 help(bs_call())

    def bs_call(S,X,T,rf,sigma):
        from scipy import log,exp,sqrt,stats
        d1=(log(S/X)+(rf+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        d2 = d1-sigma*sqrt(T)
        return S*stats.norm.cdf(d1)-X*exp(-rf*T)*stats.norm.cdf(d2)
    
    def binomial_grid(n):
        import networkx as nx 
        import matplotlib.pyplot as plt 
        G=nx.Graph() 
        for i in range(0,n+1):     
            for j in range(1,i+2):         
                if i<n:             
                    G.add_edge((i,j),(i+1,j))
                    G.add_edge((i,j),(i+1,j+1)) 
        posG={}    #dictionary with nodes position 
        for node in G.nodes():     
            posG[node]=(node[0],n+2+node[0]-2*node[1]) 
        nx.draw(G,pos=posG)      
    
    def delta_call(S,X,T,rf,sigma):
        from scipy import log,exp,sqrt,stats
        d1=(log(S/X)+(rf+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        return(stats.norm.cdf(d1))
    
    def delta_put(S,X,T,rf,sigma):
        from scipy import log,exp,sqrt,stats
        d1=(log(S/X)+(rf+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        return(stats.norm.cdf(d1)-1)
    

    要应用 Black-Scholes-Merton 看涨期权模型,我们只需使用以下代码:

    >>>import p4f
    >>>c=p4f.bs_call(40,42,0.5,0.015,0.2) 
    >>>round(c,2)
    1.56
    

    第二个优点是节省空间并使编程更加简洁。在本章后续内容中,当我们使用一个名为 binomial_grid() 的函数时,这一点将变得更加明确。从现在开始,每当首次讨论某个函数时,我们会提供完整的代码。然而,当该程序再次使用且程序比较复杂时,我们会通过 p4f 间接调用它。要查找我们的工作目录,使用以下代码:

    >>>import os
    >>>print os.getcwd()
    

    具有已知股息的欧式期权

    假设我们知道在时间 T1(T1 < T)时分发的股息 d1,其中 T 为到期日。我们可以通过将 S0 替换为 S 来修改原始的 Black-Scholes-Merton 期权模型,其中 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_57.jpg:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_40.jpg

    在前面的例子中,如果我们有已知的$1.5 股息将在一个月内支付,那么认购期权的价格是多少?

    >>>import p4f
    >>>s0=40
    >>>d1=1.5
    >>>r=0.015
    >>>T=6/12
    >>>s=s0-exp(-r*T*d1)
    >>>x=42
    >>>sigma=0.2 
    >>>round(p4f.bs_call(s,x,T,r,sigma),2)
    1.18
    

    程序的第一行导入了名为p4f的模块,该模块包含了认购期权模型。结果显示,认购期权的价格为$1.18,低于之前的值($1.56)。这是可以理解的,因为标的股票在一个月内大约会下跌$1.5。由于这个原因,我们行使认购期权的机会会变小,也就是说,股票价格不太可能超过$42。前述论点适用于在 T 之前分配的已知股息,即https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_85.jpg。

    各种交易策略

    在下表中,我们总结了几种常用的期权交易策略:

    名称 描述 初始现金流方向 未来价格变动预期
    看涨价差(认购期权) 买入一个认购期权(x1),卖出一个认购期权(x2)[x1 < x2] 支出 上涨
    看涨价差(看跌期权) 买入一个看跌期权(x1),卖出一个看跌期权(x2)[x1 < x2] 进账 上涨
    熊市价差(看跌期权) 买入一个看跌期权(x2),卖出一个看跌期权(x1)[x1 < x2] 支出 下跌
    看跌价差(认购期权) 买入一个认购期权(x2),卖出一个认购期权(x1)[x1 < x2] 进账 下跌
    交易策略 买入认购期权并卖出看跌期权(相同执行价格) 支出 上涨或下跌
    条带策略 买入两个看跌期权和一个认购期权(相同的执行价格) 支出 下跌的概率 > 上涨的概率
    踏带策略 买入两个认购期权和一个看跌期权(相同的执行价格) 支出 上涨的概率 > 下跌的概率
    脱口而出策略 买入一个认购期权(x2)并买入一个看跌期权(x1)[x1 < x2] 支出 上涨或下跌
    蝴蝶策略(认购期权) 买入两个认购期权(x1, x3),卖出两个认购期权(x2)[x2=(x1+x3)/2] 支出 保持在 x2 附近
    蝴蝶策略(看跌期权) 买入两个看跌期权(x1, x3),卖出两个看跌期权(x2)[x2=(x1+x3)/2] 保持在 x2 附近
    日历价差 卖出一个认购期权(T1),买入一个认购期权(T2),具有相同的执行价格,且 T1<T2 支出

    表 10.2 各种交易策略

    覆盖认购期权 – 持有股票并卖出认购期权

    假设我们购买了 100 股 A 股票,每股价格为$10。那么,总成本为$1,000。如果我们同时写出一个认购期权合同,一个合同对应 100 股,价格为$20。那么,我们的总成本将减少$20。再假设行权价格为$12。以下是我们盈亏函数的图示:

    import matplotlib.pyplot as plt 
    import numpy as np
    sT = np.arange(0,40,5) 
    k=15;s0=10;c=2
    y0=np.zeros(len(sT))
    y1=sT-s0                    # stock only
    y2=(abs(sT-k)+sT-k)/2-c     # long a call 
    y3=y1-y2                    # covered-call 
    plt.ylim(-10,30)
    plt.plot(sT,y1) 
    plt.plot(sT,y2) 
    plt.plot(sT,y3,'red')
    plt.plot(sT,y0,'b-.') 
    plt.plot([k,k],[-10,10],'black')
    plt.title('Covered call (long one share and short one call)') 
    plt.xlabel('Stock price')
    plt.ylabel('Profit (loss)')
    plt.annotate('Stock only (long one share)', xy=(24,15),xytext=(15,20),arrowprops=dict(facecolor='blue',shrink=0.01),)
    plt.annotate('Long one share, short a call', xy=(10,4), xytext=(9,25), arrowprops=dict(facecolor='red',shrink=0.01),)
    plt.annotate('Exercise price= '+str(k), xy=(k+0.2,-10+0.5))
    plt.show()
    

    这里给出了一个图示,展示了仅持有股票、认购期权和覆盖认购期权的位置。显然,当股票价格低于$17(15 + 2)时,覆盖认购期权优于单纯持有股票:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_04.jpg

    跨式策略 – 买入认购期权和看跌期权,且行权价格相同

    我们来看最简单的情形。一家公司面临下个月的不确定事件,问题在于我们不确定事件的方向,是好事还是坏事。为了利用这样的机会,我们可以同时购买看涨期权和看跌期权,且它们的行使价格相同。这意味着无论股票是上涨还是下跌,我们都将受益。进一步假设行使价格为 $30。此策略的收益如下所示:

    import matplotlib.pyplot as plt 
    import numpy as np
    sT = np.arange(30,80,5)
    x=50;    c=2; p=1
    straddle=(abs(sT-x)+sT-x)/2-c + (abs(x-sT)+x-sT)/2-p 
    y0=np.zeros(len(sT))
    plt.ylim(-6,20) 
    plt.xlim(40,70) 
    plt.plot(sT,y0) 
    plt.plot(sT,straddle,'r')
    plt.plot([x,x],[-6,4],'g-.')
    plt.title("Profit-loss for a Straddle") 
    plt.xlabel('Stock price') 
    plt.ylabel('Profit (loss)')
    plt.annotate('Point 1='+str(x-c-p), xy=(x-p-c,0), xytext=(x-p-c,10),
    arrowprops=dict(facecolor='red',shrink=0.01),) 
    plt.annotate('Point 2='+str(x+c+p), xy=(x+p+c,0), xytext=(x+p+c,13),
    arrowprops=dict(facecolor='blue',shrink=0.01),) 
    plt.annotate('exercise price', xy=(x+1,-5))
    plt.annotate('Buy a call and buy a put with the same exercise price',xy=(45,16))
    plt.show()
    

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_05.jpg

    上图显示了无论股票如何波动,我们都会获利。我们会亏损吗?显然,当股票变化不大时,我们的预期未能实现。

    使用看涨期权的蝶式交易

    当购买两个行使价格为 x1x3 的看涨期权,并卖出两个行使价格为 x2 的看涨期权时,其中 x2=(x1+x2)/2,且期权到期日相同,标的股票也相同,我们称之为蝶式交易。其盈亏函数如下所示:

    import matplotlib.pyplot as plt 
    import numpy as np
    sT = np.arange(30,80,5) 
    x1=50;    c1=10
    x2=55;    c2=7
    x3=60;    c3=5
    y1=(abs(sT-x1)+sT-x1)/2-c1 
    y2=(abs(sT-x2)+sT-x2)/2-c2 
    y3=(abs(sT-x3)+sT-x3)/2-c3 
    butter_fly=y1+y3-2*y2 
    y0=np.zeros(len(sT))
    plt.ylim(-20,20) 
    plt.xlim(40,70) 
    plt.plot(sT,y0) 
    plt.plot(sT,y1) 
    plt.plot(sT,-y2,'-.') 
    plt.plot(sT,y3)
    plt.plot(sT,butter_fly,'r') 
    plt.title("Profit-loss for a Butterfly") 
    plt.xlabel('Stock price')
    plt.ylabel('Profit (loss)')
    plt.annotate('Butterfly', xy=(53,3), xytext=(42,4), arrowprops=dict(facecolor='red',shrink=0.01),)
    plt.annotate('Buy 2 calls with x1, x3 and sell 2 calls with x2', xy=(45,16))
    plt.annotate('    x2=(x1+x3)/2', xy=(45,14)) 
    plt.annotate('    x1=50, x2=55, x3=60',xy=(45,12)) 
    plt.annotate('    c1=10,c2=7, c3=5', xy=(45,10)) 
    plt.show()
    

    相关图表如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_06.jpg

    输入值与期权值之间的关系

    当标的股票的波动性增加时,其看涨期权和看跌期权的价值都会增加。其逻辑是,当股票变得更具波动性时,我们有更好的机会观察到极端值,也就是说,我们有更大的机会行使我们的期权。以下 Python 程序展示了这一关系:

    import numpy as np
    import p4f as pf
    import matplotlib.pyplot as plt
    s0=30
    T0=0.5
    sigma0=0.2
    r0=0.05
    x0=30
    sigma=np.arange(0.05,0.8,0.05)
    T=np.arange(0.5,2.0,0.5)
    call_0=pf.bs_call(s0,x0,T0,r0,sigma0)
    call_sigma=pf.bs_call(s0,x0,T0,r0,sigma)
    call_T=pf.bs_call(s0,x0,T,r0,sigma0)
    plt.title("Relationship between sigma and call, T and call")
    plt.plot(sigma,call_sigma,'b')
    plt.plot(T,call_T,'r')
    plt.annotate('x=Sigma, y=call price', xy=(0.6,5), xytext=(1,6), arrowprops=dict(facecolor='blue',shrink=0.01),)
    plt.annotate('x=T(maturity), y=call price', xy=(1,3), xytext=(0.8,1), arrowprops=dict(facecolor='red',shrink=0.01),)
    plt.ylabel("Call premium")
    plt.xlabel("Sigma (volatility) or T(maturity) ")
    plt.show()
    

    相应的图表如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_07.jpg

    希腊字母

    Delta https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_58.jpg 定义为期权对其标的证券价格的导数。看涨期权的 delta 定义如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_41.jpg

    欧式看涨期权在没有分红的股票上的 delta 定义为:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_42.jpg

    delta_call() 的程序非常简单。由于它包含在 p4f.py 中,我们可以轻松地调用它:

    >>>>from p4f import *
    >>> round(delta_call(40,40,1,0.1,0.2),4)
    0.7257
    

    欧式看跌期权在无分红的股票上的 delta 为:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_43.jpg

    >>>>from p4f import *
    >>> round(delta_put(40,40,1,0.1,0.2),4)
    -0.2743
    

    Gamma 是 delta 对价格的变化率,如下公式所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_44.jpg

    对于欧式看涨期权(或看跌期权),其 gamma 如下所示,其中 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_59.jpg:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_45.jpg

    欧式看涨期权和看跌期权的希腊字母的数学定义如下表所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_89.jpg

    表 10.1 希腊字母的数学定义

    请注意,在表格中,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_68.jpg

    显然,很少有人能记住这些公式。这里有一个非常简单的方法,基于它们的定义:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_86.jpg

    表 10.2 估算希腊字母的简单方法

    如何记住?

  • Delta:一阶导数

  • Gamma:二阶导数

  • Theta:时间(T)

  • Vega:波动性(V)

  • Rho:利率(R)

  • 例如,根据 delta 的定义,我们知道它是c2 - c1s2 - s1的比率。因此,我们可以生成一个小的数值来生成这两个对;见以下代码:

    from scipy import log,exp,sqrt,stats
    tiny=1e-9
    S=40
    X=40
    T=0.5
    r=0.01
    sigma=0.2
    
    def bsCall(S,X,T,r,sigma):
        d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        d2 = d1-sigma*sqrt(T)
        return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
    
    def delta1(S,X,T,r,sigma):
        d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
        return stats.norm.cdf(d1)
    
    def delta2(S,X,T,r,sigma):
        s1=S
        s2=S+tiny
        c1=bsCall(s1,X,T,r,sigma)
        c2=bsCall(s2,X,T,r,sigma)
        delta=(c2-c1)/(s2-s1)
        return delta
    
    print("delta (close form)=", delta1(S,X,T,r,sigma))
    print("delta (tiny number)=", delta2(S,X,T,r,sigma))
    ('delta (close form)=', 0.54223501331161406)
    ('delta (tiny number)=', 0.54223835949323917)
    

    根据最后两个值,差异非常小。我们可以将此方法应用于其他希腊字母,参见章节末尾的问题。

    看跌-看涨平价及其图示

    让我们来看一个行使价格为$20,期限为三个月,风险自由利率为 5%的看涨期权。这个未来$20 的现值如下面所示:

    >>>x=20*exp(-0.05*3/12)   
    >>>round(x,2)
    19.75
    >>>
    

    在三个月后,由一个看涨期权和今天$19.75 现金组成的投资组合的财富将是多少?如果股票价格低于$20,我们不会行使看涨期权,而是保留现金。如果股票价格高于$20,我们用$20 现金行使看涨期权以拥有股票。因此,我们的投资组合价值将是这两者中的最大值:三个月后的股票价格或$20,即max(s,20)

    另一方面,假设有一个由股票和一个行使价格为$20 的看跌期权组成的投资组合。如果股票价格下跌$20,我们行使看跌期权并获得$20。如果股票价格高于$20,我们就直接持有股票。因此,我们的投资组合价值将是这两者中的最大值:三个月后的股票价格或$20,即max(s,20)

    因此,对于这两个投资组合,我们都有相同的最终财富max(s,20)。根据无套利原理,这两个投资组合的现值应该相等。我们称之为看跌-看涨平价:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_46.jpg

    当股票在到期日之前有已知的股息支付时,我们有以下等式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_47.jpg

    这里,D是所有股息支付在其到期日之前的现值(T)。以下 Python 程序提供了看跌-看涨平价的图示:

    import pylab as pl 
    import numpy as np 
    x=10
    sT=np.arange(0,30,5) 
    payoff_call=(abs(sT-x)+sT-x)/2 
    payoff_put=(abs(x-sT)+x-sT)/2 
    cash=np.zeros(len(sT))+x
    
    def graph(text,text2=''): 
        pl.xticks(())
        pl.yticks(())
        pl.xlim(0,30)
        pl.ylim(0,20) 
        pl.plot([x,x],[0,3])
        pl.text(x,-2,"X");
        pl.text(0,x,"X")
        pl.text(x,x*1.7, text, ha='center', va='center',size=10, alpha=.5) 
        pl.text(-5,10,text2,size=25)
    
    pl.figure(figsize=(6, 4))
    pl.subplot(2, 3, 1); graph('Payoff of call');       pl.plot(sT,payoff_call) 
    pl.subplot(2, 3, 2); graph('cash','+');             pl.plot(sT,cash)
    pl.subplot(2, 3, 3); graph('Porfolio A ','=');   pl.plot(sT,cash+payoff_call)
    pl.subplot(2, 3, 4); graph('Payoff of put ');       pl.plot(sT,payoff_put) 
    pl.subplot(2, 3, 5); graph('Stock','+');       pl.plot(sT,sT)
    pl.subplot(2, 3, 6); graph('Portfolio B','=');   pl.plot(sT,sT+payoff_put) 
    pl.show()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_08.jpg

    看跌-看涨比率代表了投资者对未来的共同预期。如果没有明显的趋势,也就是说,我们预期未来是正常的,那么看跌-看涨比率应该接近 1。另一方面,如果我们预期未来会更加光明,那么比率应该低于 1。

    以下代码显示了这种类型的比率在多年的变化。首先,我们必须从 CBOE 下载数据。

    执行以下步骤:

    1. 访问www.cboe.com/

    2. 点击菜单栏中的报价与数据

    3. 查找put call ratio,即,www.cboe.com/data/putcallratio.aspx

    4. 点击当前下的CBOE 总交易量和看涨/看跌比率(2006 年 11 月 1 日至今)

      注意

      对于数据,读者可以在canisius.edu/~yany/data/totalpc.csv下载。

    以下代码显示了看跌-看涨比率的趋势:

    import pandas as pd
    import scipy as sp
    from matplotlib.pyplot import *
    infile='c:/temp/totalpc.csv'
    data=pd.read_csv(infile,skiprows=2,index_col=0,parse_dates=True)
    data.columns=('Calls','Puts','Total','Ratio') 
    x=data.index
    y=data.Ratio 
    y2=sp.ones(len(y)) 
    title('Put-call ratio') 
    xlabel('Date') 
    ylabel('Put-call ratio') 
    ylim(0,1.5)
    plot(x, y, 'b-')
    plot(x, y2,'r') 
    show()
    

    相关图表如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_09.jpg

    带趋势的短期看跌期权与看涨期权比率

    基于前面的程序,我们可以选择一个带趋势的较短时期,如以下代码所示:

    import scipy as sp
    import pandas as pd
    from matplotlib.pyplot import * 
    import matplotlib.pyplot as plt 
    from datetime import datetime 
    import statsmodels.api as sm
    
    data=pd.read_csv('c:/temp/totalpc.csv',skiprows=2,index_col=0,parse_dates=True)
    data.columns=('Calls','Puts','Total','Ratio') 
    begdate=datetime(2013,6, 1) 
    enddate=datetime(2013,12,31)
    data2=data[(data.index>=begdate) & (data.index<=enddate)] 
    x=data2.index
    y=data2.Ratio 
    x2=range(len(x)) 
    x3=sm.add_constant(x2) 
    model=sm.OLS(y,x3) 
    results=model.fit()
    
    #print results.summary() 
    alpha=round(results.params[0],3) 
    slope=round(results.params[1],3) 
    y3=alpha+sp.dot(slope,x2) 
    y2=sp.ones(len(y))
    title('Put-call ratio with a trend') 
    xlabel('Date') 
    ylabel('Put-call ratio') 
    ylim(0,1.5)
    plot(x, y, 'b-')
    plt.plot(x, y2,'r-.')
    plot(x,y3,'y+')
    plt.figtext(0.3,0.35,'Trend: intercept='+str(alpha)+',slope='+str(slope)) 
    show()
    

    相应的图表如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_10.jpg

    二项树及其图形展示

    二项树方法是由 Cox、Ross 和 Robinstein 于 1979 年提出的。因此,它也被称为 CRR 方法。基于 CRR 方法,我们有以下两步方法。首先,我们绘制一棵树,例如以下的一步树。假设我们当前的股票价值是 S。那么,结果有两个,SuSd,其中u>1d<1,请参见以下代码:

    import matplotlib.pyplot as plt 
    plt.xlim(0,1) 
    plt.figtext(0.18,0.5,'S')
    plt.figtext(0.6,0.5+0.25,'Su')
    plt.figtext(0.6,0.5-0.25,'Sd')
    
    plt.annotate('',xy=(0.6,0.5+0.25), xytext=(0.1,0.5), arrowprops=dict(facecolor='b',shrink=0.01))
    plt.annotate('',xy=(0.6,0.5-0.25), xytext=(0.1,0.5), arrowprops=dict(facecolor='b',shrink=0.01))
    plt.axis('off')
    plt.show()
    

    图表如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_11.jpg

    显然,最简单的树是一棵一步树。假设今天的价格为 10 美元,行权价为 11 美元,且一个看涨期权将在六个月后到期。此外,假设我们知道价格将有两个结果:上涨(u=1.15)或下跌(d=0.9)。换句话说,最终的价格要么是 11 美元,要么是 9 美元。基于这些信息,我们有以下图表,展示了这种一步二项树的价格:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_12.jpg

    生成前面图表的代码如下所示。

    这些代码基于pypi.python.org/pypi/PyFi的代码:

    import networkx as nx
    import matplotlib.pyplot as plt 
    plt.figtext(0.08,0.6,"Stock price=$20") 
    plt.figtext(0.75,0.91,"Stock price=$22") 
    plt.figtext(0.75,0.87,"Option price=$1")
    plt.figtext(0.75,0.28,"Stock price=$18") 
    plt.figtext(0.75,0.24,"Option price=0") 
    n=1
    def binomial_grid(n): 
        G=nx.Graph()
        for i in range(0,n+1):
            for j in range(1,i+2): 
                if i<n:
                    G.add_edge((i,j),(i+1,j))
                    G.add_edge((i,j),(i+1,j+1))
        posG={}
        for node in G.nodes(): 
            posG[node]=(node[0],n+2+node[0]-2*node[1])
        nx.draw(G,pos=posG) 
    binomial_grid(n)
    plt.show()
    

    在前面的程序中,我们生成了一个名为binomial_grid()的函数,因为我们将在本章后面多次调用此函数。由于我们事先知道会有两个结果,因此我们可以选择一个合适的股票和看涨期权组合,以确保我们的最终结果是确定的,即相同的终端值。假设我们选择合适的 delta 份额的基础证券,并加上一份看涨期权,以确保在一个时期结束时具有相同的终端值,即!二项树及其图形展示。

    因此,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_75.jpg。这意味着,如果我们持有0.4股并卖空一份看涨期权,那么当股票上涨时,我们的最终财富将相同,即0.411.5-1 =3.6*;而当股票下跌时,则为0.49=3.6*。进一步假设,如果持续复利的无风险利率为 0.12%,则今天的投资组合的价值将相当于未来确定值的折现值,即0.410 – c=pv(3.6)*。也就是说,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_76.jpg。如果使用 Python,我们将得到以下结果:

    >>>round(0.4*10-exp(-0.012*0.5)*3.6,2)
    0.42
    >>>
    

    对于二步二项树,我们有以下代码:

    import p4f
    plt.figtext(0.08,0.6,"Stock price=$20")
    plt.figtext(0.08,0.56,"call =7.43")
    plt.figtext(0.33,0.76,"Stock price=$67.49")
    plt.figtext(0.33,0.70,"Option price=0.93")
    plt.figtext(0.33,0.27,"Stock price=$37.40")
    plt.figtext(0.33,0.23,"Option price=14.96")
    plt.figtext(0.75,0.91,"Stock price=$91.11")
    plt.figtext(0.75,0.87,"Option price=0")
    plt.figtext(0.75,0.6,"Stock price=$50")
    plt.figtext(0.75,0.57,"Option price=2")
    plt.figtext(0.75,0.28,"Stock price=$27.44")
    plt.figtext(0.75,0.24,"Option price=24.56")
    n=2
    p4f.binomial_grid(n)
    

    基于 CRR 方法,我们有以下程序:

    1. 绘制一个n步树。

    2. n步结束时,估算终端价格。

    3. 根据终端价格、行权、看涨或看跌期权,在每个节点计算期权值。

    4. 按照风险中性概率将其向后折现一步,即从第 n 步到第 n-1 步。

    5. 重复前一步骤,直到找到第 0 步的最终值。udp的公式如下所示:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_48.jpg

    这里,u是向上波动,d是向下波动,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_77.jpg是标的证券的波动率,r 是无风险利率,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_78.jpg是步长,即https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_79.jpg,T是到期时间(以年为单位),n是步数,q是股息收益率,p 是向上波动的风险中性概率。binomial_grid()函数基于一步二项树图示中的函数。如前所述,该函数包含在名为p4fy.py的总主文件中。输出图形如下所示。一个明显的结果是,前面的 Python 程序非常简单且直接。接下来,我们使用一个两步二项树来解释整个过程。假设当前股票价格为 10 美元,行权价格为 10 美元,到期时间为三个月,步数为二,风险自由利率为 2%,标的证券的波动率为 0.2。以下 Python 代码将生成一个两步二项树:

    import p4f
    from math import sqrt,exp 
    import matplotlib.pyplot as plt
    s=10
    r=0.02
    sigma=0.2
    T=3./12
    x=10
    n=2
    deltaT=T/n
    q=0 
    u=exp(sigma*sqrt(deltaT))
    d=1/u 
    a=exp((r-q)*deltaT)
    p=(a-d)/(u-d) 
    su=round(s*u,2);
    suu=round(s*u*u,2) 
    sd=round(s*d,2)
    sdd=round(s*d*d,2) 
    sud=s
    
    plt.figtext(0.08,0.6,'Stock '+str(s)) 
    plt.figtext(0.33,0.76,"Stock price=$"+str(su)) 
    plt.figtext(0.33,0.27,'Stock price='+str(sd)) 
    plt.figtext(0.75,0.91,'Stock price=$'+str(suu)) 
    plt.figtext(0.75,0.6,'Stock price=$'+str(sud)) 
    plt.figtext(0.75,0.28,"Stock price="+str(sdd)) 
    p4f.binomial_grid(n)
    plt.show()
    

    树形结构如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_13.jpg

    现在,我们使用风险中性概率将每个值向后折现一步。代码和图形如下所示:

    import p4f
    import scipy as sp
    import matplotlib.pyplot as plt
    s=10;x=10;r=0.05;sigma=0.2;T=3./12.;n=2;q=0    # q is dividend yield 
    deltaT=T/n    # step
    u=sp.exp(sigma*sp.sqrt(deltaT)) 
    d=1/u
    a=sp.exp((r-q)*deltaT) 
    p=(a-d)/(u-d)
    s_dollar='S=$'
    c_dollar='c=$' 
    p2=round(p,2)
    plt.figtext(0.15,0.91,'Note: x='+str(x)+', r='+str(r)+', deltaT='+str(deltaT)+',p='+str(p2))
    plt.figtext(0.35,0.61,'p')
    plt.figtext(0.65,0.76,'p')
    plt.figtext(0.65,0.43,'p')
    plt.figtext(0.35,0.36,'1-p')
    plt.figtext(0.65,0.53,'1-p')
    plt.figtext(0.65,0.21,'1-p')
    
    # at level 2 
    su=round(s*u,2);
    suu=round(s*u*u,2) 
    sd=round(s*d,2);
    sdd=round(s*d*d,2) 
    sud=s
    c_suu=round(max(suu-x,0),2) 
    c_s=round(max(s-x,0),2) 
    c_sdd=round(max(sdd-x,0),2) 
    plt.figtext(0.8,0.94,'s*u*u') 
    plt.figtext(0.8,0.91,s_dollar+str(suu)) 
    plt.figtext(0.8,0.87,c_dollar+str(c_suu)) 
    plt.figtext(0.8,0.6,s_dollar+str(sud)) 
    plt.figtext(0.8,0.64,'s*u*d=s') 
    plt.figtext(0.8,0.57,c_dollar+str(c_s)) 
    plt.figtext(0.8,0.32,'s*d*d') 
    plt.figtext(0.8,0.28,s_dollar+str(sdd)) 
    plt.figtext(0.8,0.24,c_dollar+str(c_sdd))
    
    # at level 1
    c_01=round((p*c_suu+(1-p)*c_s)*sp.exp(-r*deltaT),2) 
    c_02=round((p*c_s+(1-p)*c_sdd)*sp.exp(-r*deltaT),2)
    
    plt.figtext(0.43,0.78,'s*u') 
    plt.figtext(0.43,0.74,s_dollar+str(su)) 
    plt.figtext(0.43,0.71,c_dollar+str(c_01)) 
    plt.figtext(0.43,0.32,'s*d') 
    plt.figtext(0.43,0.27,s_dollar+str(sd)) 
    plt.figtext(0.43,0.23,c_dollar+str(c_02))
    # at level 0 (today)
    
    c_00=round(p*sp.exp(-r*deltaT)*c_01+(1-p)*sp.exp(-r*deltaT)*c_02,2) 
    plt.figtext(0.09,0.6,s_dollar+str(s)) 
    plt.figtext(0.09,0.56,c_dollar+str(c_00)) 
    p4f.binomial_grid(n)
    

    树形结构如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_14.jpg

    这里,我们解释图中显示的一些值。在最高节点(suu)处,由于终端股票价格为 11.52,行权价格为 10,故看涨期权的价值为 1.52(11.52-10)。类似地,在节点sud=s处,看涨期权的价值为 0,因为 10-10=0。对于看涨期权值为 0.8,我们进行如下验证:

    >>>p
    0.5266253390068362
    >>>deltaT
    0.125
    >>>v=(p*1.52+(1-p)*0)*exp(-r*deltaT)
    >>>round(v,2)
    0.80
    >>>
    

    欧洲期权的二项树(CRR)方法

    以下代码是用于使用二项树法定价欧洲期权的:

    def binomialCallEuropean(s,x,T,r,sigma,n=100):
        from math import exp,sqrt 
        deltaT = T /n
        u = exp(sigma * sqrt(deltaT)) 
        d = 1.0 / u
        a = exp(r * deltaT)
        p = (a - d) / (u - d)
        v = [[0.0 for j in xrange(i + 1)]  for i in xrange(n + 1)] 
        for j in xrange(i+1):
            v[n][j] = max(s * u**j * d**(n - j) - x, 0.0) 
        for i in xrange(n-1, -1, -1):
            for j in xrange(i + 1):
                v[i][j]=exp(-r*deltaT)*(p*v[i+1][j+1]+(1.0-p)*v[i+1][j]) 
        return v[0][0]
    

    为了应用这个函数,我们给它一组输入值。为了比较,基于Black-Scholes-Merton 期权模型的结果也在这里显示:

    >>> binomialCallEuropean(40,42,0.5,0.1,0.2,1000) 
    2.278194404573134
    >>> bs_call(40,42,0.5,0.1,0.2) 
    2.2777803294555348
    >>>
    

    美国期权的二项树(CRR)方法

    与只能应用于欧洲期权的 Black-Scholes-Merton 期权模型不同,二项树(CRR 方法)可以用来定价美国期权。唯一的区别是我们必须考虑提前行权:

    def binomialCallAmerican(s,x,T,r,sigma,n=100):
        from math import exp,sqrt
        import numpy as np
        deltaT = T /n
        u = exp(sigma * sqrt(deltaT)) 
        d = 1.0 / u
        a = exp(r * deltaT)
        p = (a - d) / (u - d)
        v = [[0.0 for j in np.arange(i + 1)] for i in np.arange(n + 1)] 
        for j in np.arange(n+1):
            v[n][j] = max(s * u**j * d**(n - j) - x, 0.0) 
        for i in np.arange(n-1, -1, -1):
            for j in np.arange(i + 1):
                v1=exp(-r*deltaT)*(p*v[i+1][j+1]+(1.0-p)*v[i+1][j]) 
                v2=max(v[i][j]-x,0)           # early exercise 
                v[i][j]=max(v1,v2)
        return v[0][0]
    

    定价美国看涨期权与定价欧洲看涨期权的关键区别在于其提前行权的机会。在前面的程序中,最后几行反映了这一点。对于每个节点,我们估算两个值:v1是折现后的值,v2是提前行权的支付。如果使用相同的数值集来应用此二项树定价美国看涨期权,我们会得到以下值。可以理解,最终结果会高于欧洲看涨期权的对应值:

    >>> call=binomialCallAmerican(40,42,0.5,0.1,0.2,1000)
    >>> round(call,2)
    2.28
    >>>
    

    对冲策略

    在卖出欧洲看涨期权后,我们可以持有https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_80.jpg同一只股票的股份来对冲我们的仓位。这被称为 delta 对冲。由于 deltahttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_81.jpg是标的股票(S)的一个函数,为了保持有效的对冲,我们必须不断地重新平衡我们的持仓。这就是动态对冲。一个投资组合的 delta 是该投资组合中各个证券的加权 delta。需要注意的是,当我们做空某个证券时,其权重将为负值:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_49.jpg

    假设一个美国进口商将在三个月后支付 1000 万英镑。他或她担心美元对英镑的潜在贬值。有几种方法可以对冲这种风险:现在购买英镑,进入期货合约以固定汇率在三个月后购买 1000 万英镑,或者购买以固定汇率为行权价格的看涨期权。第一种选择成本高,因为进口商今天并不需要英镑。进入期货合约也有风险,因为如果美元升值,进口商将面临额外的费用。另一方面,进入看涨期权将保证今天的最大汇率。同时,如果英镑贬值,进口商将获得收益。这种活动被称为对冲,因为我们采取了与我们的风险相反的立场。

    对于货币期权,我们有以下方程:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_50.jpg

    这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_82.jpg是外币的美元汇率,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_83.jpg是国内无风险利率,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_84.jpg是外国的无风险利率。

    隐含波动率

    从前面的部分我们知道,对于一组输入变量——S(当前股票价格)、X(行权价格)、T(到期日,单位为年)、r(连续复利的无风险利率)以及 sigma(股票的波动率,即其收益的年化标准差)——我们可以根据 Black-Scholes-Merton 期权模型来估算看涨期权的价格。回想一下,为了定价欧洲看涨期权,我们有以下五行 Python 代码:

    def bs_call(S,X,T,r,sigma):
        from scipy import log,exp,sqrt,stats
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
    

    在输入一组五个数值后,我们可以按照以下方式估算看涨期权的价格:

    >>>bs_call(40,40,0.5,0.05,0.25)
    3.3040017284767735
    

    另一方面,如果我们知道SXTrc,我们如何估算 sigma 呢?这里,sigma是我们的隐含波动率。换句话说,如果我们给定一组值,如 S=40、X=40、T=0.5、r=0.05 和 c=3.30,我们应该找出 sigma 的值,并且它应该等于 0.25。在本章中,我们将学习如何估算隐含波动率。实际上,计算隐含波动率的基本逻辑非常简单:试错法。让我们以之前的例子为例进行说明。我们有五个值——S=40X=40T=0.5r=0.05c=3.30。基本设计是,在输入 100 个不同的 sigma 值后,加上之前提到的四个输入值,我们将得到 100 个看涨期权价格。隐含波动率就是通过最小化估算的看涨期权价格与 3.30 之间的绝对差值来得到的 sigma 值。当然,我们可以增加试验的次数,以获得更高的精度,也就是更多的小数位。

    另外,我们可以采用另一种转换标准:当估算的看涨期权价格与给定的看涨期权值之间的绝对差值小于某个临界值时停止,例如 1 美分,即|c-3.30|<0.01。由于随机选择 100 个或 1,000 个不同的 sigma 值并不是一个好主意,我们将系统地选择这些值,也就是通过循环来系统地选择这些 sigma 值。接下来,我们将讨论两种类型的循环:for 循环和 while 循环。基于欧洲看涨期权的隐含波动率函数。最终,我们可以编写一个基于欧洲看涨期权的隐含波动率估算函数。为了节省空间,我们从程序中移除所有注释和示例,如下所示:

    def implied_vol_call(S,X,T,r,c):
        from scipy import log,exp,sqrt,stats
        for i in range(200):
            sigma=0.005*(i+1)
            d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
            d2 = d1-sigma*sqrt(T)
            diff=c-(S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2))
            if abs(diff)<=0.01:
                return i,sigma, diff
    

    使用一组输入值,我们可以像下面这样轻松地应用之前的程序:

    >>>implied_vol_call(40,40,0.5,0.05,3.3)
     (49, 0.25, -0.0040060797372882817)
    

    类似地,我们可以基于欧洲看跌期权模型来估算隐含波动率。在以下程序中,我们设计了一个名为implied_vol_put_min()的函数。这个函数与之前的函数有几个区别。首先,当前的函数依赖于看跌期权,而不是看涨期权。因此,最后一个输入值是看跌期权的溢价,而不是看涨期权的溢价。其次,转换标准是估算的价格与给定的看跌期权价格之间的差值最小。在之前的函数中,转换标准是当绝对差值小于 0.01 时。在某种意义上,当前程序将保证输出隐含波动率,而之前的程序则不能保证输出:

    def implied_vol_put_min(S,X,T,r,p):
        from scipy import log,exp,sqrt,stats 
        implied_vol=1.0
        min_value=100.0
        for i in xrange(1,10000): 
            sigma=0.0001*(i+1)
            d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T)) 
            d2 = d1-sigma*sqrt(T)
            put=X*exp(-r*T)*stats.norm.cdf(-d2)-S*stats.norm.cdf(-d1) 
            abs_diff=abs(put-p)
            if abs_diff<min_value: 
                min_value=abs_diff 
                implied_vol=sigma 
                k=i
            put_out=put
        print ('k, implied_vol, put, abs_diff') 
        return k,implied_vol, put_out,min_value
    

    让我们使用一组输入值来估算隐含波动率。之后,我们将解释前面程序的逻辑。假设S=40X=40T=12个月、r=0.1,并且看跌期权价格为$1.50,如以下代码所示:

    >>>implied_vol_put_min(40,40,1.,0.1,1.501)
    k, implied_vol, put, abs_diff
    (1999, 0.2, 12.751879946129757, 0.00036735530273501737)
    

    隐含波动率为 20%。其逻辑是我们为一个变量 min_value 赋予一个较大的值,例如 100。对于第一个 sigma 值 0.0002,我们几乎得到零的卖出期权值。因此,绝对差值为 1.50,这小于 100。所以,我们的 min_value 变量将被 1.50 替换。我们继续这样做,直到完成循环。记录下来的最小值对应的 sigma 就是我们的隐含波动率。我们可以通过定义一些中间值来优化前面的程序。例如,在之前的程序中,我们估算了 ln(S/X) 10,000 次。实际上,我们定义一个新变量 log_S_over_X,只估算一次,然后在 10,000 次中使用这个值。对于 sigma*sigma/2.sigman*sqrt(T) 也是如此:

    二分查找

    为了估算隐含波动率,早期方法背后的逻辑是运行 100 次 Black-Scholes-Merton 期权模型,并选择能使估算期权价格与观察价格之间差异最小的 sigma 值。尽管这个逻辑易于理解,但这种方法效率不高,因为我们需要调用几百次 Black-Scholes-Merton 期权模型。为了估算少量的隐含波动率,这种方法不会带来问题。然而,在两种情况下,这种方法会遇到问题。首先,如果我们需要更高的精度,比如 sigma=0.25333,或者必须估算几百万个隐含波动率,那么我们需要优化方法。让我们看一个简单的例子。假设我们随机选择一个 1 到 5,000 之间的数。如果我们从 1 到 5,000 顺序地运行循环,需要多少步才能找到这个数?二分查找是 log(n) 的最坏情况,而线性查找则是 n 的最坏情况。因此,在 1 到 5,000 范围内搜索一个值时,线性查找在最坏情况下需要 5,000 步(平均 2,050 步),而二分查找在最坏情况下只需 12 步(平均 6 步)。以下是一个实现二分查找的 Python 程序:

    def binary_search(x, target, my_min=1, my_max=None):
        if my_max is None:
           my_max = len(x) - 1
        while my_min <= my_max:
          mid = (my_min + my_max)//2
          midval = x[mid]
          if midval < target:
              my_min = my_mid + 1
          elif midval > target:
              my_max = mid - 1
          else:
              return mid
        raise ValueError
    

    以下程序展示了其在搜索隐含波动率中的应用:

    from scipy import log,exp,sqrt,stats
    S=42;X=40;T=0.5;r=0.01;c=3.0
    def bsCall(S,X,T,r,sigma):
        d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T)) 
        d2 = d1-sigma*sqrt(T)
        return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
    #
    def impliedVolBinary(S,X,T,r,c):
        k=1
        volLow=0.001
        volHigh=1.0
        cLow=bsCall(S,X,T,r,volLow)
        cHigh=bsCall(S,X,T,r,volHigh)
        if cLow>c or cHigh<c:
            raise ValueError
        while k ==1:
            cLow=bsCall(S,X,T,r,volLow)
            cHigh=bsCall(S,X,T,r,volHigh)
            volMid=(volLow+volHigh)/2.0
            cMid=bsCall(S,X,T,r,volMid)
            if abs(cHigh-cLow)<0.01:
                k=2
            elif cMid>c:
                volHigh=volMid
            else:
                volLow=volMid
        return volMid, cLow, cHigh
    #
    print("Vol,     cLow,      cHigh")
    print(impliedVolBinary(S,X,T,r,c))
    Vol,     cLow,      cHigh
    (0.16172778320312498, 2.998464657758511, 3.0039730848624977)
    

    根据结果,隐含波动率为 16.17%。在前面的程序中,转换条件,即程序应该停止的条件,是两个看涨期权之间的差异。读者可以设置其他转换条件。为了避免无限循环,我们设置了一个屏幕条件:

        if cLow>c or cHigh<c:
            raise ValueError
    

    从 Yahoo! 财经获取期权数据

    有许多期权数据来源可以用于我们的投资、研究或教学。其中一个来源是 Yahoo! 财经。

    为了检索 IBM 的期权数据,我们有以下程序:

    1. 访问 finance.yahoo.com

    2. 在搜索框中输入 IBM

    3. 点击导航栏中的 期权

    相关页面是 finance.yahoo.com/quote/IBM/options?p=IBM。该网页的截图如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_15.jpg

    波动率微笑与偏斜

    显然,每只股票应有一个波动率值。然而,在估算隐含波动率时,不同的行权价格可能会提供不同的隐含波动率。更具体来说,基于虚值期权、平值期权和实值期权的隐含波动率可能会有显著差异。波动率微笑是指随着行权价格的变化,波动率先下降后上升,而波动率偏斜则表现为向下或向上倾斜。关键在于投资者情绪以及供求关系对波动率偏斜的根本影响。因此,这种微笑或偏斜提供了关于投资者(例如基金经理)是更倾向于卖出看涨期权还是看跌期权的信息,如以下代码所示:

    import datetime
    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData
    
    # Step 1: input area
    infile="c:/temp/callsFeb2014.pkl"
    ticker='IBM'
    r=0.0003                          # estimate
    begdate=datetime.date(2010,1,1)   # this is arbitrary 
    enddate=datetime.date(2014,2,1)   # February 2014
    
    # Step 2: define a function 
    def implied_vol_call_min(S,X,T,r,c): 
        from scipy import log,exp,sqrt,stats 
        implied_vol=1.0
        min_value=1000
        for i in range(10000): 
            sigma=0.0001*(i+1)
            d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T)) 
            d2 = d1-sigma*sqrt(T)
            c2=S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2) 
            abs_diff=abs(c2-c)
            if abs_diff<min_value: 
                min_value=abs_diff 
                implied_vol=sigma 
                k=i
        return implied_vol
    
    # Step 3: get call option data 
    calls=pd.read_pickle(infile)
    exp_date0=int('20'+calls.Symbol[0][len(ticker):9])  # find expiring date
    p = getData(ticker, begdate,enddate,asobject=True, adjusted=True)
    s=p.close[-1]                    # get current stock price 
    y=int(exp_date0/10000)
    m=int(exp_date0/100)-y*100
    d=exp_date0-y*10000-m*100
    exp_date=datetime.date(y,m,d)    # get exact expiring date 
    T=(exp_date-enddate).days/252.0  # T in years
    
    # Step 4: run a loop to estimate the implied volatility 
    n=len(calls.Strike)   # number of strike
    strike=[]             # initialization
    implied_vol=[]        # initialization
    call2=[]              # initialization
    x_old=0               # used when we choose the first strike 
    
    for i in range(n):
        x=calls.Strike[i]
        c=(calls.Bid[i]+calls.Ask[i])/2.0
        if c >0:
            print ('i=',i,'',    c='',c)
            if x!=x_old:
                vol=implied_vol_call_min(s,x,T,r,c)
                strike.append(x)
                implied_vol.append(vol)
                call2.append(c)
                print x,c,vol
                x_old=x
    
    # Step 5: draw a smile 
    plt.title('Skewness smile (skew)') 
    plt.xlabel('Exercise Price') 
    plt.ylabel('Implied Volatility')
    plt.plot(strike,implied_vol,'o')
    plt.show()
    

    注意事项

    请注意,.pickle数据集可以在 canisus.edu/~yan/python/callsFeb2014.pkl 下载。

    与波动率微笑相关的图表如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_16.jpg

    参考文献

    请参考以下文章:

  • Black, F., M. Scholes, 1973, 期权和公司负债定价,《政治经济学杂志》,81(3),637-654www.cs.princeton.edu/courses/archive/fall09/cos323/papers/black_scholes73.pdf

  • Cox, J. C., Ross, S. A., Rubinstein, M, 1979, 期权定价:简化方法,《金融经济学杂志》,7(3),229-263www.sciencedirect.com/science/article/pii/0304405X79900151

  • 附录 A – 数据案例 6:投资组合保险

    投资组合保险是一种通过卖空股指期货来对冲股票投资组合市场风险的方法。当市场方向不确定或波动较大时,机构投资者常常使用这种对冲技术。假设你管理着一个价值 5000 万美元的行业投资组合。如果你预计未来三个月整个市场将非常波动——换句话说,市场可能会大幅下跌——此时我们可能有哪些选择?

  • 选项 #1:立即卖出股票,并在几个月后再买回来

  • 选项 #2:卖出 S&P500 指数期货

  • 显然,第一个选择因交易成本而昂贵:

    1. 获取五个行业投资组合:

      1. 要获取 Fama-French 五行业投资组合,请访问 French 教授的数据图书馆。

      2. 请访问 mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html

      3. 搜索关键词Industry;见下方截图:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_17.jpg

      4. 下载数据并估算这五个行业的贝塔值。让我们看看当市场下跌一个点时会发生什么。今天的 S&P500 水平如下:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_18.jpg

      5. 如果市场下跌一个点,多头头寸(S&P500 期货合约)将损失 250 美元,而空头头寸将获得 250 美元。一个 S&P500 期货合约的规模是指数水平 *250。

      6. 如果我们想对冲我们的 5 美元投资组合,应该空头 n 个期货合约。具体说明见www3.canisius.edu/~yany/doc/sp500futures.pdf

      https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_51.jpg

      这里,Vp是投资组合价值,βp是投资组合贝塔值,指数水平是 S&P500 指数水平。应用上述公式,我们应空头十个期货合约。假设三个月后指数为 2090.4,即下跌了十个点。由于我们知道贝塔值是衡量市场风险的指标,假设年化无风险利率为 1%,即三个月的利率为 0.25%。

    2. 通过应用以下线性回归来估计投资组合的贝塔值:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_52.jpg

    3. 确定市场大幅下跌的几个时刻。

      你可以使用一个名为 Business Cycle 的 Python 数据集:

      import pandas as pd
      x=pd.read_pickle("c:/temp/businessCycle.pkl")
      print(x.head())
      print(x.tail())
      date             
      1926-10-01  1.000
      1926-11-01  0.846
      1926-12-01  0.692
      1927-01-01  0.538
      1927-02-01  0.385
         cycle
      date             
      2009-02-01 -0.556
      2009-03-01 -0.667
      2009-04-01 -0.778
      2009-05-01 -0.889
      2009-06-01 -1.000
      

      提示

      注意,-1 表示经济处于深度衰退,而 1 表示经济在扩张。

    4. 估算有无对冲策略时的损失。你的投资组合损失是多少?如果你空头一个 S&P500 期货合约,收益是多少?

    5. 重复整个过程,假设我们持有 1,000 股 IBM,2,000 股 DELL,5,000 股 Citi Group 和 7,000 股 IBM。

    6. 今天的总市值是多少?

    7. 投资组合的贝塔值是多少?[注:你可以使用最新的五年月度数据来估算贝塔值]

    8. 如果我们想通过使用 S&P500 期货合约来对冲投资组合,应该做多(做空)多少合约?

    9. 如果市场下跌 5%,我们的投资组合损失是多少,套期保值头寸的收益是多少?

    以下公式是通用公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_10_53.jpg

    这里,n是合约数,β是我们的目标贝塔值,VF是一个期货合约的价值。Vpβp在前文已定义。如果 n 为正(负),则表示多头(空头)头寸。在前面的 S&P500 期货使用案例中,VF=S&P500 指数水平 *250。

    提示

    通过使用 S&P500 期货来改变投资组合贝塔值以应对市场不利时机,考虑市场时机。

    练习

    1. 如果年利率为 5%,按季度复利计算,其等效的连续复利利率是多少?

    2. 一个投资组合今天的价值为 477 万美元,β值为 0.88。如果投资组合经理解释市场将在未来三个月上涨,并且他/她打算在仅三个月内通过使用 S&P500 期货将投资组合的 β 值从 0.88 提高到 1.20,那么他/她应该做多少合约的多头或空头?如果 S&P500 指数上涨 70 点,他/她的盈亏是多少?如果 S&P500 下跌 50 点呢?

    3. 编写一个 Python 程序来定价一个看涨期权。

    4. 解释在编写复杂的 Python 程序时,“空壳方法”的含义。

    5. 解释在编写复杂的 Python 程序时,所谓的“注释掉所有代码”方法背后的逻辑。

    6. 解释当我们调试程序时,返回值的用途。

    7. 当我们编写 CND(累积分布标准正态分布)时,我们可以单独定义 a1、a2、a3、a4 和 a5。以下两种方法有何不同?

    8. 当前方法:(a1,a2,a3,a4,a5)=(0.31938153,-0.356563782,1.781477937,-1.821255978,1.330274429)
    9. 一种替代方法:

    10. a1=0.31938153

    11. a2=-0.356563782

    12. a3=1.781477937

    13. a4=-1.821255978

    14. a5=1.330274429

    15. 美式看涨期权和欧式看涨期权有什么区别?

    16. 在 Black-Scholes-Merton 期权模型中,rf 的单位是什么?

    17. 如果给定年利率为 3.4%,半年复利,我们应该在 Black-Scholes-Merton 期权模型中使用哪个 rf 值?

    18. 如何使用期权进行对冲?

    19. 在定价欧洲看涨期权时,如何处理预定的现金股息?

    20. 为什么美式看涨期权比欧式看涨期权更有价值?

    21. 假设你是一个共同基金经理,且你的投资组合的β值与市场高度相关。你担心市场会短期下跌,你可以采取什么措施来保护你的投资组合?

    22. 股票 A 的当前价格为 38.5 美元,看涨期权和看跌期权的行使价格均为 37 美元。如果无风险利率为 3.2%,到期时间为三个月,股票 A 的波动率为 0.25,那么欧洲看涨期权和看跌期权的价格是多少?

    23. 使用买卖平价验证上述解决方案。

    24. 在 9.11) 中,当看涨期权和看跌期权的行使价格不同,我们还能应用买卖平价吗?

    25. 对于一组输入值,如 S=40、X=40、T=3/12=0.25、r=0.05 和 sigma=0.20,使用 Black-Scholes-Merton 期权模型,我们可以估算看涨期权的价值。现在保持所有参数不变,除了 S(股票的当前价格);请展示看涨期权和 S 之间的关系,最好以图表形式展示。

    26. 在看涨期权模型中,有效年利率、半年有效利率和无风险利率的定义是什么?假设当前年化无风险利率为 5%,半年复利,应该使用哪个值作为 Black-Scholes-Merton 看涨期权模型的输入值?

    27. 当股票交易价格为 39 美元,行使价格为 40 美元,到期日为三个月,无风险利率为 3.5%,连续复利,年波动率为 0.15 时,期权的权利金是多少?

    28. 对于风险无关利率仍为每年 3.5% 但按半年复利的情况,重复之前的练习。

    29. 使用他人的程序有哪些优点和缺点?

    30. 你如何调试他人的程序?

    31. 编写一个 Python 程序,将任何给定的按每年 m 次复利计算的 APR 转换为连续复利利率。

    32. 你如何提高累积正态分布的准确性?

    33. APR 和 Rc(连续复利率)之间的关系是什么?

    34. 对于当前股价为 52.34 美元的股票,如果行使价格与当前股价相同,期满时间为六个月,年波动率为 0.16,风险无关利率为 3.1%,且按连续复利计算,计算其看涨期权的价格。

    35. 对于一组 SXTr 和 sigma,我们可以使用这 13 行 Python 代码来估算一个欧洲看涨期权。当当前股价 S 增加,而其他输入值不变时,看涨期权的价格会增加还是减少?为什么?

    36. 以图形方式展示前述结果。

    37. 当行使价格 X 增加时,看涨期权的价值会下降。这个说法对吗?为什么?

    38. 如果其他输入值保持不变,股票的 sigma 增加时,看涨期权溢价会增加。这个说法对吗?为什么?
    39. 对于一组输入值 SXTr 和 sigma,我们可以使用本章中的代码来定价欧洲看涨期权,即 C。另一方面,如果我们观察到一个实际的看涨期权溢价(Cobs),并且拥有一组值 SXTr,我们可以估计出隐含波动率(sigma)。指定一个试错法来粗略估计隐含波动率(如果一个新手没有解答这个问题是完全可以的,因为我们会 dedicating 一整章来讨论如何做到这一点)。

    40. 根据所谓的看涨看跌平价公式,它表明一个到期时持有足够现金的看涨期权(X 美元)与持有一只标的股票的看跌期权是等价的——在此,两者的行使价格(X)和到期时间(T)相同,且均为欧洲期权——如果股票价格为 10 美元,行使价格为 11 美元,到期时间为六个月,风险无关利率为 2.9%,且按半年复利计算,那么该欧洲看跌期权的价格是多少?

    总结

    在本章中,我们首先解释了许多与投资组合理论相关的基本概念,例如协方差、相关性,如何计算二股票投资组合的方差以及 n 股票投资组合的方差公式。接着,我们讨论了针对个别股票或投资组合的各种风险衡量标准,如夏普比率、特雷诺比率、索提诺比率,如何基于这些衡量标准(比率)来最小化投资组合风险,如何设置目标函数,如何为给定的股票集选择一个有效的投资组合,以及如何构建有效前沿。

    在下一章中,我们将讨论现代金融中最重要的理论之一:期权和期货。我们将从基本概念入手,例如看涨期权和看跌期权的收益函数。接着,我们将解释相关的应用,如各种交易策略、公司激励计划以及对冲策略,包括不同类型的期权和期货。

    第十一章:风险价值(VaR)

    在金融领域,理性投资者在隐性或显性地,总是考虑风险与回报之间的权衡。通常,衡量回报没有歧义。然而,在衡量风险方面,我们有许多不同的度量标准,如使用收益的方差和标准差来衡量总风险,个股的贝塔值,或投资组合贝塔值来衡量市场风险。在前几章中,我们知道总风险有两个组成部分:市场风险和公司特定风险。为了平衡回报的收益和风险的成本,可以应用多种度量方法,如夏普比率、特雷诺比率、索提诺比率和 M2 绩效度量(莫迪利安尼与莫迪利安尼绩效度量)。所有这些风险度量或比率都有一个共同的格式:即回报的收益(以风险溢价表示)和风险(以标准差、贝塔值或下偏标准差LPSD)表示)之间的权衡。另一方面,这些度量并未考虑概率分布。在本章中,将介绍一个新的风险度量——风险价值VaR),并通过使用现实世界的数据来应用它。特别地,将涵盖以下主题:

  • VaR 介绍

  • 正态分布的密度函数和累积分布函数回顾

  • 方法一—基于正态性假设估算 VaR

  • 从 1 天风险转换为 n 天风险,一天 VaR 与 n 天 VaR 的比较

  • 正态性检验

  • 偏度和峰度的影响

  • 通过包含偏度和峰度来修正 VaR 度量

  • 方法二—基于历史收益估算 VaR

  • 使用蒙特卡洛模拟将两种方法联系起来

  • 回测和压力测试

  • VaR 介绍

    目前为止,我们有几种方法来评估个股或投资组合的风险,比如使用收益的方差和标准差来衡量总风险,或者用贝塔值来衡量投资组合或个股的市场风险。另一方面,许多 CEO 更倾向于使用一个简单的指标,称为风险价值VaR),它有一个简单的定义:

    “在预定时间段内,具有一定置信水平的最大损失。”

    从前面的定义来看,它有三个明确的因素,再加上一个隐含的因素。隐含因素或变量是我们当前的位置,或者说我们当前投资组合或个股的价值。前述陈述提供了未来可能的最大损失,这是第一个因素。第二个因素是在特定的时间段内。这两个因素是相当常见的。然而,最后一个因素是非常独特的:它带有置信水平或概率。以下是几个例子:

  • 示例 #1:在 2017 年 2 月 7 日,我们持有 300 股国际商业机器公司的股票,市值为$52,911。明天,即 2017 年 2 月 8 日,最大损失为$1,951,置信水平为 99%。

  • 示例 #2:我们今天的共同基金价值为 1000 万美元。在接下来的三个月里,基于 95%的置信水平,最大损失为 50 万美元。

  • 示例 #3:我们银行的价值为 2 亿美元。我们银行的 VaR 为 1000 万美元,具有 1%的概率,时间跨度为接下来的 6 个月。

  • 通常,有两种方法来估算 VaR。第一种方法基于假设我们的证券或投资组合回报遵循正态分布,而第二种方法依赖于历史回报的排名。在讨论第一种方法之前,我们先复习一下正态分布的相关概念。正态分布的密度在这里定义:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_05.jpg

    这里,f(x)是密度函数,x是输入变量,μ是均值,σ是标准差。可以使用一个叫做spicy.stats.norm.pdf()的函数来估算密度。该函数有三个输入值:xμσ。以下代码调用此函数,并根据前面的公式手动验证结果:

    import scipy.stats as stats
    from scipy import sqrt, exp,pi
    d1=stats.norm.pdf(0,0.1,0.05)      
    print("d1=",d1)
    d2=1/sqrt(2*pi*0.05**2)*exp(-(0-0.1)**2/0.05**2/2)  # verify manually
    print("d2=",d2) 
    ('d1=', 1.0798193302637611)
    ('d2=', 1.0798193302637611)
    

    在前面的代码中,我们导入了sqrt()exp()函数以及π,以简化我们的代码。设置μ=0,σ=1,前面的正态分布密度函数简化为标准正态分布;请看其对应的密度函数:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_06.jpg

    spicy.stats.norm.pdf()函数的第二个和第三个输入值的默认值分别为 0 和 1。换句话说,只需要一个输入值,它就代表了标准正态分布;请查看以下代码并了解如何手动验证:

    from scipy import exp,sqrt,stats,pi
    d1=stats.norm.pdf(0)
    print("d1=",d1)
    d2=1/sqrt(2*pi)           # verify manually
    print("d2=",d2)
    ('d1=', 0.3989422804014327)
    ('d2=', 0.3989422804014327)
    

    以下代码生成了标准正态分布的图形,其中spicy.stats.norm.pdf()函数只接受一个输入值:

    import scipy as sp
    import matplotlib.pyplot as plt
    x = sp.arange(-3,3,0.1)
    y=sp.stats.norm.pdf(x)
    plt.title("Standard Normal Distribution")
    plt.xlabel("X")
    plt.ylabel("Y")
    plt.plot(x,y)
    plt.show()
    

    下面是图形展示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_01.jpg

    对于 VaR 估算,通常我们会选择 95%和 99%两个置信水平。对于 95%(99%)置信水平,我们实际上关注的是左尾的 5%(1%)概率。以下图表展示了基于标准正态分布和 95%置信水平的 VaR 概念:

    import scipy as sp
    from matplotlib import pyplot as plt
    z=-2.325       # user can change this number 
    xStart=-3.8    # arrow line start x
    yStart=0.2     # arrow line start x
    xEnd=-2.5      # arrow line start x
    yEnd=0.05      # arrow line start x
    def f(t):
        return sp.stats.norm.pdf(t) 
    
    plt.ylim(0,0.45)
    x = sp.arange(-3,3,0.1) 
    y1=f(x)
    plt.plot(x,y1)
    x2= sp.arange(-4,z,1/40.) 
    sum=0
    delta=0.05
    s=sp.arange(-10,z,delta) 
    for i in s:
        sum+=f(i)*delta
    
    plt.annotate('area is '+str(round(sum,4)),xy=(xEnd,yEnd),xytext=(xStart,yStart), arrowprops=dict(facecolor='red',shrink=0.01))
    plt.annotate('z= '+str(z),xy=(z,0.01)) 
    plt.fill_between(x2,f(x2))
    plt.show()
    

    要生成图形,应用了三个函数。matplotlib.pyplot.annotate()函数的作用是生成一个文本或带有文本描述的箭头。str()函数将数字转换为字符串。matplotlib.pyplot.fill_between()将填充指定区域。输出的图形如下所示:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_02.jpg

    基于正态分布假设,我们有以下一般形式来估算 VaR:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_07.jpg

    在这里,VaR 是我们的风险价值,position 是我们投资组合的当前市场价值,μperiod 是预期的期间回报,z 是一个根据置信水平确定的临界值,σ 是我们投资组合的波动性。对于正态分布,z=2.33 对应于 99% 的置信水平,而 z=1.64 对应于 95% 的置信水平。由于我们可以使用 scipy.stats.norm.ppf() 来获取 z 值,因此前述方程可以重写为:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_08.jpg

    比较前述两个方程。仔细的读者应该注意到,z 前的符号是不同的。对于前面的方程,它有一个正号,而不是前一个方程中的负号。原因是,应用 scipy.stats.norm.ppf() 估算出的 z 值会是负数;请参见以下代码:

    from scipy.stats import norm
    confidence_level=0.99
    z=norm.ppf(1-confidence_level)
    print(z)
    -2.32634787404
    

    当时间周期较短,如 1 天时,我们可以忽略 μperiod 的影响。因此,我们有以下最简单的形式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_09.jpg

    以下程序显示了一个假设的盈亏概率密度函数的 5% VaR:

    import scipy as sp
    import scipy as sp
    from scipy.stats import norm
    from matplotlib import pyplot as plt
    
    confidence_level=0.95   # input 
    z=norm.ppf(1-confidence_level) 
    def f(t):
        return sp.stats.norm.pdf(t)
    #
    plt.ylim(0,0.5)
    x = sp.arange(-7,7,0.1) 
    ret=f(x)
    plt.plot(x,ret)
    x2= sp.arange(-4,z,1/40.) 
    x3=sp.arange(z,4,1/40.)
    sum=0
    delta=0.05
    s=sp.arange(-3,z,delta) 
    for i in s:
        sum+=f(i)*delta
    note1='Red area to the left of the'
    note2='dotted red line reprsesents'
    note3='5% of the total area'
    #
    note4='The curve represents a hypothesis'
    note5='profit/loss density function. The'
    note6='5% VaR is 1.64 standard deviation'
    note7='from the mean, i.e.,zero'
    #
    note8='The blue area to the righ of the'
    note9='red dotted line represents 95%'
    note10='of the returns space'
    # this is for the vertical line
    plt.axvline(x=z, ymin=0.1, ymax = 1, linewidth=2,ls='dotted', color='r')
    plt.figtext(0.14,0.5,note1)
    plt.figtext(0.14,0.47,note2)
    plt.figtext(0.14,0.44,note3)
    #
    plt.figtext(0.5,0.85,note4)
    plt.figtext(0.5,0.82,note5)
    plt.figtext(0.5,0.79,note6)
    plt.figtext(0.5,0.76,note7)
    plt.annotate("",xy=(-2.5,0.08),xytext=(-2.5,0.18), arrowprops=dict(facecolor='red',shrink=0.001))
    #
    plt.figtext(0.57,0.5,note8)
    plt.figtext(0.57,0.47,note9)
    plt.figtext(0.57,0.44,note10)
    plt.annotate("",xy=(1.5,0.28),xytext=(4.5,0.28), arrowprops=dict(facecolor='blue',shrink=0.001))
    #
    plt.annotate('z= '+str(z),xy=(2.,0.1)) 
    plt.fill_between(x2,f(x2), color='red')
    plt.fill_between(x3,f(x3), color='blue')
    plt.title("Visual presentation of VaR, 5% vs. 95%")
    plt.show()
    

    相关图表如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_03.jpg

    这是估算明天最大损失的最简单示例。假设我们在 2017 年 2 月 7 日拥有 1,000 股 IBM 的股票。以 99% 的置信水平,明天的最大损失是多少?为了估算每日回报的标准差,我们使用过去 5 年的数据。实际上,这是一个决策变量。我们可以使用 1 年的数据或多年的数据。每种方法都有其优缺点。基于较长时间段估算的标准差会更稳定,因为我们有更大的样本量。然而,远过去的一些信息肯定会过时:

    import numpy as np
    import pandas as pd
    from scipy.stats import norm
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData
    #
    # input area
    ticker='IBM'              # input 1
    n_shares=1000             # input 2
    confidence_level=0.99     # input 3
    begdate=(2012,2,7)        # input 4
    enddate=(2017,2,7)        # input 5
    #
    z=norm.ppf(1-confidence_level) 
    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    print(x[0])
    ret = x.aclose[1:]/x.aclose[:-1]-1
    #
    position=n_shares*x.close[0] 
    std=np.std(ret)
    #
    VaR=position*z*std
    print("Holding=",position, "VaR=", round(VaR,4), "tomorrow")
    (datetime.date(2012, 2, 7), 2012, 2, 7, 734540.0, 167.75861437920275, 168.543152, 169.23178870104016, 167.34020198573538, 3433000.0, 168.543152)
    ('Holding=', 168543.152, 'VaR=', -4603.5087, 'tomorrow')
    

    打印数据第一行的目的是为了显示收盘价确实是在 2017 年 2 月 7 日。我们的持仓价值为 168,543 美元,其 1 日 VaR 为 4,604 美元。第二个示例是关于 10 天期间的 VaR。要将每日回报的方差(标准差)转换为 n 天的方差(标准差),我们有以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_10.jpg

    例如,年波动率等于日波动率乘以 252 的平方根 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_35.jpg。为了将每日平均回报转换为 n 天的平均回报,我们有以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_11.jpg

    基于每日回报,我们有以下一般公式来估算 n 天 VaR 的置信水平:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_12.jpg

    以下代码显示了在 2016 年最后一天,持有 50 股沃尔玛股票,在 99% 置信水平下的 10 天 VaR:

    import numpy as np
    import pandas as pd
    from scipy.stats import norm
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData
    ticker='WMT'            # input 1
    n_shares=50             # input 2
    confidence_level=0.99   # input 3
    n_days=10               # input 4
    begdate=(2012,1,1)      # input 5
    enddate=(2016,12,31)    # input 6
    
    z=norm.ppf(confidence_level) 
    
    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    ret = x.aclose[1:]/x.aclose[:-1]-1 
    position=n_shares*x.close[0] 
    VaR=position*z*np.std(ret)*np.sqrt(n_days)
    print("Holding=",position, "VaR=", round(VaR,4), "in ", n_days, "Days")
    ('Holding=', 2650.3070499999999, 'VaR=', 205.0288, 'in ', 10, 'Days')
    

    2016 年 12 月 31 日,我们的持仓价值为$2,650。我们在接下来的 10 天内的最大损失为$205,置信水平为 99%。在前面的程序中,我们基于日收益率估算了日均收益和标准差。然后我们将其转换为 10 天的平均收益和 10 天的波动率。另一方面,实际上我们可以直接计算 10 天的收益率。在 10 天收益率可用后,可以直接应用scipy.mean()scipy.std()函数。换句话说,我们不需要将日均收益和日标准差转换为 10 天均值和 10 天标准差。相关代码如下。为了节省空间,前 11 行没有重复:

    x = getData(ticker, begdate, enddate,asobject=True, adjusted=True)
    logret = np.log(x.aclose[1:]/x.aclose[:-1])
    
    # method 2: calculate 10 day returns 
    ddate=[]
    d0=x.date
    for i in range(0,np.size(logret)): 
        ddate.append(int(i/nDays))
    y=pd.DataFrame(logret,ddate,columns=['retNdays']) 
    retNdays=y.groupby(y.index).sum()
    #print(retNdays.head())
    position=n_shares*x.close[0] 
    VaR=position*z*np.std(retNdays)
    print("Holding=",position, "VaR=", round(VaR,4), "in ", nDays, "Days")
    ('Holding=', 2650.3070499999999, 'VaR=', 209.1118, 'in ', 10, 'Days')
    

    我们的新结果显示,VaR 为$209.11,相比之下,原值为$205.03。低估的百分比为-0.01951126,约为-2%。以下代码估算了 Fama-French 五个按市值加权的行业组合的 VaR,数据频率为月度。数据集可以在作者的官网获取,链接:canisius.edu/~yany/python/ff5VWindustryMonthly.pkl。这五个行业分别是消费品、制造业、高科技、健康和其他。以下是前几行和最后几行代码:

    import pandas as pd
    x=pd.read_pickle("c:/temp/ff5VWindustryMonthly.pkl")
    print(x.head())
    print(x.tail())
             CNSMR   MANUF   HITEC   HLTH    OTHER
    192607  0.0543  0.0273  0.0183  0.0177  0.0216
    192608  0.0276  0.0233  0.0241  0.0425  0.0438
    192609  0.0216 -0.0044  0.0106  0.0069  0.0029
    192610 -0.0390 -0.0242 -0.0226 -0.0057 -0.0285
    192611  0.0370  0.0250  0.0307  0.0542  0.0211
             CNSMR   MANUF   HITEC   HLTH    OTHER
    201608 -0.0101  0.0040  0.0068 -0.0323  0.0326
    201609 -0.0143  0.0107  0.0202  0.0036 -0.0121
    201610 -0.0252 -0.0231 -0.0141 -0.0743  0.0059
    201611  0.0154  0.0539  0.0165  0.0137  0.1083
    201612  0.0132  0.0158  0.0163  0.0084  0.0293
    

    以下程序估算了将$1,000 投资于每个行业组合的 VaR,置信水平为 99%,计算的是下一个周期的风险。由于数据频率为月度,因此固定周期为下一个月:

    import pandas as pd
    import scipy as sp
    from scipy.stats import norm
    #
    confidence_level=0.99   # input 
    position=([1000,1000,1000,1000,1000])
    z=norm.ppf(1-confidence_level)
    x=pd.read_pickle("c:/temp/ff5VWindustryMonthly.pkl")
    #
    std=sp.std(x,axis=0)
    mean=sp.mean(x,axis=0)
    #
    t=sp.dot(position,z)
    VaR=t*std
    #
    # output area
    print(sp.shape(x))
    print("Position=",position)
    print("VaR=")
    print(VaR)
    1086, 5)
    ('Position=', [1000, 1000, 1000, 1000, 1000])
    VaR=
    CNSMR   -122.952735
    MANUF   -128.582446
    HITEC   -129.918893
    HLTH    -130.020356
    OTHER   -149.851230
    dtype: float64
    

    对于这五个行业,VaR 分别为$122.95、$128.58、$129.92、$130.02 和$149.85,假设每个行业投资$1,000。通过比较这些值,我们可以看到,消费品行业的风险最低,而被定义为“其他”的行业具有最高的最大可能损失。

    正态性检验

    第一个估算 VaR 的方法是基于一个重要的假设,即个别股票或组合的收益符合正态分布。然而,在现实世界中,我们知道股票收益或组合收益不一定符合正态分布。以下程序通过使用 5 年的日数据,检验微软的收益是否满足这个假设:

    from scipy import stats 
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData 
    import numpy as np 
    #	
    ticker='MSFT' 
    begdate=(2012,1,1) 
    enddate=(2016,12,31) 
    #
    p =getData(ticker, begdate, enddate,asobject=True, adjusted=True) 
    ret = (p.aclose[1:] - p.aclose[:-1])/p.aclose[1:] 
    print 'ticker=',ticker,'W-test, and P-value' 
    print(stats.shapiro(ret))
    print( stats.anderson(ret))
    ticker= MSFT W-test, and P-value
    (0.9130843877792358, 3.2116320877511604e-26)
    AndersonResult(statistic=14.629260310763584, critical_values=array([ 0.574,  0.654,  0.785,  0.915,  1.089]), significance_level=array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))
    

    我们的原假设是微软股票的日收益符合正态分布。根据前面的结果,我们拒绝原假设,因为 F 值远高于临界值 1.089(假设显著性水平为 1%)。即使我们基于单只股票拒绝了这个假设,也有人可能会认为组合的收益可能符合这一假设。下一个程序检验 S&P500 的日收益是否符合正态分布。S&P500 的股票代码是^GSPC,可以通过 Yahoo!Finance 获取:

    import numpy as np 
    from scipy import stats 
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData 
    #
    ticker='^GSPC'    # ^GSPC is for S&P500
    begdate=(2012,1,1) 
    enddate=(2016,12,31) 
    #
    p =getData(ticker, begdate, enddate,asobject=True, adjusted=True) 
    ret = (p.aclose[1:] - p.aclose[:-1])/p.aclose[1:] 
    print 'ticker=',ticker,'W-test, and P-value' 
    print(stats.shapiro(ret))
    print( stats.anderson(ret) )
    ticker= ^GSPC W-test, and P-value
    (0.9743353128433228, 3.7362179458122827e-14)
    AndersonResult(statistic=8.6962226557502618, critical_values=array([ 0.574,  0.654,  0.785,  0.915,  1.089]), significance_level=array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))
    

    从前面的结果来看,我们拒绝了 S&P500 的正态性假设。换句话说,由 S&P500 日收益表示的市场指数不符合正态分布。

    偏度和峰度

    基于正态性假设,VaR 估算只考虑前两个矩:均值和方差。如果股票回报真的遵循正态分布,这两个矩将完全定义它们的概率分布。从前面的章节可以知道,这并不成立。第一种修正方法是除了前两个矩之外,加入其他更高阶的矩。第三和第四阶矩分别称为偏度和峰度。对于一个具有 n 个回报的股票或投资组合,偏度通过以下公式估算:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_13.jpg

    在这里,偏度是偏度,Ri是第i个回报,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_36.jpg是平均回报,n是回报的数量,σ是回报的标准差。峰度反映了极端值的影响,因为四次方的值非常高。峰度通常通过以下公式估算:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_14.jpg

    对于标准正态分布,它的均值为零,方差为 1,偏度为零,峰度为 3。因此,有时峰度被定义为前面的公式减去 3:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_15.jpg

    一些教科书将这两种定义区分为峰度和超额峰度。然而,也有一些教科书简单地将前面的公式也标记为峰度。因此,当我们进行检验以查看时间序列的峰度是否为零时,我们必须知道使用的是哪个基准。以下程序生成了 500 万随机数来自标准差,并应用四个函数来估算这四个矩,即均值、标准差、偏度和峰度:

    from scipy import stats,random
    import numpy as np
    np.random.seed(12345)
    n=5000000   
    #
    ret = random.normal(0,1,n)
    print('mean    =', np.mean(ret))
    print('std     =',np.std(ret))
    print('skewness=',stats.skew(ret))
    print('kurtosis=',stats.kurtosis(ret))
    ('mean    =', 0.00035852273706422504)
    ('std     =', 0.99983435063933623)
    ('skewness=', -0.00040545999711941665)
    ('kurtosis=', -0.001162270913658947)
    

    由于从标准正态分布中抽取的随机数的峰度接近零,scipy.stats.kurtosis()函数应基于公式(11)而不是公式(10)

    修正 VaR

    从前面的讨论中我们知道,基于假设,股票回报服从正态分布。因此,回报的偏度和峰度都假设为零。然而,在现实世界中,许多股票回报的偏度和超额峰度并不为零。因此,开发了修正 VaR,利用这四个矩而不仅仅是两个;请参见以下定义:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_16.jpg

    这里,z是基于正态分布的值,S是偏度,K是峰度,t是一个中间变量,scipy.stats.ppf()函数为给定的置信水平提供一个 z 值。以下程序提供了基于正态性假设和基于前述公式(即使用所有四个矩)计算的两种 VaR。年末时持有的股票数量为 500 只,测试的股票是沃尔玛WMT)。1 天 VaR 的置信水平为 99%:

    import numpy as np
    import pandas as pd
    from scipy.stats import stats,norm
    from matplotlib.finance import quotes_historical_yahoo_ochl as getData
    #
    ticker='WMT'            # input 1
    n_shares=500            # input 2
    confidence_level=0.99   # input 3
    begdate=(2000,1,1)      # input 4
    enddate=(2016,12,31)    # input 5
    #
    # Method I: based on the first two moments
    z=abs(norm.ppf(1-confidence_level)) x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    ret = x.aclose[1:]/x.aclose[:-1]-1
    position=n_shares*x.close[0] 
    mean=np.mean(ret)
    std=np.std(ret)
    VaR1=position*(mean-z*std)
    print("Holding=",round(position,2), "VaR1=", round(VaR1,2), "for 1 day ")
    #
    # Modified VaR: based on 4 moments
    s=stats.skew(ret)
    k=stats.kurtosis(ret)
    t=z+1/6.*(z**2-1)*s+1/24.*(z**3-3*z)*k-1/36.*(2*z**3-5*z)*s**2
    mVaR=position*(mean-t*std)
    print("Holding=",round(position,2), "modified VaR=", round(mVaR,2), "for 1 day ")
    ('Holding=', 24853.46, 'VaR1=', -876.84, 'for 1 day ')
    ('Holding=', 24853.46, 'modified VaR=', -1500.41, 'for 1 day ')
    

    根据最后两行,我们得出基于正态分布的 VaR 为 $876.84,修正后的 VaR 为 $1,500。两者之间的百分比差异为 42%。这一结果表明,忽略偏度和峰度将极大低估 VaR。

    基于排序的历史收益 VaR

    我们知道,股票收益不一定遵循正态分布。另一种方法是使用排序后的收益来评估 VaR。这种方法称为基于历史收益的 VaR。假设我们有一个名为 ret 的每日收益向量。我们将其从最小到最大排序。我们将排序后的收益向量称为 sorted_ret。对于给定的置信水平,一期 VaR 计算公式如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_17.jpg

    这里,position 是我们的财富(投资组合的价值),confidence 是置信水平,n 是收益的数量。len() 函数显示观察值的数量,int() 函数取输入值的整数部分。例如,如果收益向量的长度是 200,且置信水平为 99%,那么排序后的收益向量中第二个值(200*0.01),从最小到最大,再乘以我们的财富,就是我们的 VaR。显然,如果我们有更长的时间序列,也就是更多的收益观察值,我们的最终 VaR 将更加准确。比如拥有 500 股沃尔玛股票,99% 的置信水平下,第二天的最大损失是多少?首先,让我们看看几种排序数据的方法。第一种方法使用 numpy.sort() 函数:

    import numpy as np
    a = np.array([[1,-4],[9,10]])
    b=np.sort(a)                
    print("a=",a)
    print("b=",b)
    ('a=', array([[ 1, -4],
           [ 9, 10]]))
    ('b=', array([[-4,  1],
           [ 9, 10]]))
    

    这是使用 Python pandas 模块进行排序的第二种方法:

    import pandas as pd
    a = pd.DataFrame([[9,4],[9,2],[1,-1]],columns=['A','B'])
    print(a)
    # sort by A ascedning, then B descending 
    b= a.sort_values(['A', 'B'], ascending=[1, 0])
    print(b)
    # sort by A and B, both ascedning 
    c= a.sort_values(['A', 'B'], ascending=[1, 1])
    print(c)
    

    为了方便比较,这三个数据集并排放置。左侧面板显示原始数据集。中间面板显示按列 A 升序排序后,再按列 B 降序排序的结果。右侧面板显示按列 AB 都升序排序的结果:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_04.jpg

    接下来的两个程序比较了两种估算 VaR(风险价值)的方法:基于正态分布的方法和基于排序的方法。为了让我们的程序更易理解,时间段仅为 1 天:

    #
    z=norm.ppf(confidence_level) 
    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    ret = x.aclose[1:]/x.aclose[:-1]-1
    #
    position=n_shares*x.close[0] 
    std=np.std(ret)
    #
    VaR=position*z*std
    print("Holding=",position, "VaR=", round(VaR,4), "tomorrow")
    ('Holding=', 26503.070499999998, 'VaR=', 648.3579, 'tomorrow')
    

    上面程序中使用的公式是 VaR=positionzsigma。结果告诉我们,持仓为 $26,503,1 天 VaR 为 $648,置信水平为 99%。以下程序基于排序估算同一股票的 VaR:

    ret = np.array(x.aclose[1:]/x.aclose[:-1]-1)
    ret2=np.sort(ret) 
    #
    position=n_shares*x.close[0] 
    n=np.size(ret2)
    leftTail=int(n*(1-confidence_level))
    print(leftTail)
    #
    VaR2=position*ret2[leftTail]
    print("Holding=",position, "VaR=", round(VaR2,4), "tomorrow")
    ('Holding=', 26503.070499999998, 'VaR=', -816.7344, 'tomorrow')
    

    结果显示,1 天 VaR 为 $817。回想一下,基于正态分布的 VaR 为 $648。如果第二种方法更准确,第一种方法将我们的潜在损失低估了 20%。在风险评估中,这个差异非常巨大!以下代码是基于排序的 n 天期的 VaR:

    ret = x.aclose[1:]/x.aclose[:-1]-1
    position=n_shares*x.close[0] 
    #
    # Method 1: based on normality 
    mean=np.mean(ret)
    std=np.std(ret)
    meanNdays=(1+mean)**nDays-1
    stdNdays=std*np.sqrt(nDays)
    z=norm.ppf(confidence_level) 
    VaR1=position*z*stdNdays
    print("Holding=",position, "VaR1=", round(VaR1,0), "in ", nDays, "Days")
    #
    # method 2: calculate 10 day returns 
    ddate=[]
    d0=x.date
    for i in range(0,np.size(logret)): 
        ddate.append(int(i/nDays))
    y=pd.DataFrame(logret,index=ddate,columns=['retNdays']) 
    logRet=y.groupby(y.index).sum()
    retNdays=np.exp(logRet)-1
    # 
    VaR2=position*z*np.std(retNdays)
    print("Holding=",position, "VaR2=", round(VaR2,0), "in ", nDays, "Days")
    # 
    # Method III
    ret2=np.sort(retNdays) 
    n=np.size(ret2)
    leftTail=int(n*(1-confidence_level))
    print(leftTail)
    #
    VaR3=position*ret2[leftTail]
    print("Holding=",position, "VaR=", round(VaR3,0), "in ",nDays, "Days")
    ('Holding=', 24853.456000000002, 'VaR1=', 2788.0, 'in ', 10, 'Days')
    ('Holding=', 24853.456000000002, 'VaR2=', 2223.0, 'in ', 10, 'Days')
    4
    ('Holding=', 24853.456000000002, 'VaR=', 1301.0, 'in ', 10, 'Days')
    

    在前面的程序中有两个技巧。第一个技巧是将每日对数收益率的总和转化为 10 天的对数收益率。然后,我们将对数收益率转换为百分比收益率。第二个技巧是如何生成 10 天的收益率。首先,我们使用int()函数生成组,即int(i/nDays)。由于nDays的值为 10,int(i/10)会生成 10 个零、10 个一、10 个二,依此类推。基于三种方法的 VaR 分别为$2,788、$2,223 和$1,301。显然,第三种方法存在一些问题。一个问题是,对于 n 天的周期,我们只有 428 个观察值,也就是说样本的大小可能太小。如果我们选择 99%的置信区间,则必须选择计算中的第四低收益。这肯定会导致一些问题。

    模拟与 VaR

    在前面的章节中,我们学习了两种估算单只股票或投资组合 VaR 的方法。第一种方法假设股票收益符合正态分布。第二种方法使用排序后的历史收益。那么这两种方法之间有什么联系呢?实际上,蒙特卡洛模拟可以作为它们之间的桥梁。首先,让我们来看一下基于正态假设的第一种方法。我们在 2016 年最后一天持有 500 股沃尔玛股票。如果明天的置信区间为 99%,那么明天的 VaR 是多少?

    #
    position=n_shares*x.close[0] 
    mean=np.mean(ret)
    std=np.std(ret)
    #
    VaR=position*(mean+z*std)
    print("Holding=",position, "VaR=", round(VaR,4), "tomorrow")
    ('Holding=', 26503.070499999998, 'VaR=', -641.2911, 'tomorrow')
    

    明天的 VaR 为$641.29,置信水平为 99%。下面是蒙特卡洛模拟的工作原理。首先,我们基于每日收益计算均值和标准差。由于假设股票收益符合正态分布,我们可以生成 5,000 个收益,均值和标准差相同。如果我们的置信水平为 99%,那么从按升序排列的收益中,位于第 50 个的收益将是我们的截断点,50000.01=50*。以下是代码:

    #
    position=n_shares*x.close[0] 
    mean=np.mean(ret)
    std=np.std(ret)
    #
    n_simulation=5000
    sp.random.seed(12345) 
    ret2=sp.random.normal(mean,std,n_simulation) 
    ret3=np.sort(ret2) 
    m=int(n_simulation*(1-confidence_level))
    VaR=position*(ret3[m])
    print("Holding=",position, "VaR=", round(VaR,4), "tomorrow")
    ('Holding=', 26503.070499999998, 'VaR=', -627.3443, 'tomorrow')
    

    与基于公式的$641.29 相比,蒙特卡洛模拟给出的值为$627.34,差异相对较小。

    投资组合的 VaR

    在第九章中,投资组合理论展示了当我们将多只股票放入投资组合时,可以降低或消除公司特定的风险。估算 n 只股票投资组合收益率的公式如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_18.jpg

    这里的Rp,t是时刻t的投资组合收益,wi是股票i的权重,Ri,t是股票i在时刻t的收益。谈到预期收益或均值时,我们有一个非常相似的公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_19.jpg

    在这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_37.jpg是平均或预期的投资组合收益率,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_38.jpg是股票i的平均或预期收益率。这样一个 n 只股票投资组合的方差公式如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_20.jpg

    在这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_39.jpg是投资组合方差,σi,j 是股票 i 和股票 j 之间的协方差;请参见以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_21.jpg

    股票i与股票j之间的相关性ρi,j定义如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_22.jpg

    当股票之间没有完全正相关时,组合股票会降低我们的投资组合风险。以下程序显示,投资组合的 VaR 不仅仅是单个股票 VaR 的简单加总或加权 VaR:

    from matplotlib.finance import quotes_historical_yahoo_ochl as getData
    
    # Step 1: input area
    tickers=('IBM','WMT','C')  # tickers
    begdate=(2012,1,1)         # beginning date 
    enddate=(2016,12,31)       # ending date
    weight=(0.2,0.5,0.3)       # weights
    confidence_level=0.99      # confidence level 
    position=5e6               # total value
    #
    z=norm.ppf(confidence_level) 
    # Step 2: define a function
    def ret_f(ticker,begdate,enddte):
        x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
        ret=x.aclose[1:]/x.aclose[:-1]-1
        d0=x.date[1:]
        return pd.DataFrame(ret,index=d0,columns=[ticker])
    # Step 3
    n=np.size(tickers)
    final=ret_f(tickers[0],begdate,enddate)
    for i in np.arange(1,n):
        a=ret_f(tickers[i],begdate,enddate)
        if i>0:
            final=pd.merge(final,a,left_index=True,right_index=True)
    #
    # Step 4: get porfolio returns
    portRet=sp.dot(final,weight)
    portStd=sp.std(portRet)
    portMean=sp.mean(portRet)
    VaR=position*(portMean-z*portStd)
    print("Holding=",position, "VaR=", round(VaR,2), "tomorrow")
    
    # compare
    total2=0.0
    for i in np.arange(n):
        stock=tickers[i]
        ret=final[stock]
        position2=position*weight[i]
        mean=sp.mean(ret)
        std=sp.std(ret)
        VaR=position2*(mean-z*std)
        total2+=VaR
        print("For ", stock, "with a value of ", position2, "VaR=", round(VaR,2))
    print("Sum of three VaR=",round(total2,2))
    ('Holding=', 5000000.0, 'VaR=', -109356.22, 'tomorrow')
    ('For ', 'IBM', 'with a value of ', 1000000.0, 'VaR=', -27256.67)
    ('For ', 'WMT', 'with a value of ', 2500000.0, 'VaR=', -60492.15)
    ('For ', 'C', 'with a value of ', 1500000.0, 'VaR=', -59440.77)
    ('Sum of three VaR=', -147189.59)
    

    我们当前投资组合的 VaR 为$109,356。然而,基于权重计算的这三只股票的 VaR 总和为$147,190。这个结果验证了通过选择不同股票来实现的分散化效应。

    回测与压力测试

    在金融领域,压力测试可以看作是一种分析或模拟,旨在确定特定金融工具(如 VaR)在经济危机中的应对能力。由于估算 VaR 的首个方法基于股票回报服从正态分布的假设,其准确性取决于股票回报在现实中偏离这一假设的程度。模型验证是基于模型的风险管理实施的关键组成部分。也就是说,我们需要某种方法来确定所选择的模型是否准确且一致地执行。对于公司及其监管机构而言,这一步非常重要。根据 Lopez(2000 年)的研究,以下是表格:

    名称 目标 方法
    回测 将观察到的结果与模型的预期输出进行比较 预测评估确立了一个具有大量学术文献的经验问题
    压力测试 在极端条件下展示模型的预期结果
  • 投影分析

  • 异常值分析

  • 情景分析与案例研究

  • |

    表 11.1 回测与压力测试

    假设我们仅使用 1 年的数据来估算 2017 年 2 月 7 日持有 1,000 股 IBM 的 1 天 VaR,置信度为 99%。程序如下所示:

    #
    position=n_shares*x.close[0] 
    mean=np.mean(ret)
    z=norm.ppf(1-confidence_level)
    std=np.std(ret)
    #
    VaR=position*(mean+z*std)
    print("Holding=",position, "VaR=", round(VaR,4), "tomorrow")
    print("VaR/holding=",VaR/position)
    (datetime.date(2016, 2, 8), 2016, 2, 8, 736002.0, 121.65280462310274, 122.598996, 123.11070921267809, 119.84731962624865, 7364000.0, 122.598996)
    ('Holding=', 122598.996, 'VaR=', -3186.5054, 'tomorrow')
    ('VaR/holding=', -0.025991284652254254)
    

    根据之前的结果,我们的持仓为$122,599,下一天的最大损失为$3,187。请记住,置信度为 99%,这意味着在这一年期间,我们应预期约 2.5 次违例(0.01*252)。252 是指一年中的交易日数。以下程序显示了违例的次数:

    VaR=-3186.5054            # from the previous program
    position=122598.996       # from the previous program
    #('Holding=', 122598.996, 'VaR=', -3186.5054, 'tomorrow')
    #('VaR/holding=', -0.025991284652254254)
    #
    z=norm.ppf(1-confidence_level) 
    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    print("first day=",x[0])
    ret = x.aclose[1:]/x.aclose[:-1]-1
    #
    cutOff=VaR/position 
    n=len(ret)
    ret2=ret[ret<=cutOff]
    n2=len(ret2)
    print("n2=",n2)
    ratio=n2*1./(n*1.)
    print("Ratio=", ratio)
    ('first day=', (datetime.date(2016, 2, 8), 2016, 2, 8, 736002.0, 121.65280462310274, 122.598996, 123.11070921267809, 119.84731962624865, 7364000.0, 122.598996))
    ('n2=', 4)
    ('Ratio=', 0.015873015873015872)
    

    再次,我们期望根据模型看到 2.5 次违例。然而,实际违例次数为 4 次。基于 99%的置信度,我们预期回报低于-2.599%的情况应该约为 1%。不幸的是,基于 1 年的数据,这一比例为 1.58%。如果基于 55 年的历史数据,对于这只特定股票,回报低于该比例的频率超过了 2 倍,分别为 3.66%与 1%。这表明基础模型低估了潜在的最大损失。

    预期损失

    在前面的章节中,我们讨论了与 VaR 相关的许多问题,如其定义和如何估算。然而,VaR 的一个主要问题是它依赖于基础证券或投资组合的分布形态。如果正态分布的假设接近成立,那么 VaR 是一个合理的度量。否则,如果我们观察到胖尾现象,我们可能会低估最大损失(风险)。另一个问题是,VaR 触及后分布的形态被忽略了。如果我们有比正态分布描述的更胖的左尾,那么我们的 VaR 会低估真实风险。反之,如果左尾比正态分布更薄,我们的 VaR 则会高估真实风险。预期损失ES)是在 VaR 触及时的预期损失,其定义如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_23.jpg

    这里,ES是预期损失,α是我们的显著性水平,如 1%或 5%。基于正态分布假设,对于我们的 Python 示例,我们有以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_24.jpg

    预期损失可以通过以下方式估算:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_25.jpg

    以下程序展示了如何从正态分布中生成回报,然后估算 VaR 和 ES:

    import scipy as sp
    import scipy.stats as stats
    x = sp.arange(-3,3,0.01)
    ret=stats.norm.pdf(x)
    confidence=0.99
    position=10000
    z=stats.norm.ppf(1-confidence)
    print("z=",z)
    zES=-stats.norm.pdf(z)/(1-confidence)
    print("zES=", zES)
    std=sp.std(ret)
    VaR=position*z*std
    print("VaR=",VaR)
    ES=position*zES*std
    print("ES=",ES)
    

    同样,我们可以推导出基于历史回报估计预期损失(Expected Shortfall,简称 ES)的公式。从某种意义上说,预期损失是基于低于 VaR 阈值的回报的平均损失。假设我们有 n 个回报观测值,预期损失可以定义如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_26.jpg

    这里,ES是预期损失,position是我们的投资组合的价值,m是比给定置信水平指定的截断点更差的回报观测数,Ii是一个虚拟变量,当回报小于Rcutoff时其值为 1,否则为 0,Ri是第i个回报,Rcutoff是由给定置信水平确定的截断回报,n 是所有回报观测的总数,m 是小于截断回报的回报数。例如,如果我们有 1,000 个回报观测,且置信水平为 99%,则截断回报将是按从低到高排序的回报中的第 10 个观测值。预期损失将是这 10 个最差情境的平均损失。

    假设在 2016 年最后一天,我们持有 500 股沃尔玛股票。假设我们关心的是次日最大损失,并且置信水平为 99%。根据历史回报的排名,VaR 和预期损失是多少?以下代码给出了答案:

    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    ret = np.array(x.aclose[1:]/x.aclose[:-1]-1)
    ret2=np.sort(ret) 
    #
    position=n_shares*x.close[0] 
    n=np.size(ret2)
    m=int(n*(1-confidence_level))
    print("m=",m)
    #
    sum=0.0
    for i in np.arange(m):
        sum+=ret2[i]
    ret3=sum/m
    ES=position*ret3
    print("Holding=",position, "Expected Shortfall=", round(ES,4), "tomorrow")
    ('m=', 12)
    ('Holding=', 26503.070499999998, 'Expected Shortfall=', -1105.1574, 'tomorrow')
    

    由于有 11 个回报低于第 12 个回报,预期损失将是这些 12 个回报的平均值,乘以评估日的投资组合市值:

    附录 A – 数据案例 7 – 个别股票和投资组合的 VaR 估算

    本数据集有三个目标:

  • 理解与 VaR 相关的概念和方法

  • 估算个股的 VaR

  • 估算投资组合的 VaR

  • 问题是:在 99%的置信区间下,对于每只股票和一个等权重投资组合,10 天的 VaR 是多少?假设数据期间为 2012 年 2 月 7 日到 2017 年 2 月 7 日,并且你有 100 万美元的投资(公式 1 中的position):

    i 公司名称 股票代码 行业
    1 微软公司 MSFT 应用软件
    2 苹果公司 AAPL 个人计算机
    3 家得宝公司 HD 家装服务
    4 花旗集团公司 C 大型银行
    5 沃尔玛公司 WMT 折扣与杂货店
    6 通用电气公司 GE 技术

    具体步骤如下:

    1. 从 Yahoo! Finance 获取日数据。

    2. 估算日收益。

    3. 应用以下公式估算 VaR:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_27.jpg

    4. 根据排序的历史收益估算 VaR。

    5. 如果可能,使用 VBA、R、SAS 或 Matlab 自动化该过程。

    VaR 最常用的参数为 1%和 5%的概率(99%和 95%的置信水平),以及 1 天和 2 周的时间范围。基于正态性假设,我们得到以下通用公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_28.jpg

    这里,position是我们投资组合的当前市场价值,µperiod是预期的周期收益,z是根据置信水平决定的截断点,σ是波动性。对于正态分布,z=2.33 对应 99%的置信水平,z=1.64 对应 95%的置信水平。当时间范围很短,如 1 天时,我们可以忽略µperiod的影响。因此,我们得到最简单的形式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_29.jpg

    根据正态性假设估算 VaR。

    如果基础证券遵循正态分布,则 VaR 公式如下:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_30.jpg

    对于 99%和 95%的置信水平,*公式(5)*变为以下公式:

    置信水平 公式
    99% https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_40.jpg
    95% https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_41.jpg

    n 天 VaR 的估算取决于如何计算 n 天的收益和标准差。变换基于以下公式,不同频率之间的方差关系:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_31.jpg

    例如,年波动率等于每日波动率乘以 252 的平方根 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_42.jpg。基于日收益率,我们得出以下一般公式,用于 99%或 95%置信水平的 VaR 估算:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_32.jpg

    这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_43.jpg是预期的每日收益率,n 是天数,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_44.jpg是每日波动率,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_45.jpg是 n 天的波动率,confidence 是置信水平,如 99%或 95%,而 p 是头寸。如果我们不知道预期收益率,并且假设预期的均值收益率与实际的均值收益率相同,那么我们有以下公式:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_33.jpg

    对于 99%和 95%的置信水平,我们有以下内容:

    https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_11_34.jpg

    参考文献

    请参考以下文章:

  • Jorion, Philippe, 风险价值(Value at Risk), 第二版, McGraw-Hill, 2001

  • Lopez, Jose A., 2000, 关于回测和压力测试的学术视角——信用风险模型和资本管理的未来,旧金山联邦储备银行演讲www.frbsf.org/economic-research/files/lopezbktesting.pdf

  • Wikiperia, 风险价值(Value at Risk), en.wikipedia.org/wiki/Value_at_risk

  • 练习

    1. VaR 的最简单定义是什么?VaR、方差、标准差和贝塔值之间有什么区别?

    2. 假设我们有一个计划,形成一个两只股票的投资组合。置信水平为 99%,周期数为 10 天。如果第一只股票的 VaR 为 x,而第二只股票的 VaR 为 y,那么投资组合的 VaR 是否为加权个别股票的 VaR,即VaR(投资组合) = wAx + wBy,其中WA是股票A的权重,而wB是股票 B 的权重?请解释。

    3. IBM 的回报是否符合正态分布?它们的偏度和峰度值是否分别为零和三(过度峰度为零)?

    4. 正态分布的偏度和峰度值是多少?通过使用rnorm()生成 n 个随机数来支持你的结论。

    5. 编写一个 Python 函数,估算给定股票代码的均值、标准差、偏度和峰度;例如,moments4("ticker", begdate, enddate)

    6. 假设我们持有 134 股微软股票;今天的总价值是多少?在 95%置信水平下,明天的最大损失是多少?如果我们的持有期是 1 个月而不是 1 天,价值是多少?

    7. 使用月度收益代替日度收益重复 11.4 中的最后一个问题,答案是否与 11.4 中的不同?

    8. 我们的投资组合有 100 股 IBM 股票和 300 股微软股票。在 99%的置信水平下,持有期为 1 天时的 VaR 是多少?

    9. 为了估算戴尔公司 1 个月的 VaR,我们可以将日 VaR 转换为月 VaR,或者直接根据月度数据计算 VaR。两者有区别吗?

    10. 当我们估算 VaR 时,可以使用不同的时间周期,例如过去 1 年或过去 5 年。这会有区别吗?使用几个股票代码进行探索并评论结果。

    11. 评论不同的 VaR 方法,例如基于正态假设、历史收益和修正 VaR 的方法。

    12. 如果一个基金投资了 10%的 IBM,12%的谷歌,其余部分投资于沃尔玛,投资组合的波动率是多少?

    13. 如果 IBM 股票的权重为 10%,戴尔为 12%,沃尔玛为 20%,其余部分为长期 10 年期国债,那么投资组合的波动率是多少?

    14. 基于 11.11,如果投资组合的价值为 1000 万美元,那么在接下来的 6 个月内,99%置信水平下的 VaR 是多少?

    15. 使用 99%置信水平和 10 个交易日作为持有期,基于历史收益法估算 VaR:100 股 IBM,200 股花旗,200 股微软和 400 股沃尔玛。

    16. 基于正态分布假设的 VaR 通常小于基于历史收益的 VaR,这是真的吗?

      提示

      你可以使用滚动窗口对股票进行处理来显示你的结果(答案)。或者,你可以使用多只股票。

    17. 基于偏度的代码,编写一个 Python 函数来计算峰度。将你的函数与scipy.stats.kurtosis()函数进行比较。

    18. 如果我们的持有期不是 1 天,如何根据历史收益估算 VaR?公式是什么?

    19. 如果持有期为 2 周(10 个交易日),如何根据历史收益数据估算 VaR?

    20. 如果我们的持有 IBM、戴尔和沃尔玛的股票分别是 100 股、200 股和 500 股,99%的置信水平和 2 周的持有期下,最大可能损失(VaR)是多少?

    21. 编写一个 Python 程序,使用历史数据生成 VaR。函数的结构将是VaR_historical(ticker, confidence_level, n_days)

    总结

    在本章中,详细讨论了一种重要的风险度量方法——风险价值VaR)。为了估算单只股票或投资组合的 VaR,解释了两种最流行的方法:基于正态性假设的方法和基于历史收益排序的方法。此外,我们还讨论了修改版 VaR 方法,该方法除了考虑收益的前两阶矩,还考虑了第三阶和第四阶矩。在第十二章,蒙特卡罗模拟,我们解释了如何将模拟应用于金融领域,例如模拟股票价格波动和收益,复制 Black-Scholes-Merton 期权模型,以及定价一些复杂期权。

    作者:绝不原创的飞龙

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python 量化金融第二版(四)

    发表回复