估值框架(Valuation Framework)#

复利是有史以来最伟大的数学发现。

—Albert Einstein

本章为开发DX库提供了框架,介绍了此项目所需的最基本概念。它简要回顾了资产定价基本定理(Fundamental Theorem of Asset Pricing),该定理为模拟和估值提供了理论基础。接着讨论了日期处理和风险中性贴现(risk-neutral discounting)的基本概念。本章仅考虑最简单的常数短期利率贴现情况,但更复杂和更现实的模型可以很容易地添加到库中。本章还介绍了市场环境(market environment)的概念——即一系列常数、列表和曲线的集合,后续章节中几乎所有其他类的实例化都需要用到它们。

本章包括以下部分:

"资产定价基本定理" 第558页

本节介绍资产定价基本定理,该定理为待开发的库提供了理论基础。

"风险中性贴现" 第560页

本节开发了一个用于对未来期权及其他衍生品收益进行风险中性贴现的类。

"市场环境" 第565页

本节开发了一个用于管理单个工具及由多个工具组成的投资组合的市场环境的类。

资产定价基本定理(Fundamental Theorem of Asset Pricing)#

资产定价基本定理(Fundamental Theorem of Asset Pricing)是现代金融理论和数学的基石与成功典范之一。1 该定理的核心概念是鞅测度(martingale measure),即一种从贴现风险因子(随机过程)中去除漂移项的概率测度。换句话说,在鞅测度下,所有风险因子都以无风险短期利率漂移——而不是以任何包含相对于无风险短期利率的风险溢价的市场利率漂移。

一个简单示例(A Simple Example)#

考虑一个简单经济,包含今天和明天两个时间点,有一种风险资产("股票")和一种无风险资产("债券")。债券今天价值10美元,明天偿付10美元(零利率)。股票今天价值10美元,明天以60%和40%的概率分别偿付20美元或0美元。债券的无风险收益率为0。股票的预期收益率为 $\frac{0.6 \cdot 20 + 0.4 \cdot 0}{10} - 1 = 0.2$,即20%。这是股票因其风险性而支付的风险溢价。

现在考虑一个行权价为15美元的看涨期权。这样一个或有债权(contingent claim)以60%的概率偿付5美元,否则偿付0美元,其公允价值是多少?一种方法是取期望值,然后将结果贴现回来(此处利率为零)。这种方法得出价值为0.6 × 5 = 3美元,因为当股票价格上涨至20美元时期权偿付5美元,否则为0美元。

然而,还有另一种成功应用于此类期权定价问题的方法:通过交易证券的组合来复制期权的收益。可以很容易地验证,购买0.25股股票可以完美复制期权的收益(在60%的情况下,0.25 × 20 = 5美元)。四分之一股票的成本仅为2.5美元,而非3美元。在真实世界概率测度下取期望会高估期权的价值。

这是为什么?真实世界测度意味着股票有20%的风险溢价,因为股票所涉及的风险(上涨100%或下跌100%)在无法分散或对冲的意义上是"真实的"。另一方面,存在一个可以无风险地复制期权收益的投资组合。这也意味着卖出(出售)此类期权的人可以完全对冲掉任何风险。2 这样一个完全对冲的期权和头寸组合必须产生无风险利率,以避免套利机会(即以正概率从零资金中赚钱的机会)。

能否通过取期望的方法来拯救看涨期权的估值?是的,可以。只需改变概率,使得风险资产(股票)以零无风险短期利率漂移即可。显然,赋予两种情景各50%权重的(鞅)测度可以实现这一点;计算为 $\frac{0.5 \cdot 20 + 0.5 \cdot 0}{10} - 1 = 0$。现在,在新的鞅测度下对期权的收益取期望,得到正确的(无套利)公允价值:$0.5 \cdot 5 + 0.5 \cdot 0 = 2.5$ 美元。

一般性结论(The General Results)#

这种方法的美妙之处在于,它可以推广到即使是最复杂的经济环境,例如连续时间建模(即考虑连续的时间点)、大量风险资产、复杂衍生品收益等。

因此,考虑一个离散时间的一般市场模型:3

离散时间的一般市场模型 ℳ 是以下内容的集合:

• 一个有限状态空间 Ω

• 一个滤链 ኀ

• 定义在 $\wp (\varOmega)$ 上的严格正概率测度 P

• 一个终端日期 $T \in \mathbb{N}, T < \infty$

• 一个由K+1个严格正证券价格过程组成的集合 ${\mathbb S} \equiv \left\{\left(S_{t}^{k}\right)_{t \in \{0, ..., T\}} : k \in \{0, ..., K\}\right\}$

综合起来有 $\mathcal{M} = \left\{(\Omega, \wp(\Omega), \mathbb{F}, P), T, \mathbb{S}\right\}$

基于这样的市场模型,资产定价基本定理可以表述如下:4

考虑一般市场模型 ℳ。根据资产定价基本定理,以下三个陈述是等价的:

• 市场模型 ℳ 中不存在套利机会。

• P-等价鞅测度的集合 ℚ 是非空的。

• 一致线性价格系统的集合 ℙ 是非空的。

当涉及到或有债权(即期权、衍生品、期货、远期、互换等)的估值和定价时,该定理的重要性通过以下推论得以体现:

如果市场模型 ℳ 是无套利的,那么对于任何可达的(即可复制的)或有债权(期权、衍生品等)$V_{T}$,存在一个唯一价格 $V_{0}$。它满足 $\forall Q \in \mathbb{Q}: V_{0} = \mathbf{E}_{0}^{Q}\left(e^{-rT} V_{T}\right)$,其中 $e^{-rT}$ 是常数短期利率 r 下的相关风险中性贴现因子。

这个结果说明了该定理的重要性,并表明我们之前简单推理确实可以推广到一般市场模型。

由于鞅测度的作用,这种估值方法通常也被称为鞅方法(martingale approach),或者——因为在鞅测度下所有风险资产都以无风险短期利率漂移——称为风险中性估值方法(risk-neutral valuation approach)。对我们的目的而言,后一个术语可能更好,因为在数值应用中,我们"简单地"让风险因子(随机过程)以风险中性短期利率漂移。在我们的应用中,不需要直接处理概率测度——然而,从理论上讲,正是它们证明了所应用的核心理论结果和所实现的技术方法的合理性。

最后,考虑一般市场模型中的市场完备性:

市场模型 ℳ 是完备的(complete),如果它是无套利的,并且每个或有债权(期权、衍生品等)都是可达的(即可复制的)。

假设市场模型 ℳ 是无套利的。市场模型是完备的当且仅当 ℳ 是单点集;即存在唯一的 P-等价鞅测度。

这基本上完成了对后续内容的理论背景的讨论。关于这些概念、定义和结果的详细阐述,请参阅 Hilpisch (2015) 中的第4章。

风险中性贴现(Risk-Neutral Discounting)#

显然,风险中性贴现是风险中性估值方法的核心。因此,本节开发了一个用于风险中性贴现的 Python 类。然而,首先仔细研究相关日期的建模和处理是值得的。

日期的建模与处理(Modeling and Handling Dates)#

贴现的必要前提是日期的建模(另见附录A)。出于估值目的,通常将今天与一般市场模型终端日期 T 之间的时间间隔划分为离散的时间区间。这些时间区间可以是同质的(即等长),也可以是异质的(即变长)。一个估值库应该能够处理更一般的异质时间区间情况,因为简单的情况自然包含在内。因此,代码使用日期列表,假设最小相关时间间隔为一天。这意味着日内事件被认为是无关紧要的(否则还需要对时间进行建模)。5

要编译相关日期列表,基本上可以采用两种方法之一:构建具体日期的列表(例如 Python 中的 datetime 对象)或年份分数列表(以十进制数表示,正如理论著作中常见的那样)。

首先是一些导入:

In [1]: import numpy as np
    import pandas as pd
    import datetime as dt

In [2]: from pylab import mpl, plt
    plt.style.use('seaborn')
    mpl.rcParams['font.family'] = 'serif'
    %matplotlib inline

In [3]: import sys
    sys.path.append('../dx')

例如,以下两种日期和分数的定义是(大致)等价的:

In [4]: dates = [dt.datetime(2020, 1, 1), dt.datetime(2020, 7, 1), dt.datetime(2021, 1, 1)]
In [5]: (dates[1] - dates[0]).days / 365.
Out[5]: 0.4986301369863014
In [6]: (dates[2] - dates[1]).days / 365.
Out[6]: 0.5041095890410959
In [7]: fractions = [0.0, 0.5, 1.0]

它们只是大致等价,因为年份分数很少落在某一天的起点(凌晨0点)。只需考虑将一年除以50的结果就明白了。

有时需要从日期列表中获取年份分数。函数 get_year_deltas() 完成此任务:

#
### DX 包
#
### 框架 -- 辅助函数
#
**get_year_deltas.py**
#


#
import numpy as np

def get_year_deltas(date_list, day_count=365.):
    ''' 返回以年分数表示的时间差浮点向量。
    初始值标准化为零。

    参数(Parameters)
    ========
    date_list: list or array
        datetime 对象集合
    day_count: float
        一年的天数
        (用于考虑不同惯例)

    结果(Results)
    ========
    delta_list: array
        年分数
    '''
    start = date_list[0]
    delta_list = [(date - start).days / day_count
                  for date in date_list]
    return np.array(delta_list)

该函数可以如下应用:

In [8]: from get_year_deltas import get_year_deltas

In [9]: get_year_deltas(dates)
Out[9]: array([0., 0.49863014, 1.00273973])

在建模短期利率时,这种转换的好处就显而易见了。

常数短期利率(Constant Short Rate)#

接下来的阐述关注最简单的短期利率贴现情况,即短期利率随时间保持常数的情况。许多期权定价模型,如 Black-Scholes-Merton (1973)、Merton (1976) 或 Cox-Ross-Rubinstein (1979) 等模型,都做了这一假设。6 假设连续贴现,如同期权定价应用中常见的那样。在这种情况下,给定未来日期 t 和常数短期利率 r,从今天起的一般贴现因子为 $D_{0}(t) = e^{-rt}$。当然,对于经济体的终结日期,特例 $D_{0}(T) = e^{-rT}$ 成立。注意,这里 t 和 T 都是年份分数。

贴现因子也可以被解释为一份今天价值为单位金额的零息债券(zero-coupon bond, ZCB),分别在 t 和 T 到期。7 给定两个日期 $t \ge s \ge 0$,从 t 贴现到 s 的相关贴现因子由方程 $D_{s}(t) = D_{0}(t) / D_{0}(s) = e^{-rt} / e^{-rs} = e^{-rt} \cdot e^{rs} = e^{-r(t-s)}$ 给出。

以下代码以类的形式将这些考虑转化为 Python 代码:8

#
### DX 库
#
### 框架 -- 常数短期利率类
#
**constant_short_rate.py**
#


#
from get_year_deltas import *

class constant_short_rate(object):
    ''' 常数短期利率贴现类

    属性(Attributes)
    ========
    name: string
        对象名称
    short_rate: float (positive)
        贴现常数利率

    方法(Methods)
    ======
    get_discount_factors:
        给定日期时间对象列表/数组或年份分数,获取贴现因子
    '''

    def __init__(self, name, short_rate):
        self.name = name
        self.short_rate = short_rate
        if short_rate < 0:
            raise ValueError('短期利率为负。')
            # 鉴于近期的市场现实,这一点存在争议

    def get_discount_factors(self, date_list, dtobjects=True):
        if dtobjects is True:
            dlist = get_year_deltas(date_list)
        else:
            dlist = np.array(date_list)
        dflist = np.exp(self.short_rate * np.sort(-dlist))
        return np.array((date_list, dflist)).T

类 dx.constant_short_rate 的应用最好通过一个简单具体的例子来说明。主要结果是一个二维 ndarray 对象,包含日期时间对象和相关贴现因子的配对。该类一般以及对象 csr 特别地也能处理年份分数:

In [10]: from constant_short_rate import constant_short_rate

In [11]: csr = constant_short_rate('csr', 0.05)

In [12]: csr.get_discount_factors(dates)
Out[12]: array([[datetime.datetime(2020, 1, 1, 0, 0), 0.9510991280247174],
       [datetime.datetime(2020, 7, 1, 0, 0), 0.9753767163648953],
       [datetime.datetime(2021, 1, 1, 0, 0), 1.0]], dtype=object)

In [13]: deltas = get_year_deltas(dates)
    deltas
Out[13]: array([0., 0.49863014, 1.00273973])

In [14]: csr.get_discount_factors(deltas, dtobjects=False)
Out[14]: array([[0., 0.95109913],
       [0.49863014, 0.97537672],
       [1.00273973, 1.]])

该类将处理其他类所需的所有贴现操作。

市场环境(Market Environments)#

市场环境(market environment)"仅仅"是其他数据和 Python 对象集合的名称。然而,使用这种抽象非常方便,因为它简化了许多操作,并允许对重复出现的方面进行一致的建模。9 一个市场环境主要由三个字典组成,用于存储以下类型的数据和 Python 对象:

常数(Constants)#

可以是模型参数或期权到期日等。

列表(Lists)#

通常是对象的集合,例如一组(风险)证券模型对象。

曲线(Curves)#

用于贴现的对象,例如 dx.constant_short_rate 类的实例。

以下是 dx.market_environment 类的代码。关于 dict 对象处理的详细信息,请参见第3章:

#
### DX 包
#
### 框架 -- 市场环境类
#
**market_environment.py**
#


#
class market_environment(object):
    ''' 对估值相关的市场环境进行建模的类

    属性(Attributes)
    ========
    name: string
        市场环境的名称
    pricing_date: datetime object
        市场环境的定价日期

    方法(Methods)
    ========
    add_constant:
        添加常数(例如模型参数)
    get_constant:
        获取常数
    add_list:
        添加列表(例如标的资产)
    get_list:
        获取列表
    add_curve:
        添加市场曲线(例如收益率曲线)
    get_curve:
        获取市场曲线
    add_environment:
        添加并覆盖整个市场环境的常数、列表和曲线
    '''

    def __init__(self, name, pricing_date):
        self.name = name
        self.pricing_date = pricing_date
        self.constants = {}
        self.lists = {}
        self.curves = {}

    def add_constant(self, key, constant):
        self.constants[key] = constant

    def get_constant(self, key):
        return self.constants[key]

    def add_list(self, key, list_object):
        self.lists[key] = list_object

    def get_list(self, key):
        return self.lists[key]

    def add_curve(self, key, curve):
        self.curves[key] = curve

    def get_curve(self, key):
        return self.curves[key]

    def add_environment(self, env):
        # 覆盖已存在的值
        self.constants.update(env.constants)
        self.lists.update(env.lists)
        self.curves.update(env.curves)

尽管 dx.market_environment 类没有什么特别之处,但一个简单的例子可以说明使用该类的实例是多么方便:

In [15]: from market_environment import market_environment

In [16]: me = market_environment('me_gbm', dt.datetime(2020, 1, 1))

In [17]: me.add_constant('initial_value', 36.)

In [18]: me.add_constant('volatility', 0.2)

In [19]: me.add_constant('final_date', dt.datetime(2020, 12, 31))

In [20]: me.add_constant('currency', 'EUR')

In [21]: me.add_constant('frequency', 'M')

In [22]: me.add_constant('paths', 10000)

In [23]: me.add_curve('discount_curve', csr)

In [24]: me.get_constant('volatility')
Out[24]: 0.2

In [25]: me.get_curve('discount_curve').short_rate
Out[25]: 0.05

这说明了这个相当通用的"存储"类的基本用法。在实际应用中,首先收集市场数据及其他数据和 Python 对象,然后实例化一个 dx.market_environment 对象并填充相关数据和对象。然后,将其一次性交付给需要该 dx.market_environment 对象中存储的数据和对象的其他类。

这种面向对象建模方法的一个主要优点是,例如,dx.constant_short_rate 类的实例可以存在于多个环境中(参见第6章中的聚合主题)。一旦该实例被更新——例如设置了新的常数短期利率——所有包含该贴现类实例的 dx.market_environment 类实例将自动更新。

灵活性(Flexibility)#

本节介绍的市场环境类是一种灵活的手段,用于建模和存储与期权、衍生品及其组成的投资组合定价相关的任何量和输入数据。然而,这种灵活性也带来了操作风险,因为在实例化过程中很容易将无意义的数据、对象等传递给该类,而这些可能在实例化过程中被捕获也可能不被捕获。在生产环境中,需要添加一些检查,至少捕获明显错误的情况。

本章小结(Conclusion)#

本章为构建一个通过蒙特卡洛模拟对期权和其他衍生品进行估值的 Python 包提供了基本框架。本章介绍了资产定价基本定理,并通过一个相当简单的数值例子加以说明。为此,还给出了离散时间一般市场模型的重要结果。

本章还开发了一个用于风险中性贴现的 Python 类,以便在数值上利用资产定价基本定理的数学机制。基于 Python datetime 对象或表示年份分数的浮点对象的列表,dx.constant_short_rate 类的实例提供适当的贴现因子(单位零息债券的现值)。

本章最后介绍了相当通用的 dx.market_environment 类,该类允许收集建模、模拟、估值及其他用途的相关数据和 Python 对象。

为了简化未来的导入,使用了一个名为 dx_frame.py 的包装模块:

#
### DX 分析包
#
### 框架函数与类
#
**dx_frame.py**
#


#
import datetime as dt

from get_year_deltas import get_year_deltas
from constant_short_rate import constant_short_rate
from market_environment import market_environment

一条导入语句即可一次性使用所有框架组件:

import dx_frame

考虑到 Python 包的模块组织,还可以将所有相关 Python 模块存储在一个(子)文件夹中,并在该文件夹中放置一个特殊的 __init__.py 文件来执行所有导入。例如,当将所有模块存储在一个名为 dx 的文件夹中时,下面展示的文件可以完成此工作。但请注意此特定文件的命名约定:

#
### DX 包
### 打包文件
**__init__.py**
#
import datetime as dt

from get_year_deltas import get_year_deltas
from constant_short_rate import constant_short_rate
from market_environment import market_environment

在这种情况下,你可以直接使用文件夹名一次性完成所有导入:

from dx import *

或者,通过替代方式:

import dx

延伸阅读(Further Resources)#

本章所涵盖主题的参考书籍包括:

• Bittman, James (2009). Trading Options as a Professional. New York: McGraw Hill.

• Delbaen, Freddy, and Walter Schachermayer (2004). The Mathematics of Arbitrage. Berlin, Heidelberg: Springer-Verlag.

• Fletcher, Shayne, and Christopher Gardner (2009). Financial Modelling in Python. Chichester, England: Wiley Finance.

• Hilpisch, Yves (2015). Derivatives Analytics with Python. Chichester, England: Wiley Finance.

• Williams, David (1991). Probability with Martingales. Cambridge, England: Cambridge University Press.

关于本章引用的模型原创研究论文,请参阅后续章节的"延伸阅读"部分。