精通-R-量化金融-一-
精通 R 量化金融(一)
原文:
zh.annas-archive.org/md5/6ec26de99354e5678e325828967cda76译者:飞龙
前言
精通 R 语言量化金融是我们之前出版的《量化金融 R 语言入门》的续集,旨在帮助那些希望在量化金融领域更高阶使用 R 语言构建模型的人学习。本书将涵盖新的实证金融学主题(第 1-4 章)、金融工程学(第 5-7 章)、交易策略优化(第 8-10 章)以及银行管理(第 11-13 章)。
本书内容概述
第一章, 时间序列分析(Tamás Vadász)讨论了一些重要概念,如协整(结构性)、向量自回归模型、脉冲响应函数、使用不对称 GARCH 模型的波动率建模以及新闻冲击曲线。
第二章, 因子模型(Barbara Dömötör, Kata Váradi, Ferenc Illés)介绍了如何构建和实现多因子模型。在主成分分析的帮助下,识别出了五个能够解释资产回报的独立因子。举例来说,Fama 和 French 模型也在一个真实市场数据集上进行了重现。
第三章, 交易量预测(Balázs Árpád Szűcs, Ferenc Illés)介绍了一种基于 DJIA 指数数据的日内交易量预测模型及其在 R 中的实现。该模型使用成交额而非交易量,分离了季节性成分(U 形)与动态成分,并分别预测这两者。
第四章, 大数据 – 高级分析(Júlia Molnár, Ferenc Illés)应用 R 语言从开放源数据中获取数据,并对大数据集进行各种分析。举例来说,K-means 聚类和线性回归模型被应用于大数据分析。
第五章, 外汇衍生品(Péter Medvegyev, Ágnes Vidovics-Dancs, Ferenc Illés)对衍生品定价中的 Black-Scholes 模型进行了推广。Margrabe 公式作为 Black-Scholes 模型的扩展,被编程用于定价股票期权、货币期权、交换期权和 Quanto 期权。
第六章, 利率衍生品与模型(Péter Medvegyev, Ágnes Vidovics-Dancs, Ferenc Illés)提供了利率模型和利率衍生品的概述。使用 Black 模型来定价上限和上限期权;此外,还介绍了 Vasicek 模型和 CIR 模型等利率模型。
第七章, 另类期权(Balázs Márkus, Ferenc Illés)介绍了另类期权,解释了它们与普通期权的联系,并呈现了如何为任何衍生品定价函数估算其希腊字母。特别的另类期权——双重无触及(DNT)二元期权,进行了更为详细的分析。
第八章,最优对冲(Barbara Dömötör, Kata Váradi, Ferenc Illés)分析了衍生品对冲中的一些实际问题,这些问题源自于投资组合的离散时间重组和交易成本。为了找到最优的对冲策略,使用了不同的数值优化算法。
第九章,基本面分析(Péter Juhász, Ferenc Illés)研究了如何在基本面基础上构建投资策略。为了挑选收益最好的股票,一方面,根据公司过去的表现创建公司集群,另一方面,利用决策树将表现优秀的公司分离出来。基于这些,定义并回测了股票选择规则。
第十章,技术分析、神经网络与对数最优组合(Ágnes Tuza, Milán Badics, Edina Berlinger, Ferenc Illés)概述了技术分析以及一些相关策略,如神经网络和对数最优组合。在动态设置中,研究了预测单一资产(比特币)价格、优化交易时机和投资组合分配(纽约证券交易所股票)的问题。
第十一章,资产与负债管理(Dániel Havran, István Margitai)展示了 R 如何支持银行的资产与负债管理过程。重点在于数据生成、利率风险的衡量与报告、流动性风险管理,以及非到期存款行为的建模。
第十二章,资本充足性(Gergely Gabler, Ferenc Illés)总结了巴塞尔协议的原则,并通过历史法、delta-normal 法和蒙特卡洛模拟法计算银行的风险价值,以确定其资本充足性。还涵盖了信用风险和操作风险的具体问题。
第十三章,系统性风险(Ádám Banai, Ferenc Illés)展示了两种基于网络理论的方法,有助于识别系统重要金融机构:核心-边缘模型和传染模型。
Gergely Daróczi 还通过审查程序代码对大部分章节做出了贡献。
本书所需内容
本书中提供的所有代码示例都应在 R 控制台中运行,首先需要在您的计算机上安装 R。您可以免费下载该软件,并在 r-project.org 查找适用于所有主要操作系统的安装说明。尽管本书不会涉及如何在集成开发环境中使用 R 等高级话题,但除了其他编辑器外,Emacs、Eclipse、vi 或 Notepad++ 等编辑器都有很棒的插件,我们也强烈推荐您尝试 RStudio,它是一个专为 R 设计的免费开源 IDE。
除了一个可用的 R 安装外,我们还将使用一些用户贡献的 R 包,这些包可以通过综合档案网络轻松安装。要安装一个包,请在 R 控制台中使用 install.packages 命令,如下所示:
> install.packages('Quantmod')
安装后,该包应首先加载到当前的 R 会话中,然后才能使用:
> library (Quantmod)
您可以在 R 的主页上找到免费的入门文章和手册。
本书适合谁阅读
本书面向已熟悉基本金融概念并具备一定编程技能的读者。然而,即使您了解量化金融的基础,或已经具备 R 编程经验,本书仍会为您提供新的启发。如果您已经是某个领域的专家,本书将帮助您迅速上手其他领域。然而,如果您希望完美跟上各章的节奏,您需要具备量化金融的中级水平,并且需要对 R 有一定的知识。这两项技能可以通过续集第一卷《量化金融中的 R 入门》获得。
约定
本书中,您将看到一些文本样式,用于区分不同类型的信息。以下是这些样式的示例及其含义说明。
任何命令行输入或输出均如下所示:
#generate the two time series of length 1000
set.seed(20140623) #fix the random seed
N <- 1000 #define length of simulation
x <- cumsum(rnorm(N)) #simulate a normal random walk
gamma <- 0.7 #set an initial parameter value
y <- gamma * x + rnorm(N) #simulate the cointegrating series
plot(x, type='l') #plot the two series
lines(y,col="red")
新术语 和 重要词汇 以粗体显示。您在屏幕上看到的词汇,例如在菜单或对话框中,通常会以这种方式出现在文本中:“另一个有用的可视化练习是查看对数尺度上的密度。”
注意
警告或重要说明以如下框形式出现。
提示
技巧和窍门以如下方式呈现。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对本书的看法——您喜欢或不喜欢的部分。读者反馈对我们非常重要,它帮助我们开发您真正能从中受益的书籍。
要向我们发送一般反馈,只需通过电子邮件 <feedback@packtpub.com> 与我们联系,并在邮件主题中提及书名。
如果您在某个主题上有专长并且有兴趣撰写或贡献书籍内容,请参见我们的作者指南:www.packtpub.com/authors。
客户支持
现在,您已是 Packt 图书的骄傲拥有者,我们为您准备了一些内容,帮助您最大限度地从购买中获益。
下载示例代码
您可以从您的账户下载所有购买的 Packt 出版书籍的示例代码文件,网址为 www.packtpub.com。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support,并注册以便直接通过电子邮件获取文件。
勘误
虽然我们已经尽力确保内容的准确性,但错误仍然会发生。如果您在我们的一本书中发现错误——可能是文本或代码中的错误——我们将非常感激您能向我们报告。这样,您可以帮助其他读者避免困扰,并帮助我们改进该书的后续版本。如果您发现任何勘误,请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入勘误的详细信息。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该书的勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索框中输入书名。相关信息将显示在勘误部分。
盗版
互联网版权材料的盗版问题在所有媒体中持续存在。在 Packt,我们非常重视版权和许可的保护。如果您在互联网上发现任何我们作品的非法复制品,请立即提供相关的网址或网站名称,以便我们采取措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。
感谢您帮助我们保护作者的权益,并支持我们为您提供有价值的内容。
问题
如果您在本书的任何部分遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章:时间序列分析
在本章中,我们将探讨一些高级时间序列方法,并介绍如何使用 R 语言实现它们。作为一个学科,时间序列分析广泛到足以填满数百本书(本章参考文献的理论和 R 编程的最重要文献将列在阅读清单的末尾);因此,本章的内容必然是高度选择性的,我们将重点介绍在实证金融和量化交易中不可避免地重要的主题。然而,开篇应当强调的是,本章仅为进一步学习时间序列分析奠定基础。
我们之前的书籍《量化金融 R 语言入门》,Packt 出版,讨论了一些时间序列分析的基本主题,如线性单变量时间序列建模、自回归综合滑动平均(ARIMA)以及波动性建模广义自回归条件异方差(GARCH)。如果你从未使用 R 语言进行时间序列分析,你可能想要先阅读该书的第一章,时间序列分析。
当前版本在所有这些主题上都有更深入的探讨,你将熟悉一些重要的概念,如协整、向量自回归模型、脉冲响应函数、使用不对称 GARCH 模型进行波动性建模,包括指数 GARCH 和阈值 GARCH 模型,以及新闻影响曲线。我们首先介绍相关理论,然后提供关于多变量时间序列建模的一些实用见解,并描述若干有用的 R 包和功能。此外,通过简单且具有说明性的例子,我们一步步介绍如何使用 R 编程语言进行实证分析。
多变量时间序列分析
关于金融资产价格波动、技术分析和量化交易的基本问题通常是在单变量的背景下进行讨论的。我们能否预测一只证券的价格是上涨还是下跌?这只证券是处于上涨趋势还是下跌趋势?我们应该买入还是卖出?这些都是重要的考虑因素;然而,投资者通常面临更复杂的情况,很少将市场视为仅仅是一个独立工具和决策问题的池塘。
通过单独观察这些工具,它们可能看起来是无自相关的,且均值不可预测,这正如有效市场假说所指出的那样,然而,它们之间的相关性显然是存在的。这种相关性可以通过交易活动进行利用,无论是为了投机还是对冲目的。这些考虑因素为在量化金融中使用多变量时间序列技术提供了依据。在本章中,我们将讨论两个在金融中有着广泛应用的计量经济学概念。它们分别是协整和向量自回归模型。
协整
从现在开始,我们将考虑一个时间序列向量
,它由元素
组成,每个元素代表一个时间序列,例如不同金融产品的价格演变。让我们从协整数据系列的正式定义开始。
如果每个时间序列单独是整合的,且存在该系列的线性组合是整合的,则称时间序列的
向量
是协整的,具体来说,在大多数应用中,这些序列是一级整合的(意味着它们是非平稳的单位根过程或随机游走),而且该线性组合的整合阶数为
,通常是零阶,这意味着它是一个平稳过程。
直观地讲,这一定义意味着经济中存在一些潜在的力量,这些力量在长期内将n个时间序列维系在一起,即使它们看起来都是各自的随机游走。协整时间序列的一个简单示例是以下一对向量,取自Hamilton (1994),我们将使用它来研究协整,同时也熟悉一些 R 中的基本模拟技术:


中的单位根将通过标准统计检验正式展示。可以使用 R 中的tseries包或urca包来进行单位根检验;这里我们使用第二个包。以下 R 代码模拟了两个长度为1000的序列:
#generate the two time series of length 1000
set.seed(20140623) #fix the random seed
N <- 1000 #define length of simulation
x <- cumsum(rnorm(N)) #simulate a normal random walk
gamma <- 0.7 #set an initial parameter value
y <- gamma * x + rnorm(N) #simulate the cointegrating series
plot(x, type='l') #plot the two series
lines(y,col="red")
提示
下载示例代码
你可以从www.packtpub.com的账户中下载所有你购买的 Packt Publishing 书籍的示例代码文件。如果你是从其他地方购买的此书,你可以访问www.packtpub.com/support并注册,文件将直接通过电子邮件发送给你。
上述代码的输出结果如下:

通过目视检查,两个序列似乎都是各自的随机游走。可以使用urca包进行 Augmented Dickey Fuller 检验来测试平稳性;然而,R 中也有许多其他可用的检验方法。零假设假定过程存在单位根(输出省略);如果测试统计量小于临界值,我们将拒绝零假设:
#statistical tests
install.packages('urca');library('urca')
#ADF test for the simulated individual time series
summary(ur.df(x,type="none"))
summary(ur.df(y,type="none"))
对于两个模拟的序列,测试统计量在通常的显著性水平下(1%、5%和 10%)大于临界值;因此,我们不能拒绝零假设,并得出结论认为两个序列都是单位根过程。
现在,取以下两条序列的线性组合并绘制结果序列:

z = y - gamma*x #take a linear combination of the series
plot(z,type='l')
前述代码的输出结果如下:

显然是一个白噪声过程;通过 ADF 检验结果确认单位根被拒绝:
summary(ur.df(z,type="none"))
在实际应用中,显然我们不知道
的值;这一点必须基于原始数据进行估算,方法是对一条序列进行线性回归,回归的对象是另一条序列。这就是著名的 Engle-Granger 协整检验方法。以下两个步骤就是 Engle-Granger 协整检验方法:
-
对
进行线性回归,回归对象是
(简单的 OLS 估计)。 -
测试残差是否存在单位根。
提示
我们应该在这里注意,对于n条序列,可能的独立协整向量的数量是
;因此,对于
,协整关系可能不是唯一的。我们将在后续章节简要讨论
。
可以使用lm函数拟合简单线性回归。残差可以从结果对象中获取,示例如下。ADF 检验按常规方式进行,并确认在所有显著性水平下拒绝原假设。然而,后续章节将讨论一些警告事项:
#Estimate the cointegrating relationship
coin <- lm(y ~ x -1) #regression without intercept
coin$resid #obtain the residuals
summary(ur.df(coin$resid)) #ADF test of residuals
现在,考虑我们如何将这个理论转化为成功的交易策略。此时,我们应该引入统计套利或配对交易的概念,在最简单且早期的形式中,正是利用了这种协整关系。这些方法的主要目的是基于两条时间序列之间的价差建立交易策略;如果序列是协整的,我们预计它们的平稳线性组合会回归到 0。我们可以通过卖出相对昂贵的序列并买入较便宜的序列来获利,然后坐等回归。
提示
统计套利一词通常用于许多复杂的统计学和计量经济学技术,旨在利用资产在统计意义上的相对错误定价,也就是说,不是与理论均衡模型进行比较。
这一思想背后的经济直觉是什么?形成协整关系的时间序列线性组合由潜在的经济力量决定,这些经济力量在我们的统计模型中没有明确识别,有时被称为相关变量之间的长期关系。例如,同行业的相似公司预期会有类似的增长,金融产品的现货价格和远期价格受无套利原则的约束,某些相互关联的国家的外汇汇率预期会共同波动,或者短期利率和长期利率通常会相互接近。与这种统计上或理论上预期的共同行为的偏差为各种量化交易策略提供了机会,交易者可以通过这些策略对未来的修正进行投机。
协整的概念将在后续章节中进一步讨论,但为了讨论这一点,我们需要引入向量自回归模型。
向量自回归模型
向量自回归模型(VAR)可以被视为单变量自回归(AR)模型的显而易见的多变量扩展。它们在应用计量经济学中的流行可追溯到Sims (1980)的开创性论文。VAR 模型是最重要的多变量时间序列模型,在计量经济学和金融学中有着广泛的应用。R 包 vars 为 R 用户提供了一个优秀的框架。关于这个包的详细评审,请参见 Pfaff(2013)。关于计量经济学理论,请参考Hamilton (1994)、Lütkepohl (2007)、Tsay (2010)或Martin et al. (2013)。在本书中,我们仅提供该主题的简明直观总结。
在 VAR 模型中,我们的起点是长度为
的时间序列向量
。VAR 模型将每个变量的演变指定为所有其他变量滞后值的线性函数;也就是说,阶数为p的 VAR 模型如下所示:

这里,
是所有
的系数矩阵,而
是一个具有正定协方差矩阵的向量白噪声过程。向量白噪声的术语假定没有自相关,但允许组分之间有共同的相关性;也就是说,
具有非对角协方差矩阵。
矩阵符号清晰地展示了 VAR 模型的一个特点:所有变量仅依赖于自身和其他变量的过去值,这意味着同时依赖性没有被显式建模。这个特点使我们能够通过普通最小二乘法逐方程地估计模型。这类模型被称为简化形式的 VAR 模型,与下一节讨论的结构形式模型相对。
显然,假设没有同时效应将是过于简化的,所得的脉冲响应关系,即冲击作用于特定变量后过程的变化,将具有误导性且不太有用。这就促使了结构化 VAR(SVAR)模型的引入,该模型显式地对变量之间的同时效应进行建模:

这里,
和
;因此,可以通过将简化形式与适当的参数矩阵
相乘来获得结构形式,这反映了变量之间的同时结构关系。
提示
在符号中,和通常一样,我们遵循 vars 包的技术文档,该文档与Lütkepohl (2007)的文档非常相似。
在简化形式的模型中,并未建模同时依赖性;因此,这些依赖性出现在误差项的相关结构中,即
的协方差矩阵,记作
。在 SVAR 模型中,同时依赖性被显式建模(通过左侧的A矩阵),且干扰项被定义为不相关的,因此
的协方差矩阵是对角矩阵。在这里,干扰项通常被称为结构性冲击。
SVAR 建模既有趣又充满挑战的原因在于所谓的识别问题;SVAR 模型未被识别,即矩阵A中的参数在没有额外限制的情况下无法估计。
提示
我们应如何理解一个模型未被识别?这基本上意味着存在不同的(无限多个)参数矩阵,这些矩阵导致相同的样本分布;因此,无法基于样本识别参数的唯一值。
给定一个简化形式的模型,总是可以推导出一个合适的参数矩阵,使得残差正交;协方差矩阵
是半正定的,这使得我们可以应用 LDL 分解(或替代地,Cholesky 分解)。这表明,总是存在一个
下三角矩阵和一个
对角矩阵,使得
。通过选择
,结构模型的协方差矩阵变为
,从而得到
。现在,我们可以得出结论,! Vector autoregressive models 是对角矩阵,正如我们预期的那样。请注意,通过这种方法,我们实质上是在方程中施加了一个任意的递归结构。这是 irf() 函数默认遵循的方法。
文献中有多种方法可以识别 SVAR 模型的参数,包括短期或长期的参数限制,或者对脉冲响应的符号限制(例如,参见 Fry-Pagan (2011))。其中许多方法在 R 中尚无原生支持。在这里,我们仅介绍一套标准的技术,用于施加短期参数限制,分别称为 A 模型、B 模型和 AB 模型,每个模型都在 vars 包中原生支持:
-
在 A 模型的情况下,
,对矩阵 A 施加限制,使得
是一个对角协方差矩阵。为了使模型“恰好识别”,我们需要额外的
限制。这让人联想到施加一个三角矩阵(但并不要求该特定结构)。 -
另一种方法是通过施加矩阵 B 的结构(即 B 模型),根据限制后的模型残差来识别结构性创新,即直接在相关结构上施加限制,在这种情况下,! Vector autoregressive models 和
。 -
AB 模型对 A 和 B 都施加限制,限制模型与结构模型之间的连接由
确定。
脉冲响应分析通常是建立 VAR 模型的主要目标之一。本质上,脉冲响应函数显示了一个变量如何对系统中任何其他变量的冲击(脉冲)做出反应(响应)。如果系统包含
变量,
脉冲响应函数可以被确定。脉冲响应可以通过 VAR 过程的向量移动平均表示(VMA)从数学上推导出来,类似于单变量情形(见Lütkepohl (2007)的详细说明)。
VAR 实现示例
作为一个说明性示例,我们从以下组件构建了一个三组件 VAR 模型:
-
股票回报:指定从 2004 年 1 月 1 日到 2014 年 3 月 3 日的微软价格指数
-
股票指数:指定从 2004 年 1 月 1 日到 2014 年 3 月 3 日的 S&P500 指数
-
从 2004 年 1 月 1 日到 2014 年 3 月 3 日的美国国债利率
我们的主要目的是通过使用额外的变量来预测股市指数,并识别脉冲响应。在这里,我们假设存在一个隐藏的长期关系,连接给定股票、整个股市和债券市场。选择这个示例主要是为了展示 R 编程环境中的几种数据处理可能性,并通过一个非常简单的示例阐明一个复杂概念,而非其经济意义。
我们使用了vars和quantmod包。如果你还没有安装并加载这些包,请不要忘记安装并加载:
install.packages('vars');library('vars')
install.packages('quantmod');library('quantmod')
Quantmod包提供了丰富的工具,能够直接从在线来源获取金融数据,这也是我们在本书中频繁使用的工具。我们使用getSymbols()函数:
getSymbols('MSFT', from='2004-01-02', to='2014-03-31')
getSymbols('SNP', from='2004-01-02', to='2014-03-31')
getSymbols('DTB3', src='FRED')
默认情况下,yahoofinance被用作股票和指数价格系列的数据源(src='yahoo'参数设置,示例中省略)。该例程下载开盘价、最高价、最低价、收盘价、交易量以及调整后的价格。下载的数据存储在一个xts数据类中,默认情况下根据股票代码(如 MSFT 和 SNP)自动命名。通过调用通用的plot函数可以绘制收盘价,但quantmod的chartSeries函数提供了更好的图形化展示。
下载数据的组件可以通过以下快捷方式访问:
Cl(MSFT) #closing prices
Op(MSFT) #open prices
Hi(MSFT) #daily highest price
Lo(MSFT) #daily lowest price
ClCl(MSFT) #close-to-close daily return
Ad(MSFT) #daily adjusted closing price
因此,例如,使用这些快捷方式,可以绘制如下的日收盘对收盘收益:
chartSeries(ClCl(MSFT)) #a plotting example with shortcuts
上述命令的截图如下:

利率数据是从FRED(联邦储备经济数据)数据源下载的。目前版本的接口不允许日期子集筛选;然而,下载的数据被存储在xts数据类中,方便按需进行子集提取,以获得我们感兴趣的时间段:
DTB3.sub <- DTB3['2004-01-02/2014-03-31']
下载的价格(假设是非平稳序列)应该转换为平稳序列以供分析;也就是说,我们将使用从调整后的序列中计算得出的对数收益率:
MSFT.ret <- diff(log(Ad(MSFT)))
SNP.ret <- diff(log(Ad(SNP)))
接下来,我们需要在进行 VAR 模型拟合之前执行最后一步数据清理。通过目测数据,我们可以看到 T-Bill 收益率序列中存在缺失数据,而且我们的数据库长度不一致(在某些日期,虽然有利率报价,但缺少股价)。为了解决这些数据质量问题,我们暂时选择最简单的解决方案:合并数据库(通过忽略所有没有三个数据点的日期),并去除所有 NA 数据。前者通过内部连接参数实现(有关详细信息,请参见 merge 函数的帮助文档):
dataDaily <- na.omit(merge(SNP.ret,MSFT.ret,DTB3.sub), join='inner')
在这里,我们注意到 VAR 建模通常是在较低频率的数据上进行的。通过使用以下函数,您可以将数据转换为月度或季度频率,这些函数会返回给定期间内的开盘、最高、最低和收盘值:
SNP.M <- to.monthly(SNP.ret)$SNP.ret.Close
MSFT.M <- to.monthly(MSFT.ret)$MSFT.ret.Close
DTB3.M <- to.monthly(DTB3.sub)$DTB3.sub.Close
可以通过使用vars包中的VAR()函数将一个简单的简化 VAR 模型拟合到数据中。以下代码中展示的参数化允许在方程中使用最多 4 个滞后期,并选择具有最佳(最低)赤池信息准则值的模型:
var1 <- VAR(dataDaily, lag.max=4, ic="AIC")
对于更为完善的模型选择,您可以考虑使用VARselect(),它提供多个信息准则(输出已省略):
>VARselect(dataDaily,lag.max=4)
结果对象是varest类的对象。通过summary()方法或show()方法(即直接输入变量)可以获取估计的参数和多个其他统计结果:
summary(var1)
var1
还有其他方法值得一提。varest类的自定义绘图方法为所有变量分别生成图表,包括拟合值、残差以及残差的自相关和偏自相关函数。您需要按Enter键以获取新变量。提供了大量的自定义设置;请参考vars包文档:
plot(var1) #Diagram of fit and residuals for each variables
coef(var1) #concise summary of the estimated variables
residuals(var1) #list of residuals (of the corresponding ~lm)
fitted(var1) #list of fitted values
Phi(var1) #coefficient matrices of VMA representation
使用我们估计的 VAR 模型进行预测,可以通过简单地调用predict函数并添加所需的置信区间来实现:
var.pred <- predict(var1, n.ahead=10, ci=0.95)
脉冲响应应该首先通过irf()函数以数值形式生成,然后可以通过plot()方法进行绘制。再次说明,我们可以为每个变量得到不同的图表,包括显示在以下命令中的带有自助法置信区间的脉冲响应函数:
var.irf <- irf(var1)
plot(var.irf)
现在,考虑使用前面描述的参数限制来拟合一个结构 VAR 模型,作为 A 模型。识别出的 SVAR 模型所需的限制数为
;在我们的例子中,这是 3。
提示
更多细节请参见 Lütkepohl (2007)。所需的附加约束数量为
,但对角元素被规范化为 1,这样我们就得到了前述数量。
SVAR 模型的起点是已经估计出的 VAR 模型的简化形式(var1)。这需要通过适当结构化的约束矩阵进行修正。
为了简化起见,我们将使用以下约束:
-
S&P 指数冲击对微软没有同时影响
-
S&P 指数冲击对利率没有同时影响
-
T 债券利率冲击对微软没有同时影响
这些约束作为 0 输入进入 SVAR 模型的 A 矩阵,具体如下:

在 R 中设置 A 矩阵作为 SVAR 估计的参数时,待估计参数的位置应设置为 NA 值。这可以通过以下赋值完成:
amat <- diag(3)
amat[2, 1] <- NA
amat[2, 3] <- NA
amat[3, 1] <- NA
最后,我们可以拟合 SVAR 模型并绘制脉冲响应函数(输出省略):
svar1 <- SVAR(var1, estmethod='direct', Amat = amat)
irf.svar1 <- irf(svar1)
plot(irf.svar1)
协整 VAR 和 VECM
最后,我们将整合到目前为止所学的内容,并讨论协整 VAR 和 向量误差修正模型(VECM) 的概念。
我们的出发点是一个协整变量的系统(例如,在交易背景下,这表示一组可能由相同基本面驱动的相似股票)。前面讨论的标准 VAR 模型仅在变量是平稳的情况下才能估计。正如我们所知,消除单位根模型的传统方法是首先对序列进行差分;然而,在协整序列的情况下,这会导致过度差分并丧失由变量水平的长期共动性传递的信息。最终,我们的目标是构建一个平稳变量的模型,同时结合原始协整非平稳变量之间的长期关系,也就是构建一个协整的 VAR 模型。这个思路被向量误差修正模型(VECM)所捕捉,VECM 包含一个差分变量的 VAR 模型,阶数为p - 1,以及一个来自已知(估计的)协整关系的误差修正项。直观地说,使用股市例子,VECM 模型建立了股票收益的短期关系,同时修正了价格长期共动性偏离的情况。
从形式上讲,我们将作为数值例子讨论的二变量 VECM 可以写成如下形式。设
为两个非平稳单位根序列的向量
,其中这两个序列是协整的,并具有一个协整向量
。然后,可以将一个适当的 VECM 模型表述如下:

在这里,
和第一项通常被称为误差修正项。
实际上,有两种方法可以测试协整关系并建立误差修正模型。对于双变量情况,Engle-Granger 方法非常有启发性;我们的数值例子基本上遵循了这个思路。对于多变量情况,其中可能的最大协整关系数量是
,你必须遵循 Johansen 程序。尽管后者的理论框架远远超出了本书的范围,我们简要展示了实践中的实现工具,并提供了进一步研究的参考文献。
为了展示一些关于 VECM 模型的基本 R 功能,我们将使用一个标准的例子,即三个月和六个月的美国国债次级市场利率,这些数据可以从 FRED 数据库下载,就像我们之前讨论的那样。我们将关注一个任意选择的时期,即从 1984 年到 2014 年。扩展的 Dickey-Fuller 检验表明单位根的原假设无法被拒绝。
library('quantmod')
getSymbols('DTB3', src='FRED')
getSymbols('DTB6', src='FRED')
DTB3.sub = DTB3['1984-01-02/2014-03-31']
DTB6.sub = DTB6['1984-01-02/2014-03-31']
plot(DTB3.sub)
lines(DTB6.sub, col='red')
我们可以通过运行简单的线性回归一致地估计这两个序列之间的协整关系。为了简化代码,我们将两个序列定义为变量x1和x2,并将相应的向量序列定义为y。代码片段中其他变量命名约定将是自解释的:
x1=as.numeric(na.omit(DTB3.sub))
x2=as.numeric(na.omit(DTB6.sub))
y = cbind(x1,x2)
cregr <- lm(x1 ~ x2)
r = cregr$residuals
如果回归的残差(变量r),即变量的适当线性组合,构成了一个平稳序列,那么这两个序列确实是协整的。你可以用常规的 ADF 检验来测试这个,但在这些设置下,常规的临界值并不适用,应该使用修正后的值(例如,参见Phillips 和 Ouliaris (1990))。
因此,使用专门的协整检验更为合适,例如 Phillips 和 Ouliaris 检验,它在tseries和urca包中都有实现。最基本的tseries版本如下所示:
install.packages('tseries');library('tseries');
po.coint <- po.test(y, demean = TRUE, lshort = TRUE)
原假设认为这两个序列没有协整关系,因此较低的 p 值表明拒绝原假设并存在协整关系。
Johansen 程序适用于多个可能的协整关系;可以在urca包中找到实现:
yJoTest = ca.jo(y, type = c("trace"), ecdet = c("none"), K = 2)
######################
# Johansen-Procedure #
######################
Test type: trace statistic , with linear trend
Eigenvalues (lambda):
[1] 0.0160370678 0.0002322808
Values of teststatistic and critical values of test:
test 10pct 5pct 1pct
r <= 1 | 1.76 6.50 8.18 11.65
r = 0 | 124.00 15.66 17.95 23.52
Eigenvectors, normalised to first column:
(These are the cointegration relations)
DTB3.l2 DTB6.l2
DTB3.l2 1.000000 1.000000
DTB6.l2 -0.994407 -7.867356
Weights W:
(This is the loading matrix)
DTB3.l2 DTB6.l2
DTB3.d -0.037015853 3.079745e-05
DTB6.d -0.007297126 4.138248e-05
r = 0(无协整关系)的检验统计量大于临界值,这表明拒绝原假设。然而,对于
,原假设不能被拒绝;因此,我们得出结论,存在一个协整关系。协整向量由下方检验结果中规范化特征向量的第一列给出。
最后一步是获得该系统的 VECM 表示,即对滞后差分变量和先前计算的协整关系得到的误差修正项进行 OLS 回归。适当的函数使用我们之前创建的ca.jo对象类。r = 1参数表示协整秩,具体如下:
>yJoRegr = cajorls(dyTest, r=1)
>yJoRegr
$rlm
Call:
lm(formula = substitute(form1), data = data.mat)
Coefficients:
x1.d x2.d
ect1 -0.0370159 -0.0072971
constant -0.0041984 -0.0016892
x1.dl1 0.1277872 0.1538121
x2.dl1 0.0006551 -0.0390444
$beta
ect1
x1.l1 1.000000
x2.l1 -0.994407
误差修正项的系数为负,如我们所预期;短期的偏离长期均衡水平会将我们的变量推回到零的均衡偏差。
你可以在双变量情况下轻松检查这一点;Johansen 方法的结果与按照 Engle-Granger 程序逐步实现的误差修正模型结果大致相同。相关内容在上传的 R 代码文件中展示。
波动率建模
在实证金融学中,波动率随着时间变化是一个广为人知且普遍接受的风格化事实。然而,波动率的不可观察性使得测量和预测变得具有挑战性。通常,波动率变化模型是由三种实证观察所激发的:
-
波动率聚集:这指的是在金融市场中,一个平静的时期通常会跟随另一个平静的时期,而动荡时期则会跟随另一个动荡时期的经验观察。
-
资产收益的非正态性:实证分析表明,资产收益相对于正态分布往往具有胖尾现象。
-
杠杆效应:这一现象导致了一个观察结果,即波动率对价格的正负变化反应不同;价格下跌会比相同幅度的上涨引起更大的波动率。
在以下代码中,我们展示了基于标准普尔资产价格的这些风格化事实。数据是通过使用已知方法从yahoofinance下载的:
getSymbols("SNP", from="2004-01-01", to=Sys.Date())
chartSeries(Cl(SNP))
我们感兴趣的目标是每日收益率序列,因此我们通过收盘价计算对数收益率。尽管这是一个直接的计算,Quantmod包提供了一个更简单的方法:
ret <- dailyReturn(Cl(SNP), type='log')
波动率分析不再仅仅是目测自相关和偏自相关函数。我们预计对数收益率是没有序列相关性的,但平方或绝对对数收益率会显示显著的自相关。这意味着对数收益率没有相关性,但并非独立。
注意以下代码中的par(mfrow=c(2,2))函数;通过这个,我们覆盖了 R 语言的默认绘图参数,以便将四个感兴趣的图表以方便的表格格式组织起来:
par(mfrow=c(2,2))
acf(ret, main="Return ACF");
pacf(ret, main="Return PACF");
acf(ret², main="Squared return ACF");
pacf(ret², main="Squared return PACF")
par(mfrow=c(1,1))
前面命令的截图如下:

接下来,我们查看 S&P 的日度对数收益的直方图和/或经验分布,并将其与具有相同均值和标准差的正态分布进行比较。对于后者,我们使用density(ret)函数来计算非参数经验分布函数。我们使用curve()函数,并增加参数add=TRUE,将第二条线绘制到已有的图表中:
m=mean(ret);s=sd(ret);
par(mfrow=c(1,2))
hist(ret, nclass=40, freq=FALSE, main='Return histogram');curve(dnorm(x, mean=m,sd=s), from = -0.3, to = 0.2, add=TRUE, col="red")
plot(density(ret), main='Return empirical distribution');curve(dnorm(x, mean=m,sd=s), from = -0.3, to = 0.2, add=TRUE, col="red")
par(mfrow=c(1,1))

超额峰度和肥尾是显而易见的,但我们可以通过数值验证(使用moments包)来确认我们样本的经验分布的峰度超过了正态分布的峰度(其值为 3)。与其他一些软件包不同,R 报告的是名义峰度,而不是超额峰度,其值如下:
> kurtosis(ret)
daily.returns
12.64959
还可以通过简单地重新缩放我们的图表,来放大图表的上尾或下尾部分。
# tail zoom
plot(density(ret), main='Return EDF - upper tail', xlim = c(0.1, 0.2), ylim=c(0,2));
curve(dnorm(x, mean=m,sd=s), from = -0.3, to = 0.2, add=TRUE, col="red")

另一个有用的可视化练习是查看对数尺度上的密度(见下图,左侧),或QQ 图(右侧),它们是常见的密度比较工具。QQ 图描绘了经验分位数与理论(正态)分布的分位数之间的关系。如果我们的样本来自正态分布,应该形成一条直线。偏离这条直线可能表明存在肥尾:
# density plots on log-scale
plot(density(ret), xlim=c(-5*s,5*s),log='y', main='Density on log-scale')
curve(dnorm(x, mean=m,sd=s), from=-5*s, to=5*s, log="y", add=TRUE, col="red")
# QQ-plot
qqnorm(ret);qqline(ret);
上述命令的截图如下:

现在,我们可以将注意力转向波动性的建模。
广义上讲,金融计量经济学文献中有两种建模技术用于捕捉波动性的变化特性:GARCH 家族方法(Engle, 1982 和 Bollerslev, 1986)和随机波动性(SV)模型。至于它们之间的区别,GARCH 型建模与(真正的)SV 型建模技术的主要不同在于,前者中给定过去观测值的条件方差是可得的,而在 SV 模型中,波动性相对于可用信息集是不可测量的;因此,它本质上是隐藏的,必须从测量方程中滤除(例如,参见 Andersen – Benzoni (2011))。换句话说,GARCH 型模型通过基于过去的观测来估计波动性,而在 SV 模型中,波动性有其自身的随机过程,这个过程是隐藏的,收益实现应作为测量方程,用于推断潜在的波动性过程。
在本章中,我们介绍 GARCH 方法的基本建模技术,主要有两个原因;首先,它在应用工作中得到了广泛使用。其次,由于其多样的理论背景,SV 模型尚未得到 R 包的原生支持,因此需要大量的自定义开发来进行经验实现。
使用 rugarch 包进行 GARCH 建模
在 R 中有多个可用于 GARCH 建模的包。最著名的有 rugarch、rmgarch(用于多变量模型)和 fGarch;然而,基本的 tseries 包也包含了一些 GARCH 功能。在本章中,我们将演示 rugarch 包的建模功能。本章中的符号遵循 rugarch 包的输出和文档中的符号。
标准 GARCH 模型
GARCH(p,q)过程可以写成如下形式:


在这里,
通常是条件均值方程的扰动项(在实际应用中,通常是 ARMA 过程)和
。也就是说,条件波动性过程是由其自身的滞后值
和滞后平方观测值(
的值)线性决定的。在实证研究中,GARCH(1,1)通常能够很好地拟合数据。可以将简单的 GARCH(1,1)模型视为一个模型,其中条件方差被指定为长期方差
、最后预测的方差
和新信息
的加权平均(参见Andersen 等人 (2009))。很容易看出,GARCH(1,1)模型如何捕捉波动性的自回归性(波动性聚集)和资产收益分布的厚尾特性,但它的主要缺点是对称的,不能捕捉分布中的不对称性和杠杆效应。
波动性聚集现象在 GARCH 模型中的出现是非常直观的;一个大的正(负)冲击在
中会增加(减少)
的值,从而增加(减少)
的值,最终导致
的值变大(变小)。这个冲击是持续的;这就是波动性聚集。厚尾性质需要一些推导;例如参见 Tsay (2010)。
我们的实证例子将是分析基于 2006 年 1 月 1 日至 2014 年 3 月 31 日期间 Apple Inc. 日收盘价计算的收益率序列。在开始分析之前,作为一个有用的练习,我们建议您重复本章中的探索性数据分析,以识别 Apple 数据中的典型特征。
显然,我们的第一步是安装一个包,如果还未安装:
install.packages('rugarch');library('rugarch')
获取数据时,通常我们使用 quantmod 包和 getSymbols() 函数,并基于收盘价计算收益率序列。
#Load Apple data and calculate log-returns
getSymbols("AAPL", from="2006-01-01", to="2014-03-31")
ret.aapl <- dailyReturn(Cl(AAPL), type='log')
chartSeries(ret.aapl)
rugarch 的编程逻辑可以这样理解:无论你的目标是什么(拟合、过滤、预测和模拟),首先,你需要将一个模型指定为系统对象(变量),然后将其插入到相应的函数中。可以通过调用 ugarchspec() 来指定模型。以下代码指定了一个简单的 GARCH (1,1) 模型(sGARCH),其均值方程中只有一个常数项
:
garch11.spec = ugarchspec(variance.model = list(model="sGARCH", garchOrder=c(1,1)), mean.model = list(armaOrder=c(0,0)))
一种显而易见的做法是将此模型拟合到我们的数据中,即基于我们的每日收益时间序列,通过最大似然估计未知参数:
aapl.garch11.fit = ugarchfit(spec=garch11.spec, data=ret.aapl)
该函数提供了多个输出,其中包括参数估计值
:
> coef(aapl.garch11.fit)
mu omega alpha1 beta1
1.923328e-03 1.027753e-05 8.191681e-02 8.987108e-01
通过生成的对象的 show() 方法(即直接输入变量名),可以获得估计值和各种诊断测试。通过输入适当的命令,还可以获取一系列其他统计量、参数估计值、标准误差和协方差矩阵估计。完整列表请参见 ugarchfit 对象类;以下代码显示了最重要的几个:
coef(msft.garch11.fit) #estimated coefficients
vcov(msft.garch11.fit) #covariance matrix of param estimates
infocriteria(msft.garch11.fit) #common information criteria list
newsimpact(msft.garch11.fit) #calculate news impact curve
signbias(msft.garch11.fit) #Engle - Ng sign bias test
fitted(msft.garch11.fit) #obtain the fitted data series
residuals(msft.garch11.fit) #obtain the residuals
uncvariance(msft.garch11.fit) #unconditional (long-run) variance
uncmean(msft.garch11.fit) #unconditional (long-run) mean
标准 GARCH 模型能够捕捉到重尾和波动性聚集现象,但要解释由杠杆效应引起的不对称性,我们需要更为高级的模型。为了从视觉上处理不对称性问题,我们现在将描述新闻冲击曲线的概念。
新闻冲击曲线由 Pagan 和 Schwert (1990) 以及 Engle 和 Ng (1991) 提出,是可视化波动性变化幅度应对冲击的有用工具。这个名称源于将冲击通常解释为影响市场波动的新闻。它们绘制了条件波动性变化与不同大小冲击的关系,并能够简洁地表达波动性的不对称效应。在以下代码中,第一行计算了之前定义的 GARCH(1,1) 模型的新闻冲击数值,第二行则生成了可视化图表:
ni.garch11 <- newsimpact(aapl.garch11.fit)
plot(ni.garch11$zx, ni.garch11$zy, type="l", lwd=2, col="blue", main="GARCH(1,1) - News Impact", ylab=ni.garch11$yexpr, xlab=ni.garch11$xexpr)
上述命令的截图如下:

正如我们预期的那样,正负冲击的响应中没有出现不对称现象。现在,我们转向可以同时考虑不对称效应的模型。
指数 GARCH 模型 (EGARCH)
指数 GARCH 模型由 Nelson (1991) 提出。该方法直接对条件波动性的对数建模:


其中,E 是期望算子。这个模型的公式允许在演化波动率过程中具有乘法动力学。不对称性通过
参数来捕捉;负值表示该过程对负向冲击的反应更为强烈,这在实际数据集中可以观察到。
要拟合一个 EGARCH 模型,唯一需要在模型规范中更改的参数是设置 EGARCH 模型类型。通过运行 fitting 函数,附加参数将被估计(请参见 coef()):
# specify EGARCH(1,1) model with only constant in mean equation
egarch11.spec = ugarchspec(variance.model = list(model="eGARCH", garchOrder=c(1,1)), mean.model = list(armaOrder=c(0,0)))
aapl.egarch11.fit = ugarchfit(spec=egarch11.spec, data=ret.aapl)
> coef(aapl.egarch11.fit)
mu omega alpha1 beta1 gamma1
0.001446685 -0.291271433 -0.092855672 0.961968640 0.176796061
新闻冲击曲线反映了条件波动率对冲击反应的强烈不对称性,并验证了非对称模型的必要性:
ni.egarch11 <- newsimpact(aapl.egarch11.fit)
plot(ni.egarch11$zx, ni.egarch11$zy, type="l", lwd=2, col="blue", main="EGARCH(1,1) - News Impact",
ylab=ni.egarch11$yexpr, xlab=ni.egarch11$xexpr)

阈值 GARCH 模型 (TGARCH)
另一个突出的例子是 TGARCH 模型,它的解释更为简单。TGARCH 规范明确区分了某一阈值上下的模型参数。TGARCH 也是一个更一般类别的子模型,即非对称幂 ARCH 类别,但由于其在应用金融计量经济学文献中的广泛应用,我们将单独讨论它。
TGARCH 模型可以表述如下:



解释非常直接;ARCH 系数取决于前一个误差项的符号;如果
为正,则负的误差项会对条件波动率产生更大的影响,就像我们之前看到的杠杆效应一样。
在 R 包 rugarch 中,阈值 GARCH 模型是在一个更一般的 GARCH 模型框架中实现的,该框架被称为家庭 GARCH 模型 Ghalanos (2014)。
# specify TGARCH(1,1) model with only constant in mean equation
tgarch11.spec = ugarchspec(variance.model = list(model="fGARCH", submodel="TGARCH", garchOrder=c(1,1)),
mean.model = list(armaOrder=c(0,0)))
aapl.tgarch11.fit = ugarchfit(spec=tgarch11.spec, data=ret.aapl)
> coef(aapl.egarch11.fit)
mu omega alpha1 beta1 gamma1
0.001446685 -0.291271433 -0.092855672 0.961968640 0.176796061
由于特定的函数形式,阈值 GARCH 模型的新闻冲击曲线在表示不同响应方面不够灵活,在零点处有一个拐点,我们可以通过运行以下命令观察到这一点:
ni.tgarch11 <- newsimpact(aapl.tgarch11.fit)
plot(ni.tgarch11$zx, ni.tgarch11$zy, type="l", lwd=2, col="blue", main="TGARCH(1,1) - News Impact",
ylab=ni.tgarch11$yexpr, xlab=ni.tgarch11$xexpr)

模拟与预测
Rugarch 包提供了一个简单的方法来从指定的模型中进行模拟。当然,为了进行模拟,我们还应在 ugarchspec() 中指定模型的参数;这可以通过 fixed.pars 参数来完成。指定模型后,我们可以通过简单地使用 ugarchpath() 函数来模拟一个具有给定条件均值和 GARCH 规范的时间序列:
garch11.spec = ugarchspec(variance.model = list(garchOrder=c(1,1)),
mean.model = list(armaOrder=c(0,0)),
fixed.pars=list(mu = 0, omega=0.1, alpha1=0.1,
beta1 = 0.7))
garch11.sim = ugarchpath(garch11.spec, n.sim=1000)
一旦我们得到了估计模型,并且在技术上有了拟合对象,基于该模型预测条件波动率就只需一步:
aapl.garch11.fit = ugarchfit(spec=garch11.spec, data=ret.aapl, out.sample=20)
aapl.garch11.fcst = ugarchforecast(aapl.garch11.fit, n.ahead=10, n.roll=10)
预测序列的绘图方法为用户提供了一个选择菜单;我们可以绘制预测的时间序列或预测的条件波动率。
plot(aapl.garch11.fcst, which='all')

总结
在本章中,我们回顾了时间序列分析中的一些重要概念,如协整、向量自回归和 GARCH 类型的条件波动性模型。同时,我们提供了一个有用的入门指南,介绍了开始使用 R 进行定量与实证金融建模的一些技巧与窍门。我们希望你觉得这些练习有用,但需要再次强调的是,本章在时间序列和计量经济学理论方面,以及从 R 编程的角度来看,远未完整。R 编程语言在互联网上有非常丰富的文档,R 用户社区由成千上万的高级与专业用户组成。我们鼓励你超越书本,做一个自学者,并且在遇到问题时不要停滞不前;几乎可以肯定,你会在互联网上找到继续前进的答案。请大量使用 R 包的文档和帮助文件,频繁访问 R 的官方网站cran.r-project.org/。接下来的章节将为你提供更多的示例,帮助你在 R 的各种功能、包和函数中找到自己的方向。
参考文献与阅读书单
-
Andersen, Torben G; Davis, Richard A.; Kreiß, Jens-Peters; Mikosh, Thomas(编)(2009)。金融时间序列手册
-
Andersen, Torben G. 和 Benzoni, Luca (2011)。随机波动性。复杂系统在金融与计量经济学中的应用,编辑:Meyers, Robert A.,Springer
-
Brooks, Chris (2008)。金融计量经济学导论,剑桥大学出版社
-
Fry, Renee 和 Pagan, Adrian (2011)。结构向量自回归中的符号限制:一项批判性回顾。《经济文献杂志》,美国经济学会,卷 49(4),938-960 页,12 月。
-
Ghalanos, Alexios (2014)。《rugarch 包介绍》
cran.r-project.org/web/packages/rugarch/vignettes/Introduction_to_the_rugarch_package.pdf -
Hafner, Christian M. (2011)。GARCH 建模。复杂系统在金融与计量经济学中的应用,编辑:Meyers, Robert A.,Springer
-
Hamilton, James D. (1994)。时间序列分析,普林斯顿,新泽西州
-
Lütkepohl, Helmut (2007)。多重时间序列分析新导论,Springer
-
Murray, Michael. P. (1994)。一个醉汉和她的狗:协整与误差修正的说明。美国统计学家,48(1),37-39。
-
Martin, Vance; Hurn, Stan 和 Harris, David (2013)。时间序列的计量经济模型:规格、估计与检验,剑桥大学出版社
-
Pfaff, Bernard (2008)。使用 R 分析集成和协整时间序列,Springer
-
Pfaff, Bernhard (2008)。VAR、SVAR 和 SVEC 模型:在 R 包 vars 中的实现。统计软件杂志,27(4)
-
Phillips, P. C., & Ouliaris, S. (1990). 基于残差的协整检验的渐近性质。计量经济学:计量经济学会期刊, 165-193。
-
Pole, Andrew (2007). 统计套利。Wiley
-
Rachev, Svetlozar T., Hsu, John S.J., Bagasheva, Biliana S. 和 Fabozzi, Frank J. (2008). 金融中的贝叶斯方法。John Wiley & Sons.
-
Sims, Christopher A. (1980). 宏观经济学与现实。计量经济学:计量经济学会期刊, 1-48。
-
Tsay, Ruey S. (2010). 金融时间序列分析,第 3 版,Wiley
第二章:因素模型
在金融领域,大多数情况下,金融资产的估值是基于折现现金流法;因此,现值是预计未来现金流的折现值。因此,为了能够对资产进行估值,我们需要知道反映时间价值和给定资产风险的适当回报率。有两种主要方法来确定预期回报:资本资产定价模型(CAPM)和套利定价理论(APT)。CAPM 是一个均衡模型,而 APT 则基于无套利原理;因此,这些方法的出发点和内在逻辑差异很大。然而,根据我们使用的市场因素,最终得到的定价公式可能非常相似。有关 CAPM 和 APT 的比较,参见Bodie-Kane-Marcus (2008)。当我们在现实世界数据上测试这些理论模型时,我们会进行线性回归。本章重点讨论 APT,因为我们在Daróczi et al. (2013)中已更详细地讨论了 CAPM。
本章分为两部分。在第一部分,我们一般性地介绍 APT 理论,然后展示Fama 和 French在一篇开创性论文中提出的特殊三因素模型。在第二部分,我们展示如何使用 R 进行数据选择,以及如何从实际市场数据中估算定价系数,最后我们将对一个更新的样本重新审视著名的 Fama-French 模型。
套利定价理论
APT 假设市场上资产的回报由宏观经济因素和公司特定因素决定,资产回报由以下线性因素模型生成:

方程式 1
其中,E(r[i])是资产i的预期回报,F[j]代表第j个因素的意外变化,β[ij]表示第i只证券对该因素的敏感度,而e[i]是由公司特定事件引起的回报。因此,
表示随机的系统性效应,e[i]表示非系统性(即特有的)效应,这是市场因素无法捕捉到的。由于是意外的,
和e[i]的均值都为零。在此模型中,因素彼此独立,并且与公司特定风险无关。因此,资产回报来源于两个方面:影响市场上所有资产的因素所带来的系统性风险和仅影响特定公司的非系统性风险。非系统性风险可以通过持有更多资产来进行多样化投资。相比之下,系统性风险无法通过多样化来分散,因为它是由影响整个股市的经济范围内的风险源引起的(Brealey-Myers, 2005)。
根据该模型,资产的实际回报是多个随机因素的线性组合(Wilmott, 2007)。
APT 的其他重要假设如下:
-
市场上有有限数量的投资者,他们优化下一时期的投资组合。这些投资者信息对称,且没有市场影响力。
-
存在一种无风险资产和无限数量的不断交易的风险资产;因此,可以通过多样化完全消除公司特定风险。具有零公司特定风险的投资组合被称为良好多样化的投资组合。
-
投资者是理性的,即如果出现套利机会(金融资产相对定价错误),投资者会立即购买被低估的证券/证券,并卖出被高估的证券,并会采取无限大的仓位以赚取尽可能多的无风险利润。因此,任何定价错误都会立即消失。
-
存在因子投资组合,并且它们可以不断交易。因子投资组合是一个良好多样化的投资组合,仅对某一因子有反应;具体来说,它对该指定因子的贝塔值为 1,对所有其他因子的贝塔值为 0。
根据前述假设,可以证明任何投资组合的风险溢价等于因子投资组合风险溢价的加权总和(Medvegyev-Száz, 2010)。在双因子模型的情况下,可以推导出以下定价公式:

方程 2
这里,r[i] 是第i个资产的回报,r[f] 是无风险回报,β[i1] 是第i个股票的风险溢价对第一个系统性因子的敏感度,(r[1]-r[f])是该因子的风险溢价。类似地,β[i2] 是第i个股票的风险溢价对第二个因子的超额回报(r[2]-r[f])的敏感度。
当我们实施 APT 时,我们会执行以下形式的线性回归:

方程 3
其中,α[i] 表示常数,ε[i] 是资产的非系统性、公司特定风险。所有其他变量与之前提到的一致。
如果模型中只有一个因子,且它是市场投资组合的回报,则 CAPM 模型和 APT 模型的定价方程将完全一致:

方程 4
在这种情况下,需在真实市场数据上测试的公式如下:

方程 5
这里,
是由市场指数(如 S&P 500)代表的市场投资组合的回报。这就是为什么我们称方程(5)为指数模型的原因。
APT 的实现
APT 的实现可以分为四个步骤:识别因子、估算因子系数、估算因子溢价以及使用 APT 进行定价(Bodie 等, 2008):
-
识别因素:由于 APT 没有提到任何因素,因此这些因素必须通过经验方法来识别。这些因素通常是宏观经济因素,如股市回报、通货膨胀、商业周期等。使用宏观经济因素的主要问题是,这些因素通常不是相互独立的。因素的识别通常通过因子分析来进行。然而,通过因子分析识别出的因素不一定能以经济学有意义的方式进行解释。
-
估算因子系数:为了估算多元线性回归模型中的系数,使用方程(3)的一个通用版本。
-
估算因子溢价:因子溢价的估算基于历史数据,通过取因子组合的历史时间序列数据的平均值来进行。
-
使用 APT 定价:方程(2)用于通过将适当的变量代入方程来计算任何资产的预期回报。
法马-弗兰奇三因子模型
法马和弗兰奇在 1996 年提出了一个多因子模型,在该模型中,他们使用了公司指标作为因子,而不是宏观经济因子,因为他们发现这些因子能更好地描述资产的系统性风险。法马和弗兰奇(1996)通过添加公司规模和账面市值比作为回报生成因子,扩展了指数模型,以便与市场投资组合的回报相结合(法马和弗兰奇,1996)。
公司规模因子是通过计算小型和大型公司回报的差异(r[SMB])来构建的。该变量的名称为 SMB,来源于“small minus big”(小减大)。账面市值比因子是通过计算回报差异来计算的,比较的是账面市值比高和低的公司(r[HML])。该变量的名称为 HML,来源于“high minus low”(高减低)。
他们的模型如下:

方程 6
在这里,α[i]是一个常数,表示异常回报率,r[f]是无风险回报,β[iHML]是第i资产对账面市值比因子的敏感度,β[iSMB]是第i资产对规模因子的敏感度,β[iM]是第i股票的风险溢价对市场指数因子的敏感度,(r[M]-r[f])是该因子的风险溢价,而e[i]是该资产的非系统性、公司特定的风险,其均值为零。
在 R 中建模
在接下来的章节中,我们将学习如何借助 R 实现前述模型。
数据选择
在第四章中,大数据 - 高级分析,我们将详细讨论如何从开放数据源获取数据并高效使用它们的方法。在这里,我们仅展示如何获取股票价格和其他相关信息的时间序列,并用于因子模型的估算。
我们使用了quantmod包来收集数据库。
下面是如何在 R 中实现的:
library(quantmod)
stocks <- stockSymbols()
结果是,我们需要等待几秒钟才能获取数据,然后就可以看到输出:
Fetching AMEX symbols...
Fetching NASDAQ symbols...
Fetching NYSE symbols...
现在,我们有一个包含约 6,500 只在不同交易所(如 AMEX、NASDAQ 或 NYSE)交易的股票的数据框 R 对象。为了查看数据集包含的变量,我们可以使用str命令:
str(stocks)
'data.frame': 6551 obs. of 8 variables:
$ Symbol : chr "AA-P" "AAMC" "AAU" "ACU" ...
$ Name : chr "Alcoa Inc." "Altisource Asset Management Corp"...
$ LastSale : num 87 1089.9 1.45 16.58 16.26 ...
$ MarketCap: num 0.00 2.44e+09 9.35e+07 5.33e+07 2.51e+07 ...
$ IPOyear : int NA NA NA 1988 NA NA NA NA NA NA ...
$ Sector : chr "Capital Goods" "Finance" "Basic Industries"...
$ Industry : chr "Metal Fabrications" "Real Estate"...
$ Exchange : chr "AMEX" "AMEX" "AMEX" "AMEX" ...
我们可以删除不需要的变量,并将来自其他数据库的公司市值和账面价值作为新变量加入,因为我们将在估算 Fama-French 模型时需要它们:
stocks[1:5, c(1, 3:4, ncol(stocks))]
Symbol LastSale MarketCap BookValuePerShare
1 AA-P 87.30 0 0.03
2 AAMC 985.00 2207480545 -11.41
3 AAU 1.29 83209284 0.68
4 ACU 16.50 53003808 10.95
5 ACY 16.40 25309415 30.13
我们还需要无风险回报的时间序列,这将在此计算中通过一个月的美元 LIBOR 利率来量化:
library(Quandl)
Warning message:
package 'Quandl' was built under R version 3.1.0
LIBOR <- Quandl('FED/RILSPDEPM01_N_B',
start_date = '2010-06-01', end_date = '2014-06-01')
Warning message:
In Quandl("FED/RILSPDEPM01_N_B", start_date = "2010-06-01", end_date = "2014-06-01") : It would appear you aren't using an authentication token. Please visit http://www.quandl.com/help/r or your usage may be limited.
我们可以忽略警告信息,因为数据仍然已分配给 LIBOR 变量。
Quandl包、tseries包以及其他收集数据的包在第四章中有更详细的讨论,大数据 – 高级分析。
这也可以用来获取股票价格,标准普尔 500 指数可以作为市场投资组合使用。
我们有一张股票价格表(大约 5,000 只股票在 2010 年 6 月 1 日到 2014 年 6 月 1 日之间的时间序列)。前几列和最后几列看起来是这样的:
d <- read.table("data.csv", header = TRUE, sep = ";")
d[1:7, c(1:5, (ncol(d) - 6):ncol(d))]
Date SP500 AAU ACU ACY ZMH ZNH ZOES ZQK ZTS ZX
1 2010.06.01 1070.71 0.96 11.30 20.64 54.17 21.55 NA 4.45 NA NA
2 2010.06.02 1098.38 0.95 11.70 20.85 55.10 21.79 NA 4.65 NA NA
3 2010.06.03 1102.83 0.97 11.86 20.90 55.23 21.63 NA 4.63 NA NA
4 2010.06.04 1064.88 0.93 11.65 18.95 53.18 20.88 NA 4.73 NA NA
5 2010.06.07 1050.47 0.97 11.45 19.03 52.66 20.24 NA 4.18 NA NA
6 2010.06.08 1062.00 0.98 11.35 18.25 52.99 20.96 NA 3.96 NA NA
7 2010.06.09 1055.69 0.98 11.90 18.35 53.22 20.45 NA 4.02 NA NA
如果我们将数据保存在硬盘上,可以使用read.table函数直接读取它。在第四章中,我们将讨论如何直接从互联网收集数据。
现在,我们拥有了所有需要的数据:市场投资组合(标准普尔 500 指数)、股票价格和无风险利率(一个月 LIBOR 利率)。
我们选择删除缺失值和价格为 0 或负值的变量,以便清理数据库。最简单的做法如下:
d <- d[, colSums(is.na(d)) == 0]
d <- d[, c(T, colMins(d[, 2:ncol(d)]) > 0)]
要使用colMins函数,我们需要应用matrixStats包。现在,我们可以开始处理数据了。
使用主成分分析估算 APT
实际上,进行因子分析并不容易,因为确定对证券回报有影响的宏观变量是困难的(Medvegyev – Száz, 2010, 第 42 页)。在许多情况下,驱动回报的潜在因子是通过主成分分析(PCA)来寻找的。
从最初下载的 6,500 只股票中,我们可以使用 4,015 只股票的数据;其余的由于缺失值或价格为 0 而被排除。现在,我们省略了前两列,因为我们在这一部分不需要日期,且标准普尔 500 指数被视为一个独立的因子,因此我们不将其包括在主成分分析(PCA)中。完成这些后,我们计算对数回报。
p <- d[, 3:ncol(d)]
r <- log(p[2:nrow(p), ] / p[1:(nrow(p) - 1), ])
还有另一种计算给定资产对数回报的方法,那就是使用return.calculate(data, method="log"),该方法来自PerformanceAnalytics库。
由于股票数量过多,为了进行主成分分析(PCA),我们要么需要至少 25 年的数据,要么需要减少股票的数量。因子模型在数十年内保持稳定几乎不可能;因此,为了说明问题,我们选择随机选取 10%的股票并对该样本计算模型:
r <- r[, runif(nrow(r)) < 0.1]
runif(nrow(r)) < 0.1 是一个维度为 4,013 的 0-1 向量,它大约从表格中选择 10%的列(在我们的案例中是 393 列)。我们也可以使用以下示例函数来实现这一点,更多细节可以参考stat.ethz.ch/R-manual/R-devel/library/base/html/sample.html:
pca <- princomp(r)
结果是,我们得到了一个princomp类对象,包含八个属性,其中最重要的属性是载荷矩阵和sdev属性,后者包含各个成分的标准差。第一主成分是数据集具有最大方差的向量。
让我们检查主成分的标准差:
plot(pca$sdev)
结果如下:

我们可以看到,前五个成分已被分开,因此应选择五个因子,但其他因子的标准差也很显著,所以市场不能仅由少数几个因子来解释。
我们可以通过调用factanal函数来确认这一结果,该函数使用五个因子估计因子模型:
factanal(r, 5)
我们注意到,进行此计算时需要更多的时间。因子分析与 PCA 有关,但在数学上稍微复杂一些。结果是,我们得到了一个factanal类的对象,包含多个属性,但此时我们只对以下部分输出感兴趣:
Factor1 Factor2 Factor3 Factor4 Factor5
SS loadings 56.474 23.631 15.440 12.092 6.257
Proportion Var 0.144 0.060 0.039 0.031 0.016
Cumulative Var 0.144 0.204 0.243 0.274 0.290
Test of the hypothesis that 5 factors are sufficient.
The chi square statistic is 91756.72 on 75073 degrees of freedom.The p-value is 0
该输出显示五因子模型拟合效果较好,但解释方差仅为约 30%,这意味着该模型需要扩展,加入其他因子。
Fama-French 模型的估计
我们有一个包含 4,015 只股票价格的五年期数据框,以及一个包含 LIBOR 时间序列的 LIBOR 数据框。首先,我们需要计算收益率并将其与 LIBOR 利率合并。
第一步,我们省略那些不用于数学计算的日期,然后计算剩余列的对数收益率:
d2 <- d[, 2:ncol(d)]
d2 <- log(tail(d1, -1)/head(d1, -1))
在计算对数收益率后,我们将日期与收益率重新合并,最后一步,我们将两个数据集结合起来:
d <- cbind(d[2:nrow(d), 1], d2)
d <- merge(LIBOR, d, by = 1)
值得注意的是,merge函数操作的数据框等同于 SQL 中的(内连接)语句。
结果如下:
print(d[1:5, 1:5])]
Date LIBOR SP500 AAU ACU
2010.06.02 0.4 0.025514387 -0.01047130 0.034786116
2010.06.03 0.4 0.004043236 0.02083409 0.013582552
2010.06.04 0.4 -0.035017487 -0.04211149 -0.017865214
2010.06.07 0.4 -0.013624434 0.04211149 -0.017316450
2010.06.08 0.4 0.010916240 0.01025650 -0.008771986
我们将 LIBOR 利率调整为日收益率:
d$LIBOR <- d$LIBOR / 36000
由于 LIBOR 利率是基于货币市场报价的(实际/360)天数计算法,且时间序列中包含了百分比形式的利率,因此我们将 LIBOR 除以 36,000。现在,我们需要计算法马-法兰奇模型的三个变量。如在数据选择部分所述,我们有股票的数据框:
d[1:5, c(1,(ncol(d) - 3):ncol(d))]
Symbol LastSale MarketCap BookValuePerShare
1 AA-P 87.30 0 0.03
2 AAMC 985.00 2207480545 -11.41
3 AAU 1.29 83209284 0.68
4 ACU 16.50 53003808 10.95
5 ACY 16.40 25309415 30.13
我们必须删除那些没有价格数据的股票:
> stocks = stocks[stocks$Symbol %in% colnames(d),]
我们有市场资本作为一个变量;我们仍然需要为每个公司计算账面市值比:
stocks$BookToMarketRatio <-
stocks$BookValuePerShare / stocks$LastSale
str(stocks)
'data.frame': 3982 obs. of 5 variables:
$ Symbol : Factor w/ 6551 levels "A","AA","AA-P",..: 14 72...
$ LastSale : num 1.29 16.5 16.4 2.32 4.05 ...
$ MarketCap : num 8.32e+07 5.30e+07 2.53e+07 1.16e+08...
$ BookValuePerShare: num 0.68 10.95 30.13 0.19 0.7 ...
$ BookToMarketRatio: num 0.5271 0.6636 1.8372 0.0819 0.1728 ...
现在,我们需要计算 SMB 和 HML 因子。为了简化,我们将公司定义为BIG,如果它们的市值大于平均值。对于账面市值比,也应用相同的原则:
avg_size <- mean(stocks$MarketCap)
BIG <- as.character(stocks$Symbol[stocks$MarketCap > avg_size])
SMALL <- as.character(stocks[stocks$MarketCap < avg_size,1])
这些数组包含了BIG和SMALL公司的符号。现在,我们可以定义 SMB 因子:
d$SMB <- rowMeans(d[,colnames(d) %in% SMALL]) –
rowMeans(d[,colnames(d) %in% BIG])
我们将 HML 因子定义如下:
avg_btm <- mean(stocks$BookToMarketRatio)
HIGH <- as.character(
stocks[stocks$BookToMarketRatio > avg_btm, 1])
LOW <- as.character(
stocks[stocks$BookToMarketRatio < avg_btm, 1])
d$HML <- rowMeans(d[, colnames(d) %in% HIGH]) –
rowMeans(d[, colnames(d) %in% LOW])
计算第三个因子:
d$Market <- d$SP500 - d$LIBOR
在定义了三个因子后,我们在花旗集团(Citi)和 Exelixis 公司(EXEL)的股票上进行了测试:
d$C <- d$C - d$LIBOR
model <- glm( formula = "C ~ Market + SMB + HML" , data = d)
GLM(广义线性模型)函数的工作原理如下:它将数据和公式作为参数。公式是一个响应~项的字符串,其中响应是数据框中的变量名称,项指定模型中的预测变量,因此它由数据集中的变量名称组成,并用+运算符分隔。该函数也可以用于逻辑回归,但默认是线性回归。
模型的输出如下:
Call: glm(formula = "C~Market+SMB+HML", data = d)
Coefficients:
(Intercept) Market SMB HML
0.001476 1.879100 0.401547 -0.263599
Degrees of Freedom: 1001 Total (i.e. Null); 998 Residual
Null Deviance: 5.74
Residual Deviance: 5.364 AIC: -2387
模型总结的输出如下:
summary(model)
Call:
glm(formula = "C~Market+SMB+HML", data = d)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.09344 -0.01104 -0.00289 0.00604 2.26882
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.001476 0.002321 0.636 0.525
Market 1.879100 0.231595 8.114 1.43e-15 ***
SMB 0.401547 0.670443 0.599 0.549
HML -0.263599 0.480205 -0.549 0.583
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for gaussian family taken to be 0.005374535)
Null deviance: 5.7397 on 1001 degrees of freedom
Residual deviance: 5.3638 on 998 degrees of freedom
AIC: -2387
Number of Fisher Scoring iterations: 2
结果显示,唯一显著的因子是市场溢价,这意味着花旗的股票回报似乎与整个市场的表现同步。
要绘制结果,应使用以下命令:
estimation <- model$coefficients[1]+
model$coefficients[2] * d$Market +
model$coefficients[3]*d$SMB +
model$coefficients[4]*d$HML
plot(estimation, d$C, xlab = "estimated risk-premium",
ylab = "observed riks premium",
main = "Fama-French model for Citigroup")
lines(c(-1, 1), c(-1, 1), col = "red")
以下截图显示了法马-法兰奇模型对花旗的估算风险溢价:

如果我们看一下图表,可以看到回报中存在异常值。让我们看看如果我们将其替换为 0 会发生什么。
outlier <- which.max(d$C)
d$C[outlier] <- 0
如果我们再次运行相同的代码来创建模型,并重新计算估算和实际回报,我们将得到以下结果:
model_new <- glm( formula = "C ~ Market + SMB + HML" , data = d)
summary(model_new)
Call:
glm(formula = "C ~ Market + SMB + HML", data = d)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.091733 -0.007827 -0.000633 0.007972 0.075853
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.0000864 0.0004498 -0.192 0.847703
Market 2.0726607 0.0526659 39.355 < 2e-16 ***
SMB 0.4275055 0.1252917 3.412 0.000671 ***
HML 1.7601956 0.2031631 8.664 < 2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for gaussian family taken to be 0.0001955113)
Null deviance: 0.55073 on 1001 degrees of freedom
Residual deviance: 0.19512 on 998 degrees of freedom
AIC: -5707.4
Number of Fisher Scoring iterations: 2
根据结果,所有三个因子都是显著的。
GLM 函数不会返回 R²。对于线性回归,可以完全相同地使用 lm 函数,并且可以从模型总结中得到 r.squared = 0.6446。
这个结果表明,这些变量解释了花旗银行风险溢价方差的 64%以上。让我们绘制新的结果:
estimation_new <- model_new$coefficients[1]+
model_new$coefficients[2] * d$Market +
model_new$coefficients[3]*d$SMB +
model_new$coefficients[4]*d$HML
dev.new()
plot(estimation_new, d$C, xlab = "estimated risk-premium",ylab = "observed riks premium",main = "Fama-French model for Citigroup")
lines(c(-1, 1), c(-1, 1), col = "red")
在这种情况下的输出如下:

我们还在另一只股票 EXEL 上测试该模型:
d$EXEL <- d$EXEL – d$LIBOR
model2 <- glm( formula = "EXEL~Market+SMB+HML" , data = d)
Call: glm(formula = "EXEL~Market+SMB+HML", data = d)
Coefficients:
(Intercept) Market SMB HML
-0.001048 2.038001 2.807804 -0.354592
Degrees of Freedom: 1001 Total (i.e. Null); 998 Residual
Null Deviance: 1.868
Residual Deviance: 1.364 AIC: -3759
模型总结的输出如下:
summary(model2)
Call:
glm(formula = "EXEL~Market+SMB+HML", data = d)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.47367 -0.01480 -0.00088 0.01500 0.25348
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.001773 0.001185 -1.495 0.13515
Market 1.843306 0.138801 13.280 < 2e-16 ***
SMB 2.939550 0.330207 8.902 < 2e-16 ***
HML -1.603046 0.535437 -2.994 0.00282 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for gaussian family taken to be 0.001357998)
Null deviance: 1.8681 on 1001 degrees of freedom
Residual deviance: 1.3553 on 998 degrees of freedom
AIC: -3765.4
Number of Fisher Scoring iterations: 2
根据结果,所有三个因子都是显著的。
GLM 函数不包含 R²。对于线性回归,可以使用 lm 函数,使用方式完全相同,并且我们从模型摘要中得到 r.squared = 0.2723。根据结果,变量解释了 EXEL 风险溢价方差的 27%以上。
为了绘制结果,可以使用以下命令:
estimation2 <- model2$coefficients[1] +
model2$coefficients[2] * d$Market +
model2$coefficients[3] * d$SMB + model2$coefficients[4] * d$HML
plot(estimation2, d$EXEL, xlab = "estimated risk-premium",
ylab = "observed riks premium",
main = "Fama-French model for EXEL")
lines(c(-1, 1), c(-1, 1), col = "red")

总结
在本章中,我们展示了如何构建和实施多因子模型。通过主成分分析,我们识别出了五个独立因子来解释资产回报,但它们似乎不足以全面解释,因为它们仅解释了 30%的方差。为了说明这一点,我们还在真实市场数据上重现了著名的法马-法兰奇模型,其中,除了市场因子外,还使用了两个额外的公司特定因子(SMB 和 HML)。我们使用了内置的主成分分析和因子分析函数,并展示了如何使用一般线性模型进行回归分析。
我们发现这三个因子具有显著性。因此,我们可以得出结论,在更近期的样本中,法马-法兰奇因子具有解释能力。我们鼓励你开发和测试新的多因子定价公式,这些公式可以像经典模型一样有效,甚至更好。
参考文献
-
E.F. Fama 和 K.R. French(1996),《资产定价异常的多因子解释》,《金融学杂志》51 期,第 55-84 页
-
Z. Bodie, A. Kane 和 A. Marcus(2008),《投资要素》,第 7 版,麦格劳-希尔国际
-
P. Medvegyev 和 J. Száz(2010),《金融市场中的意外特征》,Bankárképző,布达佩斯
-
P. Wilmott(2007),《Paul Wilmott 介绍量化金融》,第二版,约翰·威利父子公司,西萨塞克斯
-
G. Daróczi, M. Puhle, E. Berlinger, P. Csóka, D. Havran, M. Michaletzky, Zs. Tulassay, K. Váradi 和 A. Vidovics-Dancs(2013),《量化金融中的 R 入门》,Packt 出版,伯明翰-孟买
-
S.A. Ross(1976),《回报、风险与套利:在《金融中的风险与回报》一书中》,剑桥,马萨诸塞州,Ballinger
-
Gy. Walter, E. Berlinger(1999),《证券市场中的因子模型》(Factormodels on securities' markets),《银行评论》43(4),第 34-43 页。ISSN 0133-0519
第三章:预测交易量
股市上的价格形成已经成为许多研究人员数十年来关注的核心。因此,关于价格的理论、模型和实证证据层出不穷,尽管总有新的方面可以探索,但我们认为在这个主题上,金融知识已经相当全面。我们对价格的动态有着相当清晰的理解,而且大多数人都同意,价格的预测相当困难。
相比之下,交易量,作为股市交易过程中的另一项基本指标,研究较少。最常见的价格均衡模型甚至没有将交易量纳入其解释交易活动的框架中。直到最近,研究人员似乎才开始越来越关注交易量,并且他们已经发现,交易量的样式化事实相比价格能够提供更好的预测。
本章旨在介绍从现有文献中选出的一个日内预测模型,并提供其在 R 语言中的实现。
动机
研究交易量背后的动机不仅仅是理论性的,它同样具有重要的实践意义。在订单驱动市场中,如果提交的买入(卖出)市价单相较于市场较大,它可能会扫除多个价格档位;因此,整个交易的平均成交价格会高于(低于)订单提交时的最佳价格档位,提交者将因此亏损。这一现象通常被称为价格冲击,它值得我们努力避免,或者至少最小化。
实现这一目标的一种方式是进行订单拆分,即将市场订单拆分成更小的部分并逐步提交。在众多拆单逻辑中,一种流行的策略是成交量加权平均价格(VWAP)策略,旨在获取每日加权平均价格,其中的权重由相对于当天总成交量的交易量决定。长期投资者通常会接受一个与每日 VWAP 相等的平均执行价格,因为它被认为是一个中性交易结果。然而,一些投资者发现,全天拆单的方式难以达到 VWAP,因为 VWAP 只有在一天结束时才能计算出来,因此他们将这一问题委托给经纪商。经纪商保证按 VWAP 执行交易,并为此服务收取费用。这个费用也起到了跟踪误差的缓冲作用,这意味着,拥有最精确日成交量预测的经纪商将能够向客户收取最少的费用,因为他们只需要根据预测的比例拆分订单,然后(假设预测是完美的)VWAP 就会在价格演变的过程中达成。因此,对于经纪商来说,准确的成交量预测被视为一种宝贵的商业资产,直接影响他们的利润。
交易强度
交易活动的强度可以通过多种方式来衡量。最常用的衡量标准是成交量,它仅仅是指在某一特定时间区间内交易的股票数量。考虑到不同股票的流动性(即交易资产的容易程度),因此每只股票的绝对交易活动也不同,使用百分比形式表示的成交量是建模时更方便的选择。这个衡量标准被称为换手率,它是根据成交量计算出来的,计算公式如下:

在这里,x 代表换手率,V 代表成交量,TSO 代表流通股本;后者表示公开交易的股票总数。指数 i 表示实际的股票,指数 t 表示时间区间。
如前所述,成交量中有几个典型的事实。其中一个显而易见的事实是,成交量是非负的,因为它衡量的是成交的股票数量。当没有交易时,这个数字为零,否则为正。另一个重要的典型事实是,在多个不同市场上观察到的日内 U 形曲线(参见Hmaied, D. M., Sioud, O. B., 和 Grar, A. (2006)以及Hussain, S. M. (2011)的综述)。
这意味着,交易活动通常在市场开盘后和闭盘前更为活跃,而在白天的其他时间则相对较少。有多种可能的解释可以说明这一现象,但其存在性是非常明确的。
注意
热心的读者可能对Kaastra, I. 和 Boyd, M. S. (1995)及Lux, T. 和 Kaizoji, T. (2004)感兴趣,这两者分别提出了使用月度和日度数据的成交量预测模型。Brownlees, C. T., Cipollini, F., 和 Gallo, G. M. (2011)则为日内数据建立了成交量预测模型,这与本章直接相关。我们的实证研究发现,以下部分详细介绍的模型(由Bialkowski, J., Darolles, S., 和 Le Fol, G. (2008)提出)提供了更为精准的预测,因此由于篇幅限制,本章仅详细阐述后者。
本章讨论的是股票成交量的日内预测。文献中有一些相关的模型,我们发现Bialkowski, J., Darolles, S., 和 Le Fol, G. (2008)提出的模型最为准确。以下部分简要总结了该模型,并提供了足够的细节以便后续理解其实现。
成交量预测模型
本节解释了Bialkowski, J., Darolles, S., 和 Le Fol, G. (2008)提出的日内成交量预测模型。
他们使用 CAC40 数据来测试其模型,包括截至 2004 年 9 月的指数中每只股票的成交量。交易被汇总成 20 分钟的时间段,每天得到 25 个观测值。
成交量被分解为两个加性组件。第一个是季节性组件(U 形),表示每只股票在平均日的预期成交量水平。由于每天与平均值略有不同,存在第二个动态组件,它显示特定日与平均值的预期偏差。
分解是使用Bai, J. (2003)的因子模型进行的。初始问题如下:

在这里,X (TxN)大小的矩阵包含了初始数据,F (Txr)是因子矩阵,Λ' (Nxr)是因子负载矩阵,e (TxN)是误差项。K表示公共项,T表示观测值的数量,N表示股票的数量,r表示因子的数量。
XX'矩阵的维度是(TxT)。在确定其特征值和特征向量后,Eig包含与r个最大特征值相关的特征向量。然后,估计的因子矩阵确定为:

估计的负载矩阵的转置计算为:

最终,估计的公共组件为:

由于该模型是加性模型,估计的动态组件简单地变为:

现在,估计的公共组件和动态组件都已获得,下一步是生成它们的预测。作者假设季节性(U 形)组件在 20 天的估计期内是恒定的(但在不同的股票之间有所不同),因此他们根据以下公式进行预测:

已知 25 是每天的时间段数(数据点),这意味着对于股票i,明天第一个时间段的预测值将是过去L天第一个时间段的平均值。
动态组件的预测有两种不同的方法。一种方法是拟合一个 AR(1)模型,模型指定如下:

另一种方法是拟合一个 SETAR 模型,模型指定如下:

这里,指示函数如下所示:

这意味着,如果前一个观测值没有超过模型中指定的
阈值,那么将使用一个 AR(1)模型进行预测;如果超过,则使用另一个 AR(1)模型。
在预测了季节性和动态组件之后,预测的成交量将是两者的总和:

请注意,我们以两种不同的方式预测动态组件;因此,依据添加到季节性组件预测中的方法不同,我们将得到两种不同的预测结果。
在 R 中的实现
在本节中,我们展示了如何在 R 中实现Bialkowski, J., Darolles, S., 和 Le Fol, G. (2008)的模型。我们涵盖了每个细节,从加载数据到估计模型参数并生成实际的预测。
数据
我们使用的数据包含了来自道琼斯工业平均指数的 10 只不同股票(下表提供了概述)。我们使用了 2011 年 06 月 01 日至 2011 年 06 月 29 日之间的 21 个交易日数据。纽约证券交易所和纳斯达克的交易时间为 09:30 至 16:00,交易是连续的。将数据聚合到 15 分钟的时间段后,我们每天获得 26 个观测值,总共获得26 * 21 = 546个观测值。
提示
我们将交易日分为 26 个时间段,而原文中定义为 25 个。这是因为不同市场的开盘时间存在差异,而我们使用的数据来自不同的市场。这个变化只会影响模型中的一个参数,但需要注意这个细节。
所有使用的股票在观察期间的每个时间段都有足够的流动性,能够保持正向的成交量。然而,需要注意的是,由于模型具有加性结构,某些时间段的零成交量不会造成任何困难。
以下表格来自来源kibot.com/:
| 股票代码 | 公司 | 行业 | 部门 | 交易所 | |
|---|---|---|---|---|---|
| 1 | AA | Alcoa 公司 | 铝业 | 基础材料 | 纽约证券交易所 |
| 2 | AIG | 美国国际集团公司 | 财产与意外保险 | 金融 | 纽约证券交易所 |
| 3 | AXP | 美国运通公司 | 信用服务 | 金融 | 纽约证券交易所 |
| 4 | BA | 波音公司 | 航空航天/国防产品与服务 | 工业品 | 纽约证券交易所 |
| 5 | BAC | 美国银行 | 区域 - 中大西洋地区银行 | 金融 | 纽约证券交易所 |
| 6 | C | 花旗集团 | 资金中心银行 | 金融 | 纽约证券交易所 |
| 7 | CAT | 卡特彼勒公司 | 农业和建筑机械 | 工业品 | 纽约证券交易所 |
| 8 | CSCO | 思科系统公司 | 网络与通信设备 | 技术 | 纳斯达克 |
| 9 | CVX | 雪佛龙公司 | 主要综合油气公司 | 基础材料 | 纽约证券交易所 |
| 10 | DD | 杜邦公司 | 化学品 - 主要多元化 | 基础材料 | 纽约证券交易所 |
数据集中的股票
在 546 次观测中,我们将使用前 520 次(20 天)作为估计期,最后 26 次(1 天)作为预测期。保持预测期的实际数据非常重要,这样我们可以评估预测的精度,并将其与实际结果进行比较。
作为数据的示例,请参见图 3.1,该图展示了 Alcoa 的前五天(130 次观测数据)。

图 3.1:Alcoa 营业额的前五天
尽管每天都有些许不同,但我们可以清楚地看到营业额图表中由五个 U 形曲线表示的五个不同的日子。
加载数据
我们将数据整理成了一个.csv文件,文件头包含股票代码。数据矩阵的维度为 546 x 10。以下代码加载数据并打印前五行和六列:
turnover_data <- read.table("turnover_data.csv", header = T, sep = ";")
format(turnover_data[1:5, 1:6],digits = 3)
数据矩阵的左上角部分的输出如下所示。由于我们的数据展示的是营业额值(以百分比形式),而不是交易量,因此每个值都低于 1。举例来说,在样本的前 15 分钟内,Alcoa 公司总流通股的 0.11%被交易(见方程式(1))。
AA AIG AXP BA BAC C
1 0.1101 0.0328 0.0340 0.0310 0.0984 0.0826
2 0.0502 0.0289 0.0205 0.0157 0.0635 0.0493
3 0.1157 0.0715 0.0461 0.0344 0.1027 0.1095
4 0.0440 0.1116 0.0229 0.0228 0.0613 0.0530
5 0.0514 0.0511 0.0202 0.0263 0.0720 0.0836
以下代码绘制了 Alcoa 营业额的第一天数据。图表显示在图 3.2中。
plot(turnover_data$AA[1:26], type = "l", main = "AA", xlab = "time", ylab="turnover")
我们可以识别出第一天的 U 形曲线,但此时我们需要稍微依靠我们的想象力。这是因为 U 形曲线是一种风格化的事实,仅在统计上被观察到。

图 3.2:Alcoa 营业额的第一天
因此,我们预计 U 形曲线在平均值上会更加明确。以下代码绘制了样本中 21 天的 Alcoa 营业额的平均值。为此,我们将数据矩阵的第一列转换为一个 26*21 的矩阵,并绘制行平均值。
AA_average <- matrix(turnover_data$AA, 26, 546/26)
plot(rowMeans(AA_average), type = "l", main = "AA" , xlab = "time", ylab = "turnover")
结果如图 3.3所示,U 形曲线非常清晰地绘制出来。

图 3.3:Alcoa 营业额的 21 天平均值
数据加载完毕后,我们可以开始实现模型。
季节性成分
第一步是确定季节性成分。如前所述,我们将使用前 520 个观测值进行估计。以下代码从数据框创建适当的样本矩阵:
n <- 520
m <- ncol(turnover_data)
sample <- as.matrix(turnover_data[1:n, ])
现在,我们可以开始Bai, J. (2003)的因子分解(见公式(2)到(6))。在创建了
矩阵(维度为 520 x 520)之后,我们找到了它的特征值和特征向量。
S <- sample %*% t(sample)
D <- eigen(S)$values
V <- eigen(S)$vectors
接下来,我们需要确定使用的因子数量(r)。以下代码按降序绘制特征值图:
plot(D, main = "Eigenvalues", xlab = "", ylab = "")
结果如图 3.4所示,其中第一个特征值明显主导了所有其他特征值。这意味着第一个特征向量解释的方差占据了大部分方差,因此我们选择在模型中使用单一因子 (
)。作为经验法则,我们可以使用比 1 大的特征值个数作为因子的数量,但这始终是一个主观的决定。

图 3.4: XX' 的特征值
使用与此最大特征值对应的特征向量,我们现在可以计算估计的因子矩阵(见公式(3))。
Eig <- V[, 1]
F <- sqrt(n) * Eig
然后,我们根据公式(4)计算估计的加载矩阵的转置,并根据公式(5)计算估计的共同(季节性)成分。最后,还计算了动态(特有)成分(见公式(6))。
Lambda <- F %*% sample / n
K <- F %*% Lambda
IC <- sample - K
动态成分将在接下来的两个小节中进行预测,但我们仍然需要在这里预测季节性成分。这将根据公式(7)完成。
K26 <- matrix(0, 26, m)
for (i in 1:m) {
tmp <- matrix(K[,i], 26, n/26)
K26[,i] <- rowMeans(tmp)
}
之前的代码计算了所有 26 个位置的 20 天平均值,每次处理一个股票,结果是一个 26 x 10 的矩阵,包含所有 10 只股票的单日季节性成分预测。
现在,我们剩下的是动态成分的预测,这将通过两种不同的方式完成:通过拟合 AR(1)模型和 SETAR 模型。
AR(1)估计和预测
在本小节中,我们将 AR(1)模型拟合到动态成分。我们需要为每只股票指定 10 个模型。以下代码执行参数估计:
library(forecast)
models <- lapply(1:m, function(i)
Arima(IC[, i], order = c(1, 0, 0), method = "CSS"))
coefs <- sapply(models, function(x) x$coef)
round(coefs, 4)
系数被收集在coefs变量中,并以四位小数的形式打印在以下输出中。系数不一定需要保存(保存模型即可),因为forecast包内置了forecast函数,我们将在以下示例中使用它:

每个股票的 AR 系数
提示
在 R 中估计 AR(1)模型有多种方法。除了之前提到的方法,适用于任何 ARIMA 模型外,下面的代码(仅使用 Alcoa 示例)通过使用不同的包来重现相同的结果,但该包只能处理 ARMA(而不是 ARIMA)模型。
library("tseries")arma_mod <- arma(IC[, 1], order = c(1, 0))
所以,下一步是使用之前估计的 AR(1)模型为第二天的 26 个时间段进行预测。以下代码将为我们执行此操作:
ARf <- sapply(1:m, function(i) forecast(models[[i]], h = 26)$mean)
为了获得完整的预测(包括季节性和动态成分),我们只需参考公式(11)。
AR_result <- K26+ARf
完整的预测结果现在存储在AR_result变量中。
SETAR 估计和预测
获得动态成分预测的第二种方法是通过 SETAR 模型。同样,我们需要为每只股票建立 10 个不同的模型。在 R 中也有一个 SETAR 估计的包,因此代码变得非常简单:
library(tsDyn)
setar_mod <- apply(IC,2,setar, 1);
setar_coefs <- sapply(setar_mod, FUN = coefficients)
round(setar_coefs, 4)
与 AR 模型不同,我们确实需要显式地保存系数用于预测,这一点也在之前的代码中完成。四位数字的四舍五入值将在以下输出中打印:

每只股票的 SETAR 系数
从上到下的五个参数如下(详见公式(9)):
-
截距(下行区间)。
-
AR 系数(下行区间)。
-
截距(上行区间)。
-
AR 系数(上行区间)。
-
阈值。
现在,我们需要做的就是使用我们刚刚描述的 SETAR 模型,预测接下来的 26 个时间段的动态成分。这可以通过以下代码实现:
SETARf <- matrix(0, 27, m)
SETARf[1,] <- sample[520,]
for (i in 2:27){
SETARf[i,] <-
(setar_coefs[1,]+SETARf[i-1,]*setar_coefs[2,])*
(SETARf[i-1,] <= setar_coefs[5,]) +
(setar_coefs[3,]+SETARf[i-1,]*setar_coefs[4,])*
(SETARf[i-1,] > setar_coefs[5,])
}
虽然我们希望为每只股票的 26 个时间段(即一天内的所有时间段)进行预测,但SETARf变量有 27 行,因为我们必须将最后已知的观察值存储在第一行,以便能够进行递归计算。此外,注意这里是逐行计算的,即我们同时为每只股票计算下一个预测值,只有在计算完后,我们才会进入下一个时间段。
最后,再次参考公式(11),完整的换手率预测如下:
SETAR_result = K26 + SETARf[2:27,]
完整的预测结果现在存储在SETAR_result变量中。
结果解释
我们已经根据过去 20 天的数据,得到了所有 10 只股票的第二天换手率预测。根据我们如何预测动态成分,每只股票会有两个不同的结果。
我们排除了数据集中的最后一天,以便能够将实际值与预测值进行比较。以下代码通过生成 10 个不同的图表来帮助我们完成此任务,每只股票一个,使用 AR(1)模型进行动态成分预测。输出显示在图 3.5中。
par(mfrow = c(2, 5))
for (i in 1:10) {matplot(cbind(AR_result[, i], turnover_data[521:546, i]), type = "l", main = colnames(turnover_data)[i], xlab = "", ylab = "", col = c("red", "black"))}
在每个图中,黑色虚线表示该特定股票的实际换手率,而红色实线表示预测的换手率。如前所述,实际的换手率可能会显著偏离 U 形的典型事实。

图 3.5:第二天的换手率预测与实际值,动态成分使用 AR(1)模型进行预测
我们可以得出结论,从视觉上看,预测结果相当精确。当实际结果呈现出更规则的 U 形时,预测结果能够更好地拟合(如 Alcoa、Caterpillar、Chevron 和 Du Pont De Nemours),但单次的大幅波动总是难以预测(例如 Chevron 的第五个观测值)。当实际结果变得异常不对称时,预测表现较差;也就是说,前几笔或最后几笔交易远大于其余部分(如 American Express、Bank of America 和 Citigroup),但即便如此,全天的其余部分仍能得到合理的拟合。
提示
这次,我们不对估计误差进行数值评估,因为我们首先需要一个基准来进行评估,更重要的是,因为我们只预测了单一天的数据;因此,结果无论如何都不会具有很强的稳健性。
我们可以使用与之前类似的代码来绘制基于 SETAR 的估计结果。输出如图 3.6所示。

图 3.6:下一天的交易量预测与实际结果,SETAR 用于动态组件
乍一看,结果与前一个案例非常相似,这是可以理解的,因为两者的季节性组件预测相同,显然季节性组件主导了预测,其余部分仅仅是个别偏差所致。基于 AR 和基于 SETAR 的预测差异在一天的开始时更为明显。
如果我们观察图 3.5和图 3.6中的一天的第一和最后一个数据点,我们可以发现一些股票(如 Alcoa、Bank of America、Citigroup、Caterpillar、Cisco 和 Du Pont De Nemours),在 SETAR 模型下,最后一个数据点(以及全天的大部分时间)预测结果相似,而第一点的预测值则明显更大。两种预测方法之间最显著的差异出现在 American International 和 Boeing 的股票上,SETAR 模型在全天的预测值普遍较高。
总结
在本章中,我们展示了一种基于 DJIA 指数数据的日内交易量预测模型及其在 R 中的实现。由于篇幅限制,我们从文献中选择了我们认为最准确的一个模型,用于预测股票交易量。该模型为了方便,使用了成交额而非交易量,并将季节性分量(U 形)和动态分量分开,分别进行预测。动态分量使用两种不同方式进行预测,分别拟合 AR(1)模型和 SETAR 模型。与原文相似,我们并未断言哪种模型更好,而是直观地展示结果并认为其准确度是可以接受的。原文通过有力的证据证明该模型优于精心选择的基准,但我们将此留给读者去检验,因为我们仅使用了一个短期数据集进行说明,无法获得稳健的结果。
参考文献
-
Bai, J. (2003):大维度因子模型的推断理论。《计量经济学》,71:135-171。
-
Bialkowski, J., Darolles, S., 和 Le Fol, G. (2008):改进 VWAP 策略:一种动态交易量方法。《银行与金融学报》,32:1709-1722。
-
Brownlees, C. T., Cipollini, F., 和 Gallo, G. M. (2011):日内交易量建模与预测,用于算法交易。《金融计量经济学杂志》,9:489-518。
-
Hmaied, D. M., Sioud, O. B., 和 Grar, A. (2006):突尼斯证券交易所买卖价差、交易量和波动性的日内与周内模式。《银行与市场》,84:35-44。
-
Hussain, S. M. (2011):买卖价差、交易量和收益波动的日内行为:来自 DAX30 的证据。《国际经济与金融杂志》,3:23-34。
-
Kaastra, I. 和 Boyd, M. S. (1995):利用神经网络预测期货交易量。《期货市场杂志》,第 15 卷,第 8 期:953-970。
-
Lux, T. 和 Kaizoji, T. (2004):预测东京股市的波动性和交易量:长期记忆模型的优势。经济学工作论文,基尔大学经济学系。
第四章:大数据——高级分析
在本章中,我们将处理高性能金融分析和数据管理的最大挑战之一;即如何在 R 中高效且无误地处理大数据集。
我们的主要目标是提供一个关于如何在 R 中访问和管理大数据集的实际介绍。本章并不专注于任何特定的金融理论,而是旨在为研究人员和专业人士提供实际的操作示例,教他们如何在 R 环境中实现计算密集型的分析和模型,利用大数据集。
在本章的第一部分,我们解释了如何直接访问多个开放源的数据。R 提供了各种工具和选项,可以将数据加载到 R 环境中,而无需任何事先的数据管理要求。本章的这一部分将通过实际示例指导你如何使用 Quandl 和 qualtmod 包来访问数据。这里展示的示例将为本书的其他章节提供有用的参考。在本章的第二部分,我们将强调 R 在处理大数据时的局限性,并展示如何在大内存和 ff 包的帮助下将大量数据加载到 R 中的实际示例。我们还将展示如何使用大数据集执行基本的统计分析,如 K-means 聚类和线性回归。
从开放源获取数据
从开放源提取金融时间序列或横截面数据是任何学术分析中的挑战之一。虽然几年前,公开数据对金融分析的可访问性非常有限,但近年来,越来越多的开放获取数据库已可用,为各个领域的定量分析师提供了巨大的机会。
在本节中,我们将介绍 Quandl 和 quantmod 包,这两个特定工具可以无缝地访问和加载 R 环境中的金融数据。我们将通过两个示例,展示这些工具如何帮助金融分析师直接从数据源整合数据,无需事先进行数据管理。
Quandl 是一个开源网站,提供金融时间序列数据,索引来自 500 个来源的数百万个金融、经济和社会数据集。Quandl 包与 Quandl API 直接交互,提供多种格式的数据,可供 R 使用。除了下载数据,用户还可以上传和编辑自己的数据,并且可以直接从 R 中在任何数据源中进行搜索。
在第一个简单的示例中,我们将展示如何使用 Quandl 轻松地获取并绘制汇率时间序列。在访问 Quandl 上的任何数据之前,我们需要使用以下命令安装并加载 Quandl 包:
install.packages("Quandl")
library(Quandl)
library(xts)
我们将下载 2005 年 1 月 1 日至 2014 年 5 月 30 日之间 EUR 汇率下的美元、瑞士法郎、英镑、日元、俄罗斯卢布、加元和澳元的汇率。以下命令指定了如何选择特定的时间序列和分析周期:
currencies <- c( "USD", "CHF", "GBP", "JPY", "RUB", "CAD", "AUD")
currencies <- paste("CURRFX/EUR", currencies, sep = "")
currency_ts <- lapply(as.list(currencies), Quandl, start_date="2005-01-01",end_date="2013-06-07", type="xts")
下一步,我们将使用matplot()函数可视化四个选定汇率(美元、英镑、加元和澳元)的汇率变化:
Q <- cbind(
currency_ts[[1]]$Rate,currency_ts[[3]]$Rate,currency_ts[[6]]$Rate,currency_ts[[7]]$Rate)
matplot(Q, type = "l", xlab = "", ylab = "", main = "USD, GBP, CAD, AUD", xaxt = 'n', yaxt = 'n')
ticks = axTicksByTime(currency_ts[[1]])
abline(v = ticks,h = seq(min(Q), max(Q), length = 5), col = "grey", lty = 4)
axis(1, at = ticks, labels = names(ticks))
axis(2, at = seq(min(Q), max(Q), length = 5), labels = round(seq(min(Q), max(Q), length = 5), 1))
legend("topright", legend = c("USD/EUR", "GBP/EUR", "CAD/EUR", "AUD/EUR"), col = 1:4, pch = 19)
以下截图显示了前述代码的输出:

图 4.1:美元、英镑、加元和澳元的汇率图
在第二个示例中,我们将演示如何使用quantmod包来访问、加载并调查开放来源的数据。quantmod包的一个巨大优势是它可以与多种数据源兼容,并直接从 Yahoo! Finance、Google Finance、联邦储备经济数据(FRED)或 Oanda 网站获取数据。
在本示例中,我们将访问宝马股票价格信息,并分析这家汽车制造公司自 2010 年以来的表现:
library(quantmod)
我们将从网络上获取宝马股票的价格数据,数据来源于 Yahoo! Finance,涵盖给定的时间段。quantmod包提供了一个易于使用的函数getSymbols(),用于从本地或远程来源下载数据。作为该函数的第一个参数,我们需要通过指定符号的名称来定义字符向量。第二个参数指定对象创建的环境:
bmw_stock<- new.env()
getSymbols("BMW.DE", env = bmw_stock, src = "yahoo", from = as.Date("2010-01-01"), to = as.Date("2013-12-31"))
下一步,我们需要将bmw_stock环境中的BMW.DE变量加载到一个向量中。借助head()函数,我们还可以显示数据的前六行:
BMW<-bmw_stock$BMW.DE
head(BMW)
BMW.DE.Open BMW.DE.High BMW.DE.Low BMW.DE.Close BMW.DE.Volume
2010-01-04 31.82 32.46 31.82 32.05 1808100
2010-01-05 31.96 32.41 31.78 32.31 1564100
2010-01-06 32.45 33.04 32.36 32.81 2218600
2010-01-07 32.65 33.20 32.38 33.10 2026100
2010-01-08 33.33 33.43 32.51 32.65 1925800
2010-01-11 32.99 33.05 32.11 32.17 2157800
BMW.DE.Adjusted
2010-01-04 29.91
2010-01-05 30.16
2010-01-06 30.62
2010-01-07 30.89
2010-01-08 30.48
2010-01-11 30.02
quantmod包还具备财务图表功能。chartSeries()函数不仅允许我们可视化图表,还能与图表进行交互。借助其扩展功能,我们还可以将多种技术和交易指标添加到基本图表中;这对于技术分析来说是非常有用的功能。
在我们的示例中,我们将使用addBBands()命令添加布林带,并使用addMACD()命令添加 MACD 趋势跟踪动量指标,以获得有关股票价格变化的更多见解:
chartSeries(BMW,multi.col=TRUE,theme="white")
addMACD()
addBBands()
以下截图显示了前述代码的输出:

图 4.2:宝马股票价格变化与技术指标
最后,我们将计算宝马股票在给定期间的日常对数收益。同时,我们还希望调查这些收益是否符合正态分布。下图展示了宝马股票的日常对数收益,并以正常 Q-Q 图的形式呈现:
BMW_return <-
log(BMW$BMW.DE.Close/BMW$BMW.DE.Open)
qqnorm(BMW_return, main = "Normal Q-Q Plot of BMW daily log return",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles", plot.it = TRUE, datax = FALSE
)
qqline(BMW_return, col="red")
以下截图显示了前述代码的输出。它以正常 Q-Q 图的形式展示了宝马股票的日常对数收益:

图 4.3:宝马股票日常收益的 Q-Q 图
R 中的大数据分析简介
大数据指的是当数据的体积、速度或种类超过我们的计算能力以处理、存储和分析它们时的情况。大数据分析不仅需要处理庞大的数据集,还需要应对计算密集型分析、模拟和具有大量参数的模型。
利用大数据样本可以在量化金融领域提供显著优势;我们可以放宽线性和正态性假设,生成更好的预测模型,或者识别低频事件。
然而,大数据集的分析提出了两个挑战。首先,大多数定量分析工具处理庞大数据的能力有限,即使是简单的计算和数据管理任务也可能变得难以执行。其次,即使没有容量限制,对大数据集的计算也可能极其耗时。
尽管 R 是一个功能强大且稳定的程序,拥有丰富的统计算法和能力,但它的一个最大缺点是其在处理大数据集时的扩展能力有限。其原因在于 R 要求其操作的数据首先被加载到内存中。然而,操作系统和系统架构只能访问大约 4 GB 的内存。如果数据集超过计算机的 RAM 阈值,它就几乎不可能在标准计算机和标准算法上进行处理。有时,即使是较小的数据集也可能在 R 中引发严重的计算问题,因为 R 必须存储分析过程中创建的最大对象。
然而,R 有一些包可以弥补这一差距,为大数据分析提供高效支持。在本节中,我们将介绍两个特别的包,它们是创建、存储、访问和操作海量数据的有用工具。
首先,我们将介绍bigmemory包,这是一个广泛用于大规模统计计算的选项。该包及其姐妹包(biganalytics、bigtabulate和bigalgebra)解决了处理和分析海量数据集时的两个挑战:数据管理和统计分析。这些工具能够实现超出 R 运行环境的巨大矩阵,并支持它们的操作和探索。
bigmemory包的替代方案是ff包。这个包允许 R 用户处理大向量和矩阵,并同时处理多个大型数据文件。ff对象的一个巨大优势是,它们表现得像普通的 R 向量。然而,数据并不存储在内存中;它驻留在磁盘上。
在本节中,我们将展示这些包如何帮助 R 用户克服 R 的局限性,处理非常大的数据集。尽管我们在这里使用的数据集在规模上相对简单,但它们有效地展示了大数据包的强大功能。
在大数据上的 K-means 聚类
数据框和矩阵是 R 中易于使用的对象,对于大小适中的数据集,常见的操作可以迅速执行。然而,当用户需要处理更大的数据集时,可能会出现问题。在本节中,我们将说明如何使用 bigmemory 和 biganalytics 包来解决数据框或数据表无法处理的超大数据集问题。
注意
截至本章撰写时,bigmemory、biganalytics 和 biglm 包的最新更新在 Windows 上不可用。这里展示的示例假设 R 版本 2.15.3 是 Windows 上当前的先进版本。
在以下示例中,我们将对大数据集执行 K-means 聚类分析。为了便于说明,我们将使用美国交通统计局的航空公司出发地和目的地调查数据集。该数据集包含超过 300 万个国内航班的汇总特征,包括行程票价、乘客人数、起始机场、往返指示符和飞行里程,数据以csv格式呈现。
加载大矩阵
从 csv 文件中读取数据集可以通过 read.csv() 函数轻松实现。然而,当我们需要处理更大的数据集时,任何文件的读取时间都可能变得相当长。然而,通过一些细致的选项,R 的数据加载功能可以得到显著提升。
一个选项是在加载数据到 R 时,通过 colClasses = argument 指定正确的数据类型;这将导致外部数据的转换速度更快。此外,指定不需要进行分析的列为 NULL 可以显著减少加载数据时所消耗的时间和内存。
然而,如果数据集达到了计算机的 RAM 阈值,我们需要采用更节省内存的数据加载选项。在以下示例中,我们将展示如何使用 bigmemory 包来处理此任务。
首先,我们将安装并加载所需的 bigmemory 和 biganalytics 包,以执行大数据的 K-means 聚类分析:
install.packages("bigmemory")
install.packages("biganalytics")
library(bigmemory)
library(biganalytics)
我们使用了read.big.matrix函数将从本地系统下载的数据集导入到 R 中。该函数将数据处理为类似矩阵的对象,而不是数据框,我们需要通过 as.matrix 函数将其转换为矩阵:
x<-read.big.matrix( "FlightTicketData.csv", type='integer', header=TRUE, backingfile="data.bin",descriptorfile="data.desc")
xm<-as.matrix(x)
nrow(x)
[1] 3156925
大数据 K-means 聚类分析
在 R 中,处理大数据 K-means 聚类的函数格式是bigkmeans (x, centers),其中 x 是一个数值数据集(大数据矩阵对象),centers 是提取的聚类数。该函数返回聚类成员、中心点、聚类内平方和(WCSS)和聚类大小。bigkmeans()函数可以处理常规 R 矩阵对象或 big.matrix 对象。
我们将根据每个聚类所解释的方差百分比来确定聚类数量;因此,我们将绘制聚类所解释的方差百分比与聚类数量之间的关系:
res_bigkmeans <- lapply(1:10, function(i) {
bigkmeans(x, centers=i,iter.max=50,nstart=1)
})
lapply(res_bigkmeans, function(x) x$withinss)
var <- sapply(res_bigkmeans, function(x) sum(x$withinss))
plot(1:10, var, type = "b", xlab = "Number of clusters", ylab = "Percentage of variance explained")
以下截图显示了前述代码的输出:

图 4.4:绘制聚类内平方和与提取的聚类数量的关系
从 1 到 3 个聚类的急剧下降(之后几乎没有下降)表明了三聚类解决方案。因此,我们将执行具有三个聚类的大数据 K-means 聚类分析:
res_big<-bigkmeans(x, centers=3,iter.max=50,nstart=1)
res_big
K-means clustering with 3 clusters of sizes 919959, 1116275, 1120691
Cluster means:
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,] 2.663235 12850.78 1285081 32097.61 0.6323662 0.03459393 2.084982 2305.836
[2,] 2.744241 14513.19 1451322 32768.11 0.6545699 0.02660276 1.974971 2390.292
[3,] 2.757645 11040.08 1104010 30910.66 0.6813850 0.03740460 1.989817 2211.801
[,9]
[1,] 1.929160
[2,] 1.930394
[3,] 1.949151
Clustering vector:
[1] 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3
[37] 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 2 2 2 2 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1
[73] 1 2 2 2 2 2 2 3 3 3 1 2 2 3 3 3 1 1 1 1 1 1 2 2
Within cluster sum of squares by cluster:
[1] 2.010160e+15 2.466224e+15 2.183142e+15
Available components:
[1] "cluster" "centers" "withinss" "size"
bigkmeans()函数也可以与普通矩阵对象一起使用,比kmeans()函数提供更快的计算速度。
为了验证这个假设,我们将测量不同数据集大小下,bigkmeans()和kmeans()函数的平均执行时间:
size<-round(seq(10,2500000,length=20))
nsize<-length(size)
calc.time <- matrix(NA, nrow=nsize, ncol=2)
for (i in 1:nsize) {
size.i<-size[i]
xm.i<-xm[1:size.i,]
vec1=rep(0,10)
vec2=rep(0,10)
for (j in 1:10) {
vec1[j]<-system.time(kmeans(xm.i,centers=3,iter.max=50,nstart=1))[3]
vec2[j]<-system.time(bigkmeans(xm.i,centers=3,iter.max=50,nstart=1))[3]
}
calc.time[i,1]<-mean(vec1)
calc.time[i,2]<-mean(vec2)
}
以下截图显示了前述代码的输出:

图 4.5:根据数据集大小,kmeans()和 bigkmeans()函数的执行时间
计算这两个函数的平均执行时间需要相当长的时间。然而,从前述图表可以看出,bigkmeans()在处理较大数据集时比kmeans()函数更高效,从而减少了 R 在分析中的计算时间。
大数据线性回归分析
本节将展示如何借助ff包直接从 URL 加载大数据集,以及如何与biglm包交互,为大于内存的数据集拟合一个通用的线性回归模型。biglm包可以有效地处理超出计算机内存的数据集,因为它将数据分块加载到内存中。它处理最后一个数据块并更新模型所需的充分统计数据,然后丢弃该数据块并加载下一个。这个过程一直重复,直到所有数据都被处理完。
以下示例考察了失业补偿金额作为几个社会经济数据的线性函数。
加载大数据
为了进行大数据线性回归分析,我们首先需要安装并加载ff包,使用该包在 R 中打开大文件;以及biglm包,用来拟合我们的数据上的线性回归模型:
install.packages("ff")
install.packages("biglm")
library(ff)
library(biglm)
对于大数据线性回归分析,我们使用了美国政府机构国税局(IRS)提供的个人所得税邮政编码数据。邮政编码级别的数据展示了按州、邮政编码和收入类别分类的选定收入和税务项目。我们使用了该数据库的 2012 年数据;该数据库数据量适中,但足以展示大数据包的功能。
我们将直接通过以下命令从 URL 加载所需的数据集到 R 中:
download.file("http://www.irs.gov/file_source/pub/irs-soi/12zpallagi.csv","soi.csv")
下载数据后,我们将使用read.table.ffdf函数将文件读取为ffdf对象,该对象由ff包支持。read.table.ffdf函数的工作方式与read.table函数非常相似。它还提供了便捷的选项来读取其他文件格式,例如csv:
x <- read.csv.ffdf(file="soi.csv",header=TRUE)
在将数据集转换为ff对象后,我们将加载biglm包以执行线性回归分析。
利用包含大约 167,000 个观测值和 77 个不同变量的数据集,我们将调查是否可以通过总薪资和工资金额(A00200)、按收入类别划分的居民数量(AGI_STUB)、被抚养人数(NUMDEP 变量)以及已婚人数(MARS2)来解释某一地区的失业补偿金额(定义为变量A02300)。
在大型数据集上拟合线性回归模型
对于线性回归分析,我们将使用biglm函数;因此,在指定模型之前,我们需要加载该包:
require(biglm)
作为下一步,我们将定义公式并在我们的数据上拟合模型。通过 summary 函数,我们可以获得拟合模型的系数及其显著性水平。由于模型输出不包括 R 平方值,我们需要使用单独的命令加载模型的 R 平方值:
mymodel<-biglm(A02300 ~ A00200+AGI_STUB+NUMDEP+MARS2,data=x)
summary(mymodel)
Large data regression model: biglm(A02300 ~ A00200 + AGI_STUB + NUMDEP + MARS2, data = x)
Sample size = 166904
Coef (95% CI) SE p
(Intercept) 131.9412 44.3847 219.4977 43.7782 0.0026
A00200 -0.0019 -0.0019 -0.0018 0.0000 0.0000
AGI_STUB -40.1597 -62.6401 -17.6794 11.2402 0.0004
NUMDEP 0.9270 0.9235 0.9306 0.0018 0.0000
MARS2 -0.1451 -0.1574 -0.1327 0.0062 0.0000
A00200 -0.0019 -0.0019 -0.0018 0.0000 0.0000
summary(mymodel)$rsq
[1] 0.8609021
我们可以从回归模型的系数输出中得出结论,所有变量对模型都有显著贡献。独立变量解释了 86.09%的失业补偿总方差,表明该模型拟合良好。
总结
在本章中,我们应用 R 来访问开放源数据,并对大型数据集进行各种分析。这里展示的示例旨在为处理大量数据的经验研究人员提供实用指南。
首先,我们介绍了开放源数据整合的实用方法。R 具有强大的选项,可以直接访问金融分析所需的数据,无需任何先前的数据管理要求。其次,我们讨论了如何在 R 环境中处理大数据。尽管 R 在处理大型数据集和执行计算密集型分析与仿真方面有基本的局限性,但我们介绍了一些特定的工具和包来弥补这一差距。我们展示了两个示例,说明如何在大数据上执行 K 均值聚类以及如何拟合线性回归模型。这是本书第一部分的最后一章。接下来,我们将探讨外汇衍生品。
参考文献
-
Adler, D., Nenadic, O., Zucchini, W., Gläser, C. (2007):The ff 包:使用二进制平面文件的内存映射页面在 R 中处理大数据集
-
Enea, M. (2009): 使用 R 在大数据集上拟合线性模型和广义线性模型。短篇论文集,《大数据集分析的统计方法》会议,意大利统计学会,基耶蒂-佩斯卡拉,2009 年 9 月 23-25 日,411-414。
-
Kane, M., Emerson, JW., Weston (2010): 大记忆项目,耶鲁大学
-
Kane, M., Emerson, JW., Weston, S. (2013): 处理海量数据的可扩展计算策略。统计软件杂志,第 55 卷,第 14 期
-
Lumley, T. (2009) biglm: 有界内存线性和广义线性模型。R 包版本 0.7
第五章:外汇衍生品
FX 衍生品(或外汇衍生品)是金融衍生产品,其支付结构取决于两种(或更多)货币的汇率。像一般衍生品一样,FX 衍生品可以分为三大类:期货、掉期和期权。在本章中,我们将仅讨论期权类型的衍生品。我们将从基础的 Black-Scholes 模型的简单推广开始,展示如何对简单的欧式看涨或看跌货币期权进行定价。之后,我们将讨论外汇期权和 Quanto 期权的定价。
在本章中,我们假设你对衍生品定价有一些基本了解,特别是对 Black-Scholes 模型和风险中性定价方法有所掌握。偶尔,我们会提到一些定量金融中常用的数学关系(例如伊藤引理或吉尔萨诺夫定理),但对这些定理的深刻理解对于本章并非必需。然而,那些对该主题的纯数学背景感兴趣的人可以参考Medvegyev (2007)。
术语和符号
由于我们将处理外汇汇率,因此澄清一些相关术语非常重要。通常,我们会用 S 来表示即期外汇汇率,它衡量一种货币(称为基础货币)与另一种货币(称为计价货币)的价格关系。换句话说,1 单位基础货币等于 S 单位的计价货币。理解如何读取外汇市场报价也非常重要。外汇对的报价由两种货币的缩写表示:基础货币的三字母代码,后跟计价货币的三字母代码。例如,EURUSD=1.25 意味着 1 欧元等于 1.25 美元。这等同于报价 USDEUR=0.8,意味着 1 美元等于 0.8 欧元。通常,哪种货币作为基础货币在某个外汇对中,取决于历史市场惯例。
在第四章大数据 - 高级分析中,我们已经学习了如何从互联网下载货币汇率,因此我们可以利用所学的内容,查看实际数据。
这段简短的代码绘制了 EURUSD 和 USDEUR 汇率在同一图表窗口中的显示:
library(Quandl)
library(xts)
EURUSD <- Quandl("QUANDL/EURUSD",
start_date="2014-01-01",end_date="2014-07-01", type="xts")
USDEUR <- Quandl("QUANDL/USDEUR",
start_date="2014-01-01",end_date="2014-07-01", type="xts")
dev.new(width = 15, height = 8)
par(mfrow = c(1, 2))
plot(USDEUR)
plot(EURUSD)
在这里,我们可以看到以下图片中的结果:

我们还可以查看数据的前几行:
USDEUR[1:5,]
Rate High (est) Low (est)
2014-01-01 0.725711 0.73392 0.71760
2014-01-02 0.725238 0.73332 0.71725
2014-01-03 0.727714 0.73661 0.71892
2014-01-06 0.733192 0.00000 0.00000
2014-01-07 0.735418 0.00000 0.00000
EURUSD[1:5,]
Rate High (est) Low (est)
2014-01-01 1.37791 0.0000 0.0000
2014-01-02 1.37876 1.3949 1.3628
2014-01-03 1.37434 0.0000 0.0000
2014-01-06 1.36346 1.3799 1.3473
2014-01-07 1.35990 1.3753 1.3447
在这里,我们需要谈一下符号问题。到目前为止,我们将外汇汇率表示为S。然而,在衍生品定价中,基础资产的价格通常表示为S,无论它是股票还是货币。另一方面,外汇汇率通常表示为X,有时也表示为E(两者都来源于“exchange”一词)。此外,期权的执行价格或行权价格也常用X或E表示。现在,作为读者,您可能已经意识到,在本章中使用一致的符号系统是多么具有挑战性,因为基础资产可能是股票或货币,而且股票价格、外汇汇率和行权价格可能同时出现。我们决定尽可能采用 R 函数的符号,因此在本章中,我们将遵循以下符号:
-
基础资产的价格将始终是S,但如果它不一定是货币,我们将使用数字或字母指数,如S[1]或S[A]。
-
行权价格将始终是X。
-
期望值算符将用E表示。
我们强烈建议在阅读关于此主题的其他文献时要小心,因为他们的符号可能与我们的不同。
货币期权
欧式货币期权赋予持有者在特定日期(到期日,T)以预定汇率(行权价格或执行价格,X)买入(看涨期权)或卖出(看跌期权)货币的权利。这些金融资产也被称为外汇期权(或 FX 期权),但为了避免与“交换期权”这一术语混淆,我们更倾向于使用“货币期权”这一术语。
原始 Black-Scholes 模型(Black 和 Sholes,1973,另见Merton,1973)的基本假设是基础资产是没有红利的股票。更一般地说,模型的结果只有在基础资产不支付任何形式的收益,也不产生任何形式的成本时才成立。然而,这一假设可以轻松放宽,Black-Scholes 公式的扩展版本同样适用于货币期权,同时模型的所有逻辑和推理保持不变。
欧式货币看涨期权的闭式公式(c[0])如下:

在前面的公式中,d[1]和 d[2]的值如下:
和
。
在前面的公式中,S[0]是现货外汇汇率(基准货币的一单位价格,用变量货币表示),X是行权价格,T是期权到期时间(以年为单位),σ是外汇汇率的波动率,r和q分别是变量货币和基准货币的无风险对数回报,N表示标准正态分布的累积分布函数。从认购-认沽平价公式可以看出,具有相同参数的欧洲货币认沽期权(p[0])的价格如下:

Black-Scholes 公式及其他期权定价模型可以在fOptions包中找到。我们可以使用BlackScholesOption或GBSOption函数,它们实际上是一样的,后者是前者的简写别名。
BlackScholesOption(TypeFlag, S, X, Time, r, b, sigma,...)
在这里,TypeFlag是字符c(表示认购期权)或p(表示认沽期权)。S是当前价格,sigma是标的资产的波动率。X是行权价格,Time是到期时间。
另外两个参数有点棘手,因为r和b是无风险利率,但在使用原始 BS 模型定价股票期权时,第二个参数是没有意义的。这意味着我们必须将b = r来得到 BS 股票期权模型,并将b = r-q来得到货币期权模型或具有连续股息收益的股票期权模型。该函数的其他参数是可选的,我们不需要它们。
为了看它是如何运作的,假设我们有一个到期时间为五年的欧元期权,行权价格为 0.7。美元的无风险利率是r = 3%,欧元的无风险利率是q = 2%。1 美元当前等于 0.7450 欧元,因此这是标的资产的现货价格。假设欧元的波动率为 20%。如果我们使用给定参数调用BlackSholesOption函数,我们将得到以下结果:
BlackScholesOption ("c", 0.7450, 0.7, 5, 0.03, 0.01, 0.2)
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "c", S = 0.745, X = 0.7, Time = 5, r = 0.03,
b = 0.01, sigma = 0.2)
Parameters:
Value:
TypeFlag c
S 0.745
X 0.7
Time 5
r 0.03
b 0.01
sigma 0.2
Option Price:
0.152222
Description:
Thu Aug 07 20:13:28 2014
我们还可以查看认沽期权的价格:
BlackScholesOption("p", 0.7450, 0.7, 5, 0.03, 0.01, 0.2)
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "p", S = 0.745, X = 0.7, Time = 5, r = 0.03,
b = 0.01, sigma = 0.2)
Parameters:
Value:
TypeFlag p
S 0.745
X 0.7
Time 5
r 0.03
b 0.01
sigma 0.2
Option Price:
0.08061367
Description:
Thu Aug 07 20:15:11 2014
然后,我们还可以检查与认购-认沽平价的一致性,对于货币期权,其形式如下:
c - p = S*exp(-r*T)–X*exp(-q*T)
替换数据后,在左侧我们得到:
c - p = 0.152222 - 0.08061367 = 0.07160833,
在右侧,我们得到:
0.745*exp(-0.02*5)-0.7*exp(-0.03*5) = 0.07160829.
提示
期权价格被四舍五入到八位数字,因此会有略微的差异。
需要特别提到的是,定价货币期权等同于定价任何具有连续收益的标的资产的期权。例如,如果标的是具有每年股息收益q的股票或股票指数,那么定价公式与前面提到的相同。
交换期权
交换期权赋予持有者在到期时将一种风险资产交换为另一种风险资产的权利。很容易看出,简单期权是交换期权的一种特殊形式,其中一个风险资产是一个固定金额的钱(行权价格)。
交换期权的定价公式最早由Margrabe, 1978推导出来。模型假设、定价原则和最终公式与 Black、Scholes 和 Merton 的定价方法非常相似(更准确地说,是它们的推广)。现在,我们将展示如何确定交换期权的价值。
设定在时间t时,两个风险资产的现货价格分别为S[1t]和S[2t]。我们假设在风险中性概率测度(Q)下,这些价格遵循几何布朗运动,漂移项等于无风险利率(r),如图所示!交换期权 和
。
这里,W[1] 和 W[2] 是Q下的标准维纳过程,相关系数为ρ。你可能会注意到,这里资产没有收益(例如,不支付股息的股票)。众所周知(并且可以通过伊藤引理轻松看出),之前提到的随机微分方程的解是
和
(1)
我们假设你已经熟悉一维随机过程的基础知识。然而,在交换期权的情况下,我们有一个二维维纳过程,因此说明这种情况如何表现是很有用的。
二维维纳过程
2D 维纳过程就像是一个二维的随机游走,并且是连续时间的。我们可以通过几行代码轻松生成这样的过程,当坐标是独立的维纳过程时(我们不需要担心对过程进行缩放,因为它的外观是一样的)。
D2_Wiener <- function() {
dev.new(width = 10, height = 4)
par(mfrow = c(1, 3), oma = c(0, 0, 2, 0))
for(i in 1:3) {
W1 <- cumsum(rnorm(100000))
W2 <- cumsum(rnorm(100000))
plot(W1,W2, type= "l", ylab = "", xlab = "")
}
mtext("2-dimensional Wiener-processes with no correlation",
outer = TRUE, cex = 1.5, line = -1)
}
如果我们调用这个函数,输出结果大致如下:
D2_Wiener()
在这里,我们可以在下图中看到结果:

维纳过程之间的相关性会显著改变图像。在正相关的情况下,两个维纳过程看起来像是朝同一个方向移动;而在负相关的情况下,它们看起来像是朝相反方向移动。
我们可以修改我们的函数以获得相关的维纳过程。很容易看出,以下代码完成了这个任务:
Correlated_Wiener <- function(cor) {
dev.new(width = 10, height = 4)
par(mfrow = c(1, 3), oma = c(0, 0, 2, 0))
for(i in 1:3) {
W1 <- cumsum(rnorm(100000))
W2 <- cumsum(rnorm(100000))
W3 <- cor * W1 + sqrt(1 - cor²) * W2
plot(W1, W3, type= "l", ylab = "", xlab = "")
}
mtext(paste("2-dimensional Wiener-processes (",cor," correlation)",
sep = ""), outer = TRUE, cex = 1.5, line = -1)
}
结果依赖于生成的随机数,但大致是这样的:
Correlated_Wiener(0.6)
在这里,我们可以在下图中看到结果:

在之前的例子中,我们将相关系数设置为 0.6。现在,让我们看看当相关系数为-0.7 时会发生什么:
Correlated_Wiener(-0.7)
在这里,我们可以在下图中看到结果:

我们可以清楚地看到,不同相关性的过程之间的差异。现在,让我们将注意力重新集中在交换期权上。
马格拉比公式
交换期权到期时的H[T]收益由
定义。根据基本的风险中性定价原则,这一收益的价值(或等价地,交换期权的价格,用π(H[T])表示)如下所示:



在方程(2)中,S[t](没有 1 或 2 的编号)被定义为S[1t]/S[2t]的商。换句话说,S是S[1]相对于S[2]的价格。如果这两个风险资产是两种货币,那么S就是外汇汇率,这也是我们使用这种符号的原因。
为了计算前面提到的期望值,我们需要引入一个新的度量(R),由以下的 Radon-Nikodym 导数定义:

这里,前面方程右侧的内容来自于方程(1)中S[2]的表达式。
然后,交换期权的价格将采取以下形式:


现在,我们需要确定S在R下遵循的过程。根据 Girsanov 定理,我们知道
和
是R下的 Wiener 过程,且它们的相关性仍然是ρ。我们引入以下两个符号:


根据 Lévy 的表征,我们知道W在R下是一个 Wiener 过程。现在我们可以确定S的方程:






这意味着,在R下,S是一个几何布朗运动,漂移为零,即
。
现在,如果你还记得,在方程(3)中,我们有关于交换期权价格的以下方程:

使用这个S的关系,右侧的期望值是一个简单的看涨期权的价值,其标的资产是S,r等于 0,X等于 1。我们简化地将这个看涨期权的价格记为c[0]。然后
。
在这里,c[0] 可能通过基本的 Black-Scholes 公式来确定,替换我们刚刚讨论的参数:


因此
其中
和
之前提到的π(H[T])公式,即交换期权的定价公式,被称为马格拉布公式。如果适用,连续的股息收益率可以像在 Black-Scholes 公式中一样简单地插入该公式中。在不重复计算的情况下,我们仅提供该情况下的结果。
因此,假设要交换的风险资产具有正的连续股息收益率,分别表示为δ[1]和δ[2]。在这种情况下,它们的价格过程在Q测度下如下所示:
和 
在这种情况下,马格拉布公式将呈现以下形式:

在这里,
和
。
R 中的应用
R 中没有内建的马格拉布公式函数。然而,理解其背后的复杂理论比实现结果更为困难。这里,我们只用几行代码展示了Margrabe函数,它基于以下代码中的参数计算交换期权的价格:
Margrabe <- function(S1, S2, sigma1, sigma2, Time, rho, delta1 = 0,
delta2 = 0) {
sigma <- sqrt(sigma1² + sigma2² - 2 * sigma1 * sigma2 * rho)
d1 <- ( log(S1/S2) + ( delta2-delta1 + sigma²/2 ) * Time ) /
(sigma*sqrt(Time))
d2 <- ( log(S1/S2) + ( delta2-delta1 - sigma²/2 ) * Time ) /
(sigma*sqrt(Time))
M <- S1*exp(-delta1*Time)*pnorm(d1) - S2*exp(-delta2*Time)*pnorm(d2)
return(M)
}
这是该函数的核心部分。如果我们要求更高或希望开发一个用户友好的应用程序,我们需要捕捉可能的错误和异常。例如,我们应该包括如下内容:
if min(S1, S2) <= 0) stop("prices must be positive")
当波动率为负时,执行也应该停止,但用户体验和相关软件设计超出了本书的范围。我们可以使用此函数并提供有效的参数,来看一个其工作原理的示例。假设我们有两个没有分红的风险资产,一个价格为 100 美元,波动率为 20%,另一个价格为 120 美元,波动率为 30%,且到期时间为两年。首先,让相关性为 15%。
我们只需使用给定的参数调用Margrabe函数:
Margrabe(100, 120, .2, .3, 2, .15)
[1] 12.05247
结果是 12 美元。现在,让我们看看如果其中一个资产是无风险的,即其波动率为 0,会发生什么。我们用以下参数调用函数:
Margrabe(100, 120, .2, 0, 2, 0, 0, 0.03)
[1] 6.566047
这意味着什么?该产品赋予我们改变第一个风险资产的权利,该资产是一只价格为 100 美元、波动率为 20%的股票,换成第二个“风险”资产,后者的价格为 120 美元,支付 3%的股息,且波动率为 0(因此它是一个固定现金金额),利率为 3%。实际上,两年后,它将是以 3%的无风险利率以 120 美元购买股票的权利。我们将其与该认购期权的 BS 价格进行比较:
BlackScholesOption("c", 100, 120, 2, 0.03, 0.03, .2)
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "c", S = 100, X = 120, Time = 2, r = 0.03,
b = 0.03, sigma = 0.2)
Parameters:
Value:
TypeFlag c
S 100
X 120
Time 2
r 0.03
b 0.03
sigma 0.2
Option Price:
6.566058
Description:
Tue Aug 05 11:29:57 2014
是的,它们确实是相同的。如果我们将第一个资产的波动率设置为 0,这实际上意味着我们拥有第二个资产的认沽期权。
Margrabe(100, 120, 0, 0.2, 2, 0, 0.03, 0)
[1] 3.247161
BS 公式的结果如下:
BlackScholesOption("p", 120, 100, 2, 0.03, 0.03, .2)
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "p", S = 120, X = 100, Time = 2, r = 0.03,
b = 0.03, sigma = 0.2)
Parameters:
Value:
TypeFlag p
S 120
X 100
Time 2
r 0.03
b 0.03
sigma 0.2
Option Price:
3.247153
Description:
Fri Aug 08 17:38:04 2014
在这两种情况下,只有从第五位数字开始才会有数值误差。
我们还可以使用 Margrabe 公式来获取我们在 货币期权 部分讨论的货币期权的价格。我们可以检查 BS 公式是否提供了相同的价格:
Margrabe(0.745, 0.7, 0.2, 0, 5, 0.15, 0.02, 0.03)
[1] 0.152222
我们需要讨论的最后一件事是相关性如何影响期权的价格。为了说明这一点,我们将计算不同相关性值下期权的 Margrabe 价格。这可以通过几行代码来完成:
x <- seq(-1, 1, length = 1000)
y <- rep(0, 1000)
for (i in 1:1000)
y[i] <- Margrabe(100, 120, .2, 0.3, 2, x[i])
plot(x, y, xlab = "correlation", ylab = "price",
main = "Price of exchange option", type = "l", lwd = 3)
在这里,我们可以看到以下图片中的结果:

结果并不令人惊讶。当相关性较高时,我们可以在相同的股票之间进行转换,显然这没有任何价值。当相关性高且为负时,如果事情出现问题,我们就有更大的机会通过期权达成好的交易(这意味着,如果我们的资产下降,负相关性越高,另一资产的价格上涨并拯救我们免于亏损的机会就越大)。换句话说,在这种情况下,期权更多的是为了保险而非投机;我们不需要承担来自其他资产价格变化的风险。这就是为什么当相关性为负时,期权更有价值的原因。
Quanto 期权
术语“quanto”是数量调整期权(quantity adjusting option)的缩写。Quanto 衍生品的收益由以一种货币计价的资产决定,但以另一种货币支付。
理解 quanto 产品(或任何种类的衍生品)最好的方法是检查其收益函数。众所周知,假设标的资产是一个不支付红利的股票,欧洲看涨期权的收益如下:

这里,S[A] 是股票的价格,X 是行权价格。这里,c、S[AT] 和 X 都以相同的货币计价;我们将其称为本国货币。
欧洲看涨 quanto 的收益如下:

这里,S 是外汇汇率。因此,看涨 quanto 期权支付的金额与简单的看涨期权相同,但使用的是另一种货币——我们称之为外币。因此,这个支付的数量必须乘以一个外汇汇率,以便我们得到以本国货币计价的收益值。当然,S 必须是外币相对于本国货币的价格。换句话说,在 S 的报价中,基准货币是外币。
看涨 quanto 定价公式
定价一个看涨 quanto 意味着确定之前的收益值。像往常一样,我们将假设在风险中性度量(Q)下,标的资产的价格遵循几何布朗运动,其漂移等于无风险的本国利率(r),即:

此外,我们假设外汇汇率遵循类似的过程:

在这些方程中,W[1]和W[2]是Q下的标准 Wiener 过程,相关性为ρ。令q表示无风险外汇利率。这意味着外币银行存款在时间t的价值是exp(qt)。以本国货币表示时,这个价值为:

假设这是一个在国内市场上交易的产品,那么它的折现值必须在Q下是一个马尔科夫过程。我们来计算这个折现值:

这个过程只有在
时是马尔科夫过程,这个是在Q下的。

现在,我们将计算SS[A]产品,记作Y。



这里,
和
。
W[2]和W[3]之间的相关性是
。
因此,
。
现在,需要特别注意的是,call quanto 是一个特殊的交换期权,因此可以使用 Margrabe 公式进行定价。我们只需识别在行使期权时将要交换的两种风险资产及相关参数。从 quanto 的支付函数中可以看出,第一个风险资产是SS[A] = Y,第二个是XS(都以本国货币表示)。由于在Q下这些过程的漂移成分并非简单的无风险国内利率,我们必须使用包含股息收益率的Margrabe公式。从之前的计算可以看出,Y过程应该当作股息收益率为
来处理,而XS则为简单的q。唯一需要确定的剩余参数是σ。通过直接替换,我们得到以下计算:



总结所有这些结果,我们必须使用Margrabe公式(见公式(4)),并进行替换!定价公式、
、
、
和
。
因此,call quanto 的价格如下:

在之前的方程中,d[1] 和 d[2] 如下:
和
。
在 R 中定价看涨 quanto
让我们看一个在 R 中定价看涨 quanto 的例子。我们喜欢的股票价格为 100 美元,波动率为 20%。我们需要一个 90 美元的看涨期权,该期权在三年后以欧元支付。美元的无风险利率为r = 2%,欧元的无风险利率为q = 3%。目前,1 美元等于 0.7467 欧元。欧元的波动率为 15%,股票价格和美元欧元汇率之间的相关性为 10%。
如果三年后股票价格高于 90 美元,差额将以欧元支付。例如,如果三年后价格为 110 美元,我们将获得 20 欧元。按当前汇率,20*0.7467 = 26.78093 美元,但如果三年后欧元美元汇率不同,例如,美元欧元汇率为 0.7,那么这相当于 28.57143 美元。所以,收益可能以美元不同,但如果我们希望以欧元支付,我们就消除了汇率风险。
这看起来很复杂,但幸运的是,我们可以使用 Margrabe 公式和我们的 Margrabe 函数来计算期权的价格。
Margrabe = function(S1, S2, sigma1, sigma2, Time, rho, delta1 = 0, delta2 = 0)
我们需要这些替代项
,
,
,
,和
。
S1 是欧元计价的股票价格,S2 是欧元计价的行权价格。delta1 和 delta2 可以很容易地计算:delta1 = 0.03-0.02-0.2*0.15*0.1 和 delta2 = 0.03。唯一的问题是,我们需要设置 sigma = sigma1,但是 sigma 不是 Margrabe 函数的参数,它是在函数体内计算的。考虑以下命令:
sigma = sqrt(sigma1² + sigma2² - 2 * sigma1 * sigma2 * rho)
为了得到 sigma = sigma1 的结果,我们需要设置 sigma2 = rho = 0。
现在,我们可以使用给定的参数调用Margrabe函数。
Margrabe(74.67, 90*0.7467, 0.2, 0,3, 0, 0.007 , 0.03)
[1] 16.23238
结果是 16.23。这是 quanto 期权的价格。
总结
在这一章中,我们面对了讨论金融数学中最美丽、最困难部分之一的挑战:衍生品定价。我们在理论和实践中学习了 Black-Scholes 模型的相关问题的推广。我们学习了如何使用 R 和 Black-Scholes 公式定价货币期权。我们看到了实现 Margrabe 公式的自定义代码是多么简单,这一公式是 Black-Scholes 模型的扩展。我们使用该公式定价了股票期权、货币期权和兑换期权。最后,我们讨论了 quanto 期权,并意识到 quanto 期权也可以使用 Margrabe 公式定价。
如果你觉得这一章很有趣,那么你一定会对下一章感兴趣,下一章将涉及一个相关的话题——即利率衍生品。
参考文献
-
Black, F. 和 Scholes, M. (1973): 期权和公司负债定价。政治经济学期刊, 81(3), 第 637-654 页。
-
Margrabe, W. (1978): 交换一种资产与另一种资产的期权价值。金融学期刊, 33(1), 第 177-186 页。
-
Medvegyev, Péter (2007): 随机积分理论。牛津大学出版社。
-
Merton, R. (1973): 理性期权定价理论。贝尔经济学与管理科学期刊, 4(1), 第 141-183 页。
第六章:利率衍生品与模型
利率衍生品是金融衍生品,其支付依赖于利率。
这种产品种类繁多,基本类型包括利率掉期、远期利率协议、可赎回和可出售债券、债券期权、上限和下限等。
在本章中,我们将从黑模型(也称为 Black-76 模型)开始,它是 Black-Scholes 模型的推广版本,通常用于定价利率衍生品。接下来,我们将展示如何应用黑模型来定价利率上限。
黑模型的一个缺点是它假设某些标的资产(例如债券价格或利率)服从对数正态分布,并忽略了利率随时间变化的情况。因此,Black 的公式不能用于所有种类的利率衍生品。有时,有必要建模利率模型的期限结构。有许多利率模型尝试捕捉这一期限结构的主要特征。在本章的第二部分,我们将讨论两种基本且常用的利率模型,即 Vasicek 模型和 Cox-Ingersoll-Ross 模型。与前一章一样,我们假设你熟悉 Black-Scholes 模型和风险中性估值的基础知识。
黑模型
我们从定义利率衍生品为具有利率依赖现金流的资产开始本章内容。值得注意的是,金融产品的价值几乎总是依赖于某些利率,因为需要对未来现金流进行折现。然而,在利率衍生品的情况下,不仅折现后的价值,而且支付本身也依赖于利率。这就是为什么利率衍生品比股票或外汇衍生品更难定价的主要原因(Hull, 2009详细讨论了这些难点)。
黑模型(Black, 1976)最初是为期货合约的期权定价而开发的。期货期权赋予持有者在指定日期(到期日,T)以预定期货价格(行权价格或执行价格,X)进入期货合约的权利。在这个模型中,我们保持 Black-Scholes 模型的假设,只不过标的资产是期货价格,而不是现货价格。因此,我们假设期货价格(F)遵循几何布朗运动:

很容易看出,期货合约可以作为具有与无风险利率(r)相等的连续增长率的产品来处理。因此,Black 的期货期权公式与 Black-Scholes 的货币期权公式完全相同(前一章讨论过),其中q等于r(就好像国内和国外利率相同)。因此,Black 的欧洲期货看涨期权公式如下:

这里,
和
。
类似的看跌期权价格如下:

不奇怪的是,GBSOption 函数(或 BlackScholesOption 函数)对于 Black 模型同样有效。现在是时候更仔细地看一下它是如何工作的了。
当在 R 控制台中输入一个函数名而不加括号时,函数不会被调用,而是返回源代码(字节编译的代码除外)。这对于初学者来说不推荐,但对于有一定经验的程序员来说非常有用,因为这些细节通常不包含在包的文档中。我们来试试:
require(fOptions)
GBSOption
function (TypeFlag = c("c", "p"), S, X, Time, r, b, sigma, title = NULL,
description = NULL)
{
TypeFlag = TypeFlag[1]
d1 = (log(S/X) + (b + sigma * sigma/2) * Time)/(sigma * sqrt(Time))
d2 = d1 - sigma * sqrt(Time)
if (TypeFlag == "c")
result = S * exp((b - r) * Time) * CND(d1) - X * exp(-r *
Time) * CND(d2)
if (TypeFlag == "p")
result = X * exp(-r * Time) * CND(-d2) - S * exp((b -
r) * Time) * CND(-d1)
param = list()
param$TypeFlag = TypeFlag
param$S = S
param$X = X
param$Time = Time
param$r = r
param$b = b
param$sigma = sigma
if (is.null(title))
title = "Black Scholes Option Valuation"
if (is.null(description))
description = as.character(date())
new("fOPTION", call = match.call(), parameters = param, price = result,
title = title, description = description)
}
<environment: namespace:fOptions>
如果这还不完全清楚,不用担心;我们目前只关心计算看涨期权的价格。首先,计算 d[1](稍后我们会检查公式)。BS 公式有不同的形式(适用于股票期权、货币期权和带股息的股票期权),但以下方程始终成立:

在这个函数中,d[2]是根据这个公式计算的。最终结果的形式是
,其中 a 和 b 依赖于模型,但它们始终是标的资产价格和执行价格的贴现值。
现在,我们可以看到 b 参数在计算中的作用。正如我们在上一章中提到的,这是我们决定使用哪个模型的方法。如果仔细检查公式,我们可以得出结论,通过设置 b = r,我们得到 Black-Scholes 股票期权模型;通过设置 b = r-q,我们得到 Merton 的股票期权模型,它具有连续股息收益率 q(这与货币期权模型相同,正如我们在上一章中看到的);通过设置 b = 0,我们得到 Black 的期货期权模型。
现在,让我们看一个 Black 模型的例子。
我们需要一个 5 年期、执行价为 100 的资产期权。期货价格为 120。假设资产的波动率为 20%,无风险利率为 5%。现在,只需用 S = F 和 b = 0 调用 BS 期权定价公式:
GBSOption("c", 120, 100, 5, 0.05, 0, 0.2)
我们得到的结果是通常的形式:
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "c", S = 120, X = 100, Time = 5, r = 0.05,
b = 0, sigma = 0.2)
Parameters:
Value:
TypeFlag c
S 120
X 100
Time 5
r 0.05
b 0
sigma 0.2
Option Price:
[1] 24.16356
期权的价格约为 24 美元,我们还可以从输出中检查出 b = 0,由此我们必须知道使用的是 Black 的期货期权模型(或者我们犯了一个严重的错误)。
尽管最初是为商品产品开发的,但 Black 模型证明是定价利率衍生品(如债券期权或上限和下限)的有用工具。在下一节中,我们将展示如何使用此模型来定价利率上限。
使用 Black 模型定价上限
利率上限是利率衍生品,持有者在一段时间内如果利率超过某个水平(行权价格,X),将获得正向支付。类似地,利率下限的持有者如果利率低于行权价格,每个时期也会收到正向支付。显然,上限和下限是有效的产品,可以对冲利率波动。在本节中,我们将讨论上限的定价。假设标的利率为 LIBOR,L。
正如我们在前一章中讨论的那样,理解衍生品的最好方式是确定它们的支付结构。cap(名义金额为单位)在第n期末的支付结构如下:

这里,τ是两个支付之间的时间间隔。这个单次支付称为 caplet,而 cap 当然是一个由多个顺序 caplet 组成的投资组合。在定价 cap 时,所有的 caplet 都必须逐个定价,并将它们的价格相加。此外,前面提到的支付结构向我们展示了,定价第n个 caplet 实际上就是定价一个以 LIBOR 为标的资产、行权价格为 X、到期日为τn的看涨期权。
如果我们假设时间n-1时的 LIBOR 利率(L[n-1])是一个具有对数正态分布的随机变量,并且波动率为
,那么我们可以使用 Black 公式来定价 caplet:

这里,
和
。
这里,F[n-1]是τ(n-1)和τn之间的远期 LIBOR 利率,r是到期日τn的无风险现货对数收益率。一旦我们得到单个 caplet 的价值,就可以对所有 caplet 进行定价,从而得到整个 cap 的价格。
让我们通过一个例子来深入理解这一点。我们需要在 2014 年 5 月到 11 月之间为某商业伙伴支付 6 个月的 USD LIBOR 利率。caplet 是避免利率风险的一种简单方式。假设我们在 LIBOR 利率上有一个 2.5%的行权价格的 caplet(使用通常的术语)。
这意味着,如果 LIBOR 利率高于 2.5%,我们将收到差额的现金支付。例如,如果 LIBOR 利率在 5 月为 3%,那么我们在一个名义金额单位上的收益为 0.5*max(3%-2.5%, 0)。
现在,让我们来看一下如何定价 caplet。这里没有什么新内容;我们可以直接使用 Black-Scholes 公式。显然,我们需要设定S = F[n-1]、Time = 0.5,并且b = 0。假设 LIBOR 利率遵循具有 20%波动率的几何布朗运动,5 月 1 日和 11 月 1 日之间的远期利率为 2.2%,即期利率为 2%。在这种情况下,caplet 的价格如下:
GBSOption("c", 0.022, 0.025, 0.5, 0.02, 0, 0.2)
Title:
Black Scholes Option Valuation
Call:
GBSOption(TypeFlag = "c", S = 0.022, X = 0.025, Time = 0.5, r = 0.02,
b = 0, sigma = 0.2)
Parameters:
Value:
TypeFlag c
S 0.022
X 0.025
Time 0.5
r 0.02
b 0
sigma 0.2
Option Price:
0.0003269133
期权的价格为 0.0003269133。我们仍然需要将其与τ = 0.5 相乘,这使得它变为 0.0001634567。如果我们以百万美元为单位进行测量,这意味着期权的价格大约为 163 美元。
上限只是上限期权的总和,但如果需要,我们可以用不同的参数将它们组合起来。假设我们需要一个上限,如果 LIBOR 利率在前 3 个月超过 2.5%,或者在接下来的 3 个月内高于 2%。在 5 月和 8 月期间(假设为 2.1%)以及 8 月和 11 月期间(假设为 2.2%),远期 LIBOR 利率也可以不同。我们只需逐个定价每个上限期权并加总它们的价格:
GBSOption("c", 0.021, 0.025, 0.25, 0.02, 0, 0.2)
GBSOption("c", 0.022, 0.02, 0.25, 0.02, 0, 0.2)
我们没有在此包含所有的输出,只列出了价格:
Option Price:
3.743394e-05
Option Price:
0.002179862
现在,我们需要将它们与τ = 0.25 相乘,并求它们价格的总和:
(3.743394e-05 + 0.002179862 ) * 0.25
0.000554324
这笔名义金额为 100 万美元的上限价格约为 554 美元。
定价一个地板与此非常相似。首先,我们将资产的现金流分为单个支付,这些支付称为地板期权。然后,我们借助黑色模型确定每个地板期权的价值;唯一的区别是地板期权是看跌期权,而不是看涨期权。最后,我们将所有地板期权的价格加起来,得到地板的价值。
黑色模型适用于当我们可以假设基础资产的未来价值具有对数正态分布时。另一种评估利率衍生品的方法是通过建模利率的期限结构。在这里,我们将继续介绍两种基本的利率模型及其主要特点。
瓦西切克模型
瓦西切克模型(Vasicek, 1977)是一个连续的仿射一因子随机利率模型。在该模型中,瞬时利率的动态由以下随机微分方程给出:

这里,α、β和σ是正的常数,r[t]是利率,t是时间,W[t]表示标准的维纳过程。在数学中,这个过程称为奥恩斯坦-乌伦贝克过程。
如您所见,瓦西切克模型中的利率遵循一个均值回归过程,具有长期平均值β;当r[t] < β时,漂移项变为正值,因此利率预计会增加,反之亦然。调整到长期均值的速度由α衡量。该模型中的波动率项是常数。
利率模型是用 R 语言实现的,但为了更深入地理解公式背后的内容,让我们写一个函数,直接实现瓦西切克模型的随机微分方程:
vasicek <- function(alpha, beta, sigma, n = 1000, r0 = 0.05){
v <- rep(0, n)
v[1] <- r0
for (i in 2:n){
v[i] <- v[i - 1] + alpha * (beta - v[i - 1]) + sigma * rnorm(1)
}
return(v)
}
就是这样。现在,让我们绘制一些轨迹,看看它的样子:
set.seed(123)
r <- replicate(4, vasicek(0.02, 0.065, 0.0003))
matplot(r, type = "l", ylab = "", xlab = "Time", xaxt = "no", main = "Vasicek modell trajectories")
lines(c(-1,1001), c(0.065, 0.065), col = "grey", lwd = 2, lty = 1)
以下截图显示了前面命令的输出:

为了理解参数的作用,我们用不同的 sigma 和 alpha 值绘制相同的轨迹(即由相同随机数生成的轨迹):
r <- sapply(c(0, 0.0002, 0.0006),
function(sigma){set.seed(102323); vasicek(0.02, 0.065, sigma)})
matplot(r, type = "l", ylab = "", xlab = "Time" ,xaxt = "no", main = "Vasicek trajectories with volatility 0, 0.02% and 0.06%")
lines(c(-1,1001), c(0.065, 0.065), col = "grey", lwd = 2, lty = 3)
以下是前面代码的输出:

r <- sapply(c(0.002, 0.02, 0.2),
function(alpha){set.seed(2014); vasicek(alpha, 0.065, 0.0002)})
轨迹形状相同,但波动性不同:
matplot(r, type = "l", ylab = "", xaxt = "no", main = "Vasicek trajectories with alpha = 0.2%, 2% and 20%")
lines(c(-1,1001), c(0.065, 0.065), col = "grey", lwd = 2, lty = 3)
以下是前述命令的输出:

我们可以看到,α 值越高,轨迹达到长期均值的时间越早。
可以证明(例如,见已引用的 Vasicek 原始论文),Vasicek 模型中的短期利率呈正态分布,其条件期望值和方差如下:


值得注意的是,当 T 或 α 趋向于无穷大时,期望值会收敛到 β。此外,当 α 趋向于无穷大时,方差会收敛到 0。这些观察结果与参数的解释是一致的。
为了展示方程的系数如何决定分布的参数,让我们绘制不同 α、β 和 σ 值的条件概率密度函数,并观察它随时间的变化:
vasicek_pdf = function(x, alpha, beta, sigma, delta_T, r0 = 0.05){
e <- r0*exp(-alpha*delta_T)+beta*(1-exp(-alpha*delta_T))
s <- sigma²/(2*alpha)*(1-exp(-2*alpha*delta_T))
dnorm(x, mean = e, sd = s)
}
x <- seq(-0.1, 0.2, length = 1000)
par(xpd = T ,mar = c(2,2,2,2), mfrow = c(2,2))
y <- sapply(c(10, 5, 3, 2), function(delta_T)
vasicek_pdf(x, .2, 0.1, 0.15, delta_T))
par(xpd = T ,mar = c(2,2,2,2), mfrow = c(2,2))
matplot(x, y, type = "l",ylab ="",xlab = "")
legend("topleft", c("T-t = 2", "T-t = 3", "T-t = 5", "T-t = 10"), lty = 1:4, col=1:4, cex = 0.7)
y <- sapply(c(0.1, 0.12, 0.14, 0.16), function(beta)
vasicek_pdf(x, .2, beta, 0.15, 5))
matplot(x, y, type = "l", ylab ="",xlab = "")
legend("topleft", c("beta = 0.1", "beta = 0.12", "beta = 0.14", "beta = 0.16"), lty = 1:4, col=1:4,cex = 0.7)
y <- sapply(c(.1, .2, .3, .4), function(alpha)
vasicek_pdf(x, alpha, 0.1, 0.15, 5))
matplot(x, y, type = "l", ylab ="",xlab = "")
legend("topleft", c("alpha = 0.1", "alpha = 0.2", "alpha = 0.3", "alpha = 0.4"), lty = 1:4, col=1:4, cex = 0.7)
y <- sapply(c(.1, .12, .14, .15), function(sigma)
vasicek_pdf(x, .1, 0.1, sigma, 5))
matplot(x, y, type = "l", ylab ="",xlab = "")
legend("topleft", c("sigma = 0.1", "sigma = 0.12", "sigma = 0.14", "sigma = 0.15"), lty = 1:4, col=1:4, cex = 0.7)
以下截图是前述代码的结果:

我们可以看到,分布的方差随着时间的推移而增加。β 仅影响概率分布的均值。显然,当 α 的值较高时,过程更早地达到长期均值,并且方差较小;而当波动性较大时,我们得到一个更平坦的密度函数,即方差更大。
当利率遵循 Vasicek 模型时,定价零息债券的公式如下(关于这个公式的推导,参见例如 Cairns [2004]):

这里,
和
。
在前述公式中,P 表示零息债券的价格,t 是我们为债券定价的时间,T 是到期时间(因此,T-t 是到期时间)。如果我们知道零息债券的价格,就可以通过以下简单关系确定现货收益率曲线:

Cox-Ingersoll-Ross 模型
与 Vasicek 模型类似,Cox-Ingersoll-Ross 模型(Cox 等,1985),通常被称为 CIR 模型,是一个连续的、仿射的单因素随机利率模型。在这个模型中,即时利率的动态由以下随机微分方程给出:

这里,α,β,和σ是正的常数,r[t]是利率,t是时间,W[t]表示标准 Wiener 过程。很容易看出,漂移项与 Vasicek 模型中的相同;因此,利率再次遵循均值回归过程,β是长期平均值,α是调整速率。不同之处在于,波动率项不是常数,而是与利率水平的平方根成比例。这一“微小”的差异对于未来短期利率的概率分布有着巨大的影响。在 CIR 模型中,利率遵循非中心卡方分布,其密度函数为(f):

这里,
,
,和
。
这里,
表示带有n自由度的卡方分布的概率密度函数,m表示非中心参数。由于这种随机变量的期望值和方差分别为n+m和2(n+2m),因此我们得到了利率的以下矩:


我们可以观察到,条件期望值与 Vasicek 模型中的完全相同。需要注意的是,短期利率作为一个正态分布的变量,在 Vasicek 模型中可能变为负值,但在 CIR 模型中则不可能发生这种情况。
如同 Vasicek 模型的情况,我们可以看到系数如何决定概率密度函数的形状,如果我们使用不同的参数集绘制它。下面的代码通过比较不同参数设置下的概率密度函数来完成这项工作:
CIR_pdf = function(x, alpha, beta, sigma, delta_T, r0 = 0.1){
q = (2*alpha*beta)/(sigma²) - 1
c = (2*alpha)/(sigma²*(1-exp(-alpha*delta_T)))
u = c*r0*exp(-alpha*delta_T)
2*c*dchisq(2*c*x, 2*q+2, ncp = 2*u)
}
x <- seq(0, 0.15, length = 1000)
y <- sapply(c(1, 2, 5, 50), function(delta_T)
CIR_pdf(x, .3, 0.05,0.1,delta_T))
par(mar = c(2,2,2,2), mfrow = c(2,2))
matplot(x, y, type = "l",ylab ="",xlab = "")
legend("topright", c("T-t = 1", "T-t = 2", "T-t = 5", "T-t = 50"), lty = 1:4, col = 1:4, cex = 0.7)
y <- sapply(c(.2, .4, .6, 1), function(alpha)
CIR_pdf(x, alpha, 0.05,0.1,1))
matplot(x, y, type = "l",ylab ="",xlab = "")
legend("topright", c("alpha = 0.2", "alpha = 0.4", "alpha = 0.6", "alpha = 1"), lty = 1:4, col = 1:4, cex = 0.7)
y <- sapply(c(.1, .12, .14, .16), function(beta)
CIR_pdf(x, .3, beta,0.1,1))
matplot(x, y, type = "l",ylab ="",xlab = "")
legend("topleft", c("beta = 0.1", "beta = 0.12", "beta = 0.14", "beta = 0.16"), lty = 1:4, col = 1:4, cex = 0.7)
x <- seq(0, 0.25, length = 1000)
y <- sapply(c(.03, .05, .1, .15), function(sigma)
CIR_pdf(x, .3, 0.05,sigma,1))
matplot(x, y, type = "l",ylab ="",xlab = "")
legend("topright", c("sigma = 1", "sigma = 5", "sigma = 10", "sigma = 15"), lty = 1:4, col = 1:4, cex = 0.7)
这里,我们可以看到结果。我们得出的结论与 Vasicek 模型的情况相同,只是这里,β改变了密度函数的形状,而不仅仅是平移它。

在 CIR 模型中定价零息债券得到以下公式(例如,参见Cairns [2004]):

这里,
,
,和
。
从债券价格确定收益曲线与 Vasicek 模型中的完全相同。
利率模型的参数估计
当使用利率模型进行定价或模拟时,重要的是要将其参数正确地校准到实际数据。这里,我们介绍了一种可能的参数估计方法。该方法由Chan 等人,1992年提出,通常称为 CKLS 方法。该程序通过经济计量学方法——广义矩估计法(GMM;更多细节请参见Hansen,1982)——来估计以下利率模型的参数:

很容易看出,当γ=0 时,这个过程给出了 Vasicek 模型,当γ=0.5 时,给出了 CIR 模型。作为参数估计的第一步,我们通过欧拉近似法离散化这个方程(参见Atkinson,1989):

这里,δ[t]是两次利率观测之间的时间间隔,e[t]是独立的标准正态随机变量。参数是通过以下零假设进行估计的:



令Θ为待估计的参数向量,即
。
我们考虑参数向量的以下函数:

很容易看出,在零假设下,
=0。
GMM 的第一步是我们考虑与
对应的样本,具体为
:

这里,n是观测值的数量。
最后,GMM 通过最小化以下二次型来确定参数:

这里,
是一个对称的、正定的权重矩阵。
在 R 中有一个quadprog包可以处理这类问题,或者我们也可以使用一般的优化方法,比如optim函数。
使用 SMFI5 包
在讨论完利率模型背后的数学原理并进行艰难的编程后,我们推荐SMFI5包,它提供了用户友好的解决方案,用于建模和模拟利率模型(如果模型是由奥恩斯坦-乌伦贝克过程表示的)、定价债券及其他许多应用。
我们无法详细讨论,但作为简短演示,让我们调用一个模拟不同到期债券价格的函数:
bond.vasicek(0.5,2.55,0.365,0.3,0,3.55,1080,c(1/12, 3/12, 6/12, 1),365)
这会返回一个惊人的结果:

摘要
本章讨论了利率模型和利率衍生品。在介绍了 Black 模型之后,我们使用它来定价利率上限和上限期权。我们还研究了 Black-Scholes 模型的 R 代码。
然后,我们将注意力转向了如 Vasicek 和 CIR 模型等利率模型。我们也讨论了参数估计的理论。最后,我们简要演示了 SMFI5 包的使用方法。利率模型在本章中对我们来说非常重要,因为利率衍生品的定价从假设未来的利率和收益曲线开始。通过使用正确选择和校准的模型,我们有机会分析未来可能的利率场景。当然,利率模型是一个更广泛的话题,值得更深入研究。然而,学习最流行和最著名的模型是一个好的起点,我们鼓励你进一步研究它们,或者查阅下一章,因为某些期权仍然为我们带来了一些惊喜。
参考文献
-
Atkinson, K. [1989]:数值分析导论。约翰·威利父子公司,纽约。
-
Black, F. [1976]:商品合同定价。金融经济学杂志,3(1-2),第 167-179 页。
-
Cairns, A. [2004]:利率模型:简介。普林斯顿大学出版社,普林斯顿-牛津。
-
Chan, K.,Karolyi, A.,Longstaff, A. 和 Sanders, A. [1992]:短期利率的替代模型的实证比较。金融学杂志,第 3 期,第 1209-1227 页。
-
Cox, J.,Ingersoll, J. 和 Ross, S. [1985]:利率期限结构理论。计量经济学,第 53 期,第 385-407 页。
-
Hansen, L. [1982]:广义矩估计量的大样本性质。计量经济学,第 4 期,第 1029-1054 页。
-
Hull, J. [2009]:期权、期货及其他衍生品。皮尔逊普伦蒂斯霍尔出版社,新泽西州。
-
Vasicek, O. [1977]:期限结构的均衡特征。金融经济学杂志,5(2),第 177-188 页。
第七章:外来期权
所有衍生品都是金融合约,在这些合约中,可以商定的特性远超过简单的买卖权利。可以根据假设情境设计复杂的支付结构;因此,外来合约的最终支付可能依赖于一整套情形。通常,甚至标的物的路径也对最终支付有重大影响。与这些衍生品相比,传统的认购和认沽期权很快就被视为简单,从而获得了一个不太令人印象深刻的昵称:普通香草期权。
普通的认购期权和认沽期权就像普通香草冰淇淋,是最简单的冰淇淋,没有任何复杂的配料。“普通香草”这个表达在金融领域中根深蒂固,甚至在债券市场中也使用,其中香草债券是最简单的支付票息债券。
任何相对于基本的普通香草期权有额外特点的期权都属于一个庞大的群体,称为外来期权。外来期权很受欢迎,因为卖方银行家在激烈竞争中,为客户提供量身定制的产品。外来期权广泛传播的另一个原因是,有趣的是,大多数情况下,对外来结构报价对市场制造者来说并不比报价普通香草期权更加困难。
一种通用的定价方法
无论是外来品还是其他,所有衍生品都有一个内在的共同特点,那就是它们是其他工具的函数,因此得名“衍生品”。因此,衍生品的价格并不是通过直接的供需关系独立发展出来的,而是作为一种估算的构建成本。例如,欧元的一个月期远期美元价格高度依赖于欧元现货美元价格;远期价格只是现货价格(以及利率)的函数。
如果通过一种涉及较简单工具的交易策略能够构建与持有衍生品相同的利益,那么该衍生品就可以被复制。衍生品不像独一无二的画作;衍生品的伪造品具有相同的价值,而复制品与原件一样好。通过使用无套利论证,Black 和 Scholes(1973)以及Merton(1973)表明,衍生品的价格应该等于在适当实施动态复制策略过程中产生的预期费用之和。Taleb(1997)广泛描述了,在实际市场环境中实施适当的复制策略往往是非常棘手的。
动态对冲的作用
大多数时候,复制是一个动态策略。在衍生品的生命周期内,你几乎需要不断地进行交易。Haug (2007b) 表明,非连续对冲的对冲误差,即使对于普通的香草期权,也可能是显著的。无论如何,连续对冲是一项巨大的工作,通常在定价公式中没有显式体现;然而,大多数定价函数都假设在后台应始终正确进行动态对冲。这也适用于我们每次谈论风险中性世界或风险中性定价时。更多参考资料,见 Wilmott (2006)。
幸运的是,不管动态对冲有多困难,运行期权组合至少是一个可扩展的业务;对冲成千上万的期权并不比对冲几个期权更难。所有期权都可以分解为某些敏感度,即所谓的希腊字母(或简称为希腊字母)。这个昵称源于某些关键敏感度是用希腊字母命名的(例如 delta、gamma、rho 和 theta)。它们是偏导数,因此它们是可加的。将各个期权的 delta 加总起来就得到组合的 delta,以此类推。这不仅适用于普通的香草期权,也适用于异域期权,从而在香草期权和异域期权之间建立了一个非常强的联系。
R 如何提供很大帮助
我们通过展示一些异域期权的例子来开始本章,给出一种可能的分类方法。我们将展示来自fExoticOptions包的例子,并说明如何为任何衍生品定价函数创建所谓的 Black-Scholes 表面。之后,我们将专注于对任何异域衍生品希腊值的数值估算。接下来,我们将展示一种尚未包含在fExoticOptions包中的异域期权定价方法。
我们选择双无触碰(DNT)二元期权,主要是因为它在外汇(FX)市场上的流行程度以及它所蕴含的许多对其他异域期权仍然适用的结论。我们将以 AUDUSD 作为标的,因为在撰写本章时,澳元和美元的利率之间存在显著的利差,我们可以展示如何将这些利率纳入定价函数中。我们将通过使用静态期权复制论证展示第二种计算 DNT 价格的方法。我们将展示一个 DNT 的实际例子,并通过模拟展示一种估算 DNT 存活概率的方法。利用这一方法,我们可以讨论现实世界概率与风险中性概率之间的关系以及风险溢价的作用。最后,我们将展示一些实用的微调技巧,用于将异域期权嵌入结构化产品中。
除了通过在 R 中实现复杂的异型期权定价函数和模拟的示例,作为附带效果,本章的学习成果还包括将希腊字母作为异型期权与普通期权之间的联系来理解。我们将使用在第五章中介绍的相同术语,外汇衍生品,该章还详细介绍了更多关于货币和普通期权的内容。
透视普通期权
Haug (2007a) 全面覆盖了大约 100 种异型衍生品的定价公式。fOptions 和 fExoticOptions 包都是基于这本书的。Wilmott (2006)、Taleb (1997) 和 DeRosa (2011) 讨论了很多关于这些期权的实际问题。
第一印象可能是异型期权种类繁多。它们的分类方式有很多种。市场做市商讨论不同世代的异型期权,如第一代、第二代等等。他们的分类方法是从对冲的角度出发。我们将采用稍有不同的角度,即最终用户的角度,按照期权的主要异型特征来分类。
亚洲型期权大致属于平均型期权。它可以是一个平均利率或平均执行价,也可以是算术平均或几何平均。这些期权是路径依赖的;也就是说,它们在到期时的价值不仅仅是到期时标的价格的函数,还取决于整个路径。由于平均价格的波动率低于标的价格本身的波动率,亚洲期权比普通期权便宜:
library(fOptions)
library(fExoticOptions)
a <- GBSOption("c", 100, 100, 1, 0.02, -0.02, 0.3, title = NULL,
description = NULL)(z <- a@price)
[1] 10.62678
a <- GeometricAverageRateOption("c", 100, 100, 1, 0.02, -0.02, 0.3,
title = NULL, description = NULL)(z <- a@price)[1] 5.889822
屏障型异型期权也是路径依赖型期权。它们可能有一个或两个屏障。每个屏障可能是 触及入场 (KI) 或 触及出场 (KO)。在期权的有效期内,标的价格会被监控,如果价格达到或超过屏障,就会发生触及事件。具有 KI 屏障的期权在触及事件发生后变得可以行使。具有 KO 屏障的期权在开始时是可以行使的,但如果发生触及事件,就会变得不可行使。如果有两个屏障,它们可以是相同类型的:双重触及出场 (DKO) 和 双重触及入场 (DKI),或者是 触及入场触及出场 (KIKO) 类型。
如果其他所有参数相同,那么以下方程成立:
KI + KO = 普通期权。
这是因为在这种情况下,KI 和 KO 期权是互斥的,但它们中的一个一定会被行使。第一个参数 cuo 和 cui 是表示“打击出场”和“打击入场”的标志。接下来,我们检查以下条件:
普通期权 - KO - KI = 0。
以下代码演示了前述条件:
library(fExoticOptions)
a <- StandardBarrierOption("cuo", 100, 90, 130, 0, 1, 0.02, -0.02, 0.30,
title = NULL, description = NULL)
x <- a@price
b <- StandardBarrierOption("cui", 100, 90, 130, 0, 1, 0.02, -0.02, 0.30,
title = NULL, description = NULL)
y <- b@price
c <- GBSOption("c", 100, 90, 1, 0.02, -0.02, 0.3, title = NULL,
description = NULL)
z <- c@price
v <- z - x - y
v
[1] 0
基于DKO + DKI = vanilla的相同逻辑,我们甚至可以表示KO - DKO = KIKO。因此,KIKO 期权从一开始就是不可行使的,只要短期 DKO 和长期 KO 都有效,它们就会互相中和。如果短期 DKO 失效而长期 KO 依然有效,那么对 KIKO 期权来说就是 KI 事件。然而,即使在被击中后,KIKO 期权仍然可能会失效。当然,KIKO + DKO = KO 的方式也得出相同的结论。
此外,障碍期权之间存在一些重要的收敛特征。根据 KO + KI = vanilla 方程,当我们将障碍推得远离现货时,KO 会收敛到 vanilla,因为 KI 会随着障碍远离现货而收敛为零。接下来的图表将展示这一特征。
vanilla <- GBSOption(TypeFlag = "c", S = 100, X = 90, Time = 1,
r = 0.02, b = -0.02, sigma = 0.3)
KO <- sapply(100:300, FUN = StandardBarrierOption, TypeFlag = "cuo",
S = 100, X = 90, K = 0, Time = 1, r = 0.02, b = -0.02, sigma = 0.30)
plot(KO[[1]]@price, type = "l",
xlab = "barrier distance from spot",
ylab = "price of option",
main = "Price of KO converges to plain vanilla")
abline(h = vanilla@price, col = "red")
以下输出是前面代码的结果:

类似地,当其中一个障碍变得不重要时,双障碍期权会收敛为单一障碍期权,如果两个障碍都变得不重要,它们会收敛为普通 vanilla 期权。
得益于前述的对称性,大多数情况下,找到 KO 期权的定价公式就足够了。尽管这非常有帮助,但定价 KO 期权往往仍然非常棘手。复制 KO 事件的技术是通过一种尝试建立一个由 vanilla 期权组成的投资组合,这些期权在击中事件发生时的价值恰好为零,因此在该时刻它们可以免费关闭。对此有两种著名的方法,分别由Derman-Ergener-Kani (1995)和Carr-Ellis-Gupta (1998)解释。
所谓的 Black-Scholes 曲面是一个三维图表,其中期权价格可以作为到期时间和标的价格的函数显示。由于一些复杂的定价函数在极端输入情况下可能会出现异常,因此建议利用我们的金融知识,期权价格永远不能低于零。
以下是 Black-Scholes 曲面的代码:
install.packages('plot3D')
BS_surface <- function(S, Time, FUN, ...) {
require(plot3D)
n <- length(S)
k <- length(Time)
m <- matrix(0, n, k)
for (i in 1:n){
for (j in 1:k){
l <- list(S = S[i], Time = Time[j], ...)
m[i,j] <- max(do.call(FUN, l)@price, 0)
}
}
persp3D(z = m, xlab = "underlying", ylab = "Remaining time",
zlab = "option price", phi = 30, theta = 20, bty = "b2")
}
BS_surface(seq(1, 200,length = 200), seq(0, 2, length = 200),
GBSOption, TypeFlag = "c", X = 90, r = 0.02, b = 0, sigma = 0.3)
前面代码的输出结果如下:

首先,我们准备了一个普通 vanilla 看涨期权的 Black-Scholes 曲面。然而,BS_surface代码可以用于更多的目的。就像 Black-Scholes 曲面的概念可以用于任何种类的单一标的相关衍生品一样,如果我们有一个定价函数,它可以作为FUN参数使用:
BS_surface(seq(1,200,length = 200), seq(0, 2, length = 200),
StandardBarrierOption, TypeFlag = "cuo", H = 130, X = 90, K = 0,
r = 0.02, b = -0.02, sigma = 0.30)
以下截图是前面代码的结果:

很容易看出,与普通 vanilla 看涨期权相比,上涨打破期权的价值是有限的。
在[第 156 页],我们使用相同的函数绘制了双触碰期权的 BS 曲面。
二元期权是一种外来期权,其支付方式是固定的应急支付。其名称来自于它只有两种可能的结果:要么支付一个固定金额,要么完全不支付。它们在期权世界中具有 0-1 关系。二元特性可以与障碍特性结合,从而变得路径依赖。单触及(OT)期权仅在其生命周期内发生敲击事件时支付,而不触及期权(No-Touch)则仅在没有发生敲击事件时支付。
二元期权可能与两个障碍相关,从而得到双一触及和双不触及期权。基于无套利原理,必须满足以下方程:
NT + OT = T-Bill
DNT + DOT = T-Bill
收敛性在这里也能看到,类似于我们之前展示的障碍案例。如果其中一个障碍足够远,DNT 会收敛到 NT;如果两个障碍都足够远,它会收敛到 T-Bill。DNT 的定价函数是二元期权中的“万金油”,类似于障碍类型的 DKO 期权。
回望期权也是路径依赖的。回望特性非常方便。到期时,持有者可以回顾并选择从标的物路径中得到的最佳价格。对于浮动利率回望,期权持有者可以回溯选择执行价格;对于固定利率回望,持有者可以在期权生命周期内选择任何价格进行行权。Taleb (1997)展示了如何通过无限链条的 KIKO 期权来复制回望期权。从这个意义上讲,这至少是第二代的外来期权,因为我们需要将外来期权作为构建模块来复制回望期权。
多个标的物也是一种常见的外来特性。两个例子已经在第五章的外汇衍生品部分讨论过,分别是交换期权和定量期权。然而,还有许多其他例子。最优和最差(也叫彩虹)期权从一篮子标的物中选择表现最好或最差的标的。价差期权非常类似于普通期权,不同之处在于此期权的标的是两种资产的差价。这只是一些例子,足以表明在所有这些情况下,相关性都起着重要作用。此外,这些特性还可以与障碍、回望或亚洲期权特性相结合,从而产生几乎无限的组合。在本章中,我们不会进一步讨论这些类型。
希腊字母 – 回到普通世界的连接
正如我们在本章介绍部分解释的那样,希腊字母是偏导数。一些重要的希腊字母如下:
-
delta:表示 DvalueDspot,指的是期权价格相对于标的现货价格变化的变化量
-
gamma:表示 DdeltaDspot
-
vega:表示 DvalueDvolatility
-
theta:表示 DvalueDtime
-
rho:表示 D 值的利率
在一些简单的情况下,这些偏导数可以通过解析方法找到。例如,fOptions包包括一个GBSGreeks函数,它提供了经典期权的希腊字母。
解析希腊字母非常方便;然而,它们存在两个问题。第一个问题是,市场交易的参数并不是在无穷小的增量中变化。例如,在纽约证券交易所,股票价格的最小变化为一美分。股票价格要么变化至少一美分,要么没有变化。在场外交易(OTC)外汇市场,交易者报价波动率时通常是 0.0005 的整数倍。第二个问题是,许多外来期权没有封闭的公式。我们仍然需要知道希腊字母,因为我们希望将它们加起来以获得整个投资组合的希腊字母。将解析希腊字母和数值希腊字母相加可能会导致错误,因此使用数值希腊字母是一种更安全的方法。
GetGreeks函数计算任何定价函数的希腊字母:
GetGreeks <- function(FUN, arg, epsilon,...) {
all_args1 <- all_args2 <- list(...)
all_args1[[arg]] <- as.numeric(all_args1[[arg]] + epsilon)
all_args2[[arg]] <- as.numeric(all_args2[[arg]] - epsilon)
(do.call(FUN, all_args1)@price -
do.call(FUN, all_args2)@price) / (2 * epsilon)
}
场外市场做市商不会以任何数量报价外汇波动率,但通常会作为 0.0005 的整数倍报价,典型的 AUDUSD 平值波动率报价为 5.95%/6.05%。当然,对于以价格报价而非波动率的交易所衍生品,价格变化隐含的波动率变化可能小于 0.0005。
因此,当我们计算 vega 时,应该将 epsilon 设置为 0.0005,这是市场一致的最小可能变化;例如,要计算 AUDUSD 期权的 delta,我们可以将 epsilon 设置为 0.0001(一个点),对于股票,我们可以将 epsilon 设置为 0.01(一美分)。调整 epsilon 为 1/365(一天)用于 theta,0.0001(一个基点)用于 rho 也是有用的。
以下代码绘制了FloatingStrikeLookbackOption的 delta、vega、theta 和 rho:
x <- seq(10, 200, length = 200)
delta <- vega <- theta <- rho <- rep(0, 200)
for(i in 1:200){
delta[i] <- GetGreeks(FUN = FloatingStrikeLookbackOption, arg = 2, epsilon = 0.01, "p", x[i], 100, 1, 0.02, -0.02, 0.2)
vega[i] <- GetGreeks(FUN = FloatingStrikeLookbackOption, arg = 7, epsilon = 0.0005, "p", x[i], 100, 1, 0.02, -0.02, 0.2)
theta[i] <- GetGreeks(FUN = FloatingStrikeLookbackOption, arg = 4, epsilon = 1/365, "p", x[i], 100, 1, 0.02, -0.02, 0.2)
rho[i] <- GetGreeks(FUN = FloatingStrikeLookbackOption, arg = 5, epsilon = 0.0001, "p", x[i], 100, 1, 0.02, -0.02, 0.2)
}
par(mfrow = c(2, 2))
plot(x, delta, type = "l", xlab = "S", ylab = "", main = "Delta")
plot(x, vega, type = "l", xlab = "S", ylab = "", main = "Vega")
plot(x, theta, type = "l", xlab = "S", ylab = "", main = "Theta")
plot(x, rho, type = "l", xlab = "S", ylab = "", main = "Rho")
上面的代码产生以下输出:

定价双重无触碰期权
双重无触碰(DNT)期权是一种二元期权,在到期时支付固定现金金额。不幸的是,fExoticOptions包目前没有此期权的公式。我们将展示两种不同的定价方式来定价 DNT,其中包含两种不同的定价方法。在本节中,我们将使用dnt1作为第一个方法的函数名,对于第二种方法,我们将使用dnt2作为函数名。
Hui (1996)展示了如何为单触及双障碍二元期权定价。在他的术语中,“单触及”意味着单一交易足以触发击穿事件,而“双障碍”二元期权意味着存在两个障碍,这是一种二元期权。我们称之为 DNT,因为它在外汇市场中被广泛使用。这是一个很好的例子,说明许多流行的另类期权有多种不同的名称。在Haug (2007a)中,Hui 公式已被转化为广义框架。S、r、b、σ和T的含义与第五章中的含义相同,外汇衍生品。K 表示支付金额(美元数额),而 L 和 U 分别是下限和上限障碍。

其中,
,
,
。
将Hui (1996)函数实现到 R 中时,首先面临一个大问号:我们该如何处理无限级数?我们应该用多大的数字来替代无穷大?有趣的是,对于实际用途,像 5 或 10 这样的较小数字通常能够很好地充当无穷大的角色。Hui (1996)指出,大多数情况下收敛是快速的。我们对此有些怀疑,因为α将作为指数使用。如果 b 为负且σ足够小,公式中的(S/L)^α部分可能会成为问题。
首先,我们将尝试使用常规参数,看看收敛速度如何:
dnt1 <- function(S, K, U, L, sigma, T, r, b, N = 20, ploterror = FALSE){
if ( L > S | S > U) return(0)
Z <- log(U/L)
alpha <- -1/2*(2*b/sigma² - 1)
beta <- -1/4*(2*b/sigma² - 1)² - 2*r/sigma²
v <- rep(0, N)
for (i in 1:N)
v[i] <- 2*pi*i*K/(Z²) * (((S/L)^alpha - (-1)^i*(S/U)^alpha ) /
(alpha²+(i*pi/Z)²)) * sin(i*pi/Z*log(S/L)) *
exp(-1/2 * ((i*pi/Z)²-beta) * sigma²*T)
if (ploterror) barplot(v, main = "Formula Error");
sum(v)
}
print(dnt1(100, 10, 120, 80, 0.1, 0.25, 0.05, 0.03, 20, TRUE))
以下截图显示了前述代码的结果:

公式误差图表显示,在第七步之后,额外的步骤对结果没有影响。这意味着在实际应用中,可以通过计算前七步来快速估算无限级数。这看起来确实是一个非常快速的收敛。然而,这可能是纯粹的运气或巧合。
如果将波动率降至 3%会怎样?我们必须将 N 设置为 50 才能看到收敛:
print(dnt1(100, 10, 120, 80, 0.03, 0.25, 0.05, 0.03, 50, TRUE))
上述命令给出的输出如下:

看起来不太令人印象深刻?50 步仍然不算太差。那如果将波动率降得更低呢?在 1%的波动率下,这些参数的公式会直接爆炸。首先,这看起来是灾难性的;然而,当我们使用 3%的波动率时,DNT 的价格已经是支付金额的 98.75%。逻辑上讲,DNT 价格应当是波动率的单调递减函数,因此我们已经知道,如果波动率低于 3%,DNT 的价格至少应该是 98.75%。
另一个问题是,如果我们选择极高的 U 或极低的 L,就会出现计算错误。然而,类似于波动性问题,常识在这里同样有效;如果我们将 U 提高或 L 降低,DNT 的价格应该会上升。
还有一个技巧。由于所有问题都来源于α参数,我们可以尝试将 b 设置为 0,这样α将等于 0.5。如果我们还将 r 设置为 0,DNT 的价格将随着波动率的下降而趋于 100%。
无论如何,每当我们用有限的和替代无限的和时,了解何时有效,何时无效总是很重要。我们编写了一个新代码,考虑到收敛并不总是迅速的。诀窍在于,只要最后一步产生了任何显著变化,函数就会计算下一步。对于所有参数来说,这仍然不是完全适用,因为对非常低波动性的情况没有解决办法,除非我们接受这样一个事实:如果隐含波动率低于 1%,这就是一个极端市场情形,在这种情况下,DNT 期权不应该通过这个公式来定价:
dnt1 <- function(S, K, U, L, sigma, Time, r, b) {
if ( L > S | S > U) return(0)
Z <- log(U/L)
alpha <- -1/2*(2*b/sigma² - 1)
beta <- -1/4*(2*b/sigma² - 1)² - 2*r/sigma²
p <- 0
i <- a <- 1
while (abs(a) > 0.0001){
a <- 2*pi*i*K/(Z²) * (((S/L)^alpha - (-1)^i*(S/U)^alpha ) / (alpha² + (i *pi / Z)²) ) * sin(i * pi / Z * log(S/L)) * exp(-1/2*((i*pi/Z)²-beta) * sigma² * Time)
p <- p + a
i <- i + 1
}
p
}
现在我们有了一个不错的公式,可以绘制一些与 DNT 相关的图表,以便更熟悉这种期权。稍后,我们将使用一个特定的 AUDUSD DNT 期权,其参数如下:L 等于 0.9200,U 等于 0.9600,K(支付金额)等于 100 万美元,T 等于 0.25 年,波动率等于 6%,r_AUD 等于 2.75%,r_USD 等于 0.25%,b 等于-2.5%。我们将计算并绘制从 0.9200 到 0.9600 的所有可能的 DNT 值;每步为一个点(0.0001),因此我们将使用 2000 步。
以下代码绘制了基础资产价格的图表:
x <- seq(0.92, 0.96, length = 2000)
y <- z <- rep(0, 2000)
for (i in 1:2000){
y[i] <- dnt1(x[i], 1e6, 0.96, 0.92, 0.06, 0.25, 0.0025, -0.0250)
z[i] <- dnt1(x[i], 1e6, 0.96, 0.92, 0.065, 0.25, 0.0025, -0.0250)
}
matplot(x, cbind(y,z), type = "l", lwd = 2, lty = 1,
main = "Price of a DNT with volatility 6% and 6.5%
", cex.main = 0.8, xlab = "Price of underlying" )
以下输出是前述代码的结果:

很明显,即使是波动性的小幅变化也会对 DNT 的价格产生巨大影响。通过查看这张图,我们可以直观地发现 vega 必须是负值。有趣的是,甚至仅仅快速浏览这张图,也足以让我们相信,当我们接近障碍时,vega 的绝对值在减少。
大多数终端用户认为最大风险是在现货接近触发点时。这是因为终端用户通常以二元的方式来考虑二元期权。只要 DNT 还有效,他们就会专注于积极的结果。然而,对于动态对冲者来说,当 DNT 的价值已经很小的时候,DNT 的风险就不那么引人关注了。
另一个非常有趣的现象是,由于 T-Bill 价格与波动性无关,并且 DNT + DOT = T-Bill 公式成立,因此波动性的增加将以完全相同的金额降低 DNT 的价格,就像它会增加 DOT 的价格一样。毫不奇怪,DOT 的 vega 应该是 DNT 的精确镜像。
我们可以使用GetGreeks函数来估算维加、伽玛、德尔塔和塞塔。对于伽玛,我们可以通过以下方式使用 GetGreeks 函数:
GetGreeks <- function(FUN, arg, epsilon,...) {
all_args1 <- all_args2 <- list(...)
all_args1[[arg]] <- as.numeric(all_args1[[arg]] + epsilon)
all_args2[[arg]] <- as.numeric(all_args2[[arg]] - epsilon)
(do.call(FUN, all_args1) -
do.call(FUN, all_args2)) / (2 * epsilon)
}
Gamma <- function(FUN, epsilon, S, ...) {
arg1 <- list(S, ...)
arg2 <- list(S + 2 * epsilon, ...)
arg3 <- list(S - 2 * epsilon, ...)
y1 <- (do.call(FUN, arg2) - do.call(FUN, arg1)) / (2 * epsilon)
y2 <- (do.call(FUN, arg1) - do.call(FUN, arg3)) / (2 * epsilon)
(y1 - y2) / (2 * epsilon)
}
x = seq(0.9202, 0.9598, length = 200)
delta <- vega <- theta <- gamma <- rep(0, 200)
for(i in 1:200){
delta[i] <- GetGreeks(FUN = dnt1, arg = 1, epsilon = 0.0001,
x[i], 1000000, 0.96, 0.92, 0.06, 0.5, 0.02, -0.02)
vega[i] <- GetGreeks(FUN = dnt1, arg = 5, epsilon = 0.0005,
x[i], 1000000, 0.96, 0.92, 0.06, 0.5, 0.0025, -0.025)
theta[i] <- - GetGreeks(FUN = dnt1, arg = 6, epsilon = 1/365,
x[i], 1000000, 0.96, 0.92, 0.06, 0.5, 0.0025, -0.025)
gamma[i] <- Gamma(FUN = dnt1, epsilon = 0.0001, S = x[i], K =
1e6, U = 0.96, L = 0.92, sigma = 0.06, Time = 0.5, r = 0.02, b = -0.02)
}
windows()
plot(x, vega, type = "l", xlab = "S",ylab = "", main = "Vega")
以下图表是上面代码的结果:

看过价值图表后,可以发现 DNT 的德尔塔值也与直觉非常接近;如果接近上边界,德尔塔变为负值;而接近下边界时,德尔塔变为正值,如下所示:
windows()
plot(x, delta, type = "l", xlab = "S",ylab = "", main = "Delta")

这真的是一种非凸的情况;如果我们想进行动态德尔塔对冲,肯定会亏钱。如果现货价格上涨,DNT 的德尔塔会减少,所以我们应该购买一些 AUDUSD 来对冲。然而,如果现货价格下跌,我们应该卖出一些 AUDUSD。想象一个场景,上午 AUDUSD 上涨 20 个点,下午又下跌 20 个点。对于动态对冲者来说,这意味着在价格上涨后购买一些 AUDUSD,然后在价格下跌后卖出同样数量的 AUDUSD。
德尔塔的变化可以通过伽玛描述,如下所示:
windows()
plot(x, gamma, type = "l", xlab = "S",ylab = "", main = "Gamma")

负伽玛意味着如果现货上涨,我们的德尔塔会减少;而如果现货下跌,我们的德尔塔会增加。这听起来并不理想。对于这种不方便的非凸情况,有一定的补偿,即塞塔值为正。如果没有其他变化,只有一天过去,DNT 将自动变得更值钱。
在这里,我们使用塞塔等于负 1 倍的偏导数,因为如果(T-t)是剩余时间,我们检查随着 t 增加一天,价值的变化:
windows()
plot(x, theta, type = "l", xlab = "S",ylab = "", main = "Theta")

伽玛越负,塞塔越正。这就是时间如何弥补负伽玛可能带来的损失。
风险中性定价也意味着负伽玛应该由正塞塔进行补偿。这是 Black-Scholes 框架在普通期权中的主要信息,但同样适用于衍生品。参见Taleb (1997)和Wilmott (2006)。
我们之前已经介绍过 Black-Scholes 曲面;现在可以更详细地讨论。这个曲面也是一个很好的解释,说明了塞塔和德尔塔如何工作。它展示了期权在不同现货价格和到期时间下的价格,因此,这个曲面的斜率代表了一个方向上的塞塔和另一个方向上的德尔塔。代码如下:
BS_surf <- function(S, Time, FUN, ...) {
n <- length(S)
k <- length(Time)
m <- matrix(0, n, k)
for (i in 1:n) {
for (j in 1:k) {
l <- list(S = S[i], Time = Time[j], ...)
m[i,j] <- do.call(FUN, l)
}
}
persp3D(z = m, xlab = "underlying", ylab = "Time",zlab = "option price", phi = 30, theta = 30, bty = "b2")
}
BS_surf(seq(0.92,0.96,length = 200), seq(1/365, 1/48, length = 200),dnt1, K = 1000000, U = 0.96, L = 0.92, r = 0.0025, b = -0.0250,sigma = 0.2)
上面的代码给出了以下输出:

我们可以看到之前的猜测得到了验证;DNT 更倾向于时间流逝且现货价格接近(L,U)区间的中点。
另一种定价双无触及期权的方法
静态复制通常是最优雅的定价方式。无套利理论告诉我们,如果未来某一时刻,两组投资组合的价值必定相等,那么在此之前的任何时刻,它们的价格应该也相等。我们将展示如何利用双击敲出(DKO)期权来构建 DNT。我们需要用到一个技巧:执行价可以与某个障碍位相同。对于 DKO 看涨期权,执行价应低于上方障碍位,因为如果执行价不低于上方障碍位,DKO 看涨期权会在成为实值期权之前被敲出,因此该期权将毫无价值,因为没人能以实值行使它。然而,我们可以选择将执行价设为下方障碍位。对于看跌期权,执行价应高于下方障碍位,那为什么不将其设为上方障碍位呢?这样,DKO 看涨期权和 DKO 看跌期权将具有一个非常方便的特点:如果它们仍然有效,它们都会以实值到期。
现在,我们差不多完成了。我们只需要加上 DKO 价格,就能得到一份具有(U-L)美元支付的 DNT。由于 DNT 价格与支付金额是线性关系,我们只需要将结果乘以 K*(U-L):
dnt2 <- function(S, K, U, L, sigma, T, r, b) {
a <- DoubleBarrierOption("co", S, L, L, U, T, r, b, sigma, 0,
0,title = NULL, description = NULL)
z <- a@price
b <- DoubleBarrierOption("po", S, U, L, U, T, r, b, sigma, 0,
0,title = NULL, description = NULL)
y <- b@price
(z + y) / (U - L) * K
}
现在,我们有两个函数可以进行结果比较:
dnt1(0.9266, 1000000, 0.9600, 0.9200, 0.06, 0.25, 0.0025, -0.025)
[1] 48564.59
dnt2(0.9266, 1000000, 0.9600, 0.9200, 0.06, 0.25, 0.0025, -0.025)
[1] 48564.45
对于一份拥有 100 万美元应急支付且初始市场价值超过 48,000 美元的 DNT 来说,看到价格差仅为 14 美分是非常令人欣慰的。然而,从技术上讲,拥有第二个定价函数并不是很有帮助,因为低波动性也是dnt2面临的问题。
本章剩余部分我们将使用dnt1。
双触发期权的生命周期 - 一次模拟
DNT 价格在 2014 年第二季度是如何变化的?我们拥有 AUDUSD 的开盘-最高-最低-收盘类型的时间序列数据,频率为五分钟,因此我们知道所有的极端价格:
d <- read.table("audusd.csv", colClasses = c("character", rep("numeric",5)), sep = ";", header = TRUE)
underlying <- as.vector(t(d[, 2:5]))
t <- rep( d[,6], each = 4)
n <- length(t)
option_price <- rep(0, n)
for (i in 1:n) {
option_price[i] <- dnt1(S = underlying[i], K = 1000000, U = 0.9600, L = 0.9200, sigma = 0.06, T = t[i]/(60*24*365), r = 0.0025, b = -0.0250)
}
a <- min(option_price)
b <- max(option_price)
option_price_transformed = (option_price - a) * 0.03 / (b - a) + 0.92
par(mar = c(6, 3, 3, 5))
matplot(cbind(underlying,option_price_transformed), type = "l",
lty = 1, col = c("grey", "red"),
main = "Price of underlying and DNT",
xaxt = "n", yaxt = "n", ylim = c(0.91,0.97),
ylab = "", xlab = "Remaining time")
abline(h = c(0.92, 0.96), col = "green")
axis(side = 2, at = pretty(option_price_transformed),
col.axis = "grey", col = "grey")
axis(side = 4, at = pretty(option_price_transformed),
labels = round(seq(a/1000,1000,length = 7)), las = 2,
col = "red", col.axis = "red")
axis(side = 1, at = seq(1,n, length=6),
labels = round(t[round(seq(1,n, length=6))]/60/24))
以下是前述代码的输出:

DNT 价格在右轴上以红色显示(单位为千分之一),而实际的 AUDUSD 价格在左轴上以灰色显示。绿色线条代表 0.9200 和 0.9600 的障碍位。图表显示,在 2014 年第二季度,AUDUSD 货币对在(0.9200; 0.9600)区间内交易,因此 DNT 的支付将为 100 万美元。这份 DNT 看起来是一个非常好的投资;然而,现实只是几乎无限多轨迹中的一个。它本来可能会发生得不一样。例如,在 2014 年 5 月 2 日,距离到期还有 59 天,AUDUSD 交易价为 0.9203,离下方障碍位仅差三点。当时,这份 DNT 的价格只有 5,302 美元,以下代码显示了这一点:
dnt1(0.9203, 1000000, 0.9600, 0.9200, 0.06, 59/365, 0.0025, -0.025)
[1] 5302.213
将这 USD 5,302 与最初的 USD 48,564 期权价格进行比较!
在接下来的模拟中,我们将展示一些不同的轨迹。所有轨迹都从 4 月 1 日黎明时的 0.9266 AUDUSD 即期价格开始,我们将查看其中有多少轨迹保持在 (0.9200; 0.9600) 区间内。为了简化,我们将使用与定价 DNT 时相同的 6% 波动率来模拟几何布朗运动:
library(matrixStats)
DNT_sim <- function(S0 = 0.9266, mu = 0, sigma = 0.06, U = 0.96,
L = 0.92, N = 5) {
dt <- 5 / (365 * 24 * 60)
t <- seq(0, 0.25, by = dt)
Time <- length(t)
W <- matrix(rnorm((Time - 1) * N), Time - 1, N)
W <- apply(W, 2, cumsum)
W <- sqrt(dt) * rbind(rep(0, N), W)
S <- S0 * exp((mu - sigma² / 2) * t + sigma * W )
option_price <- matrix(0, Time, N)
for (i in 1:N)
for (j in 1:Time)
option_price[j,i] <- dnt1(S[j,i], K = 1000000, U, L, sigma,
0.25-t[j], r = 0.0025,
b = -0.0250)*(min(S[1:j,i]) > L & max(S[1:j,i]) < U)
survivals <- sum(option_price[Time,] > 0)
dev.new(width = 19, height = 10)
par(mfrow = c(1,2))
matplot(t,S, type = "l", main = "Underlying price",
xlab = paste("Survived", survivals, "from", N), ylab = "")
abline( h = c(U,L), col = "blue")
matplot(t, option_price, type = "l", main = "DNT price",
xlab = "", ylab = "")}
set.seed(214)
system.time(DNT_sim())
以下是前述代码的输出:

在这里,唯一存活的轨迹是红色的;在所有其他情况下,DNT 要么触碰到上界,要么触碰到下界。set.seed(214) 这一行确保了每次运行时模拟结果都是一致的。五分之一的存活率还不算太差;它表明对于没有进行动态对冲的最终用户或赌徒来说,这个期权的估算价值大约是支付额的 20%(尤其是在利率较低、货币时间价值不重要的情况下)。
然而,五条轨迹仍然太少,不能轻易得出结论。我们应该检查更多轨迹下的 DNT 生存率。
存活轨迹的比率可以作为该 DNT 在实际世界中的生存概率的良好估算;因此,它的最终用户价值。在迅速增加 N 的同时,我们应该记住这次模拟花费了多少时间。对于我的计算机,N = 5 时花费了 50.75 秒,N = 15 时花费了 153.11 秒。
以下是 N = 15 时的输出:

现在,15 条轨迹中有 3 条存活,因此估算的生存比率仍然是 3/15,等于 20%。看起来这是一个非常不错的产品;价格大约是支付额的 5%,而生存比率估算为 20%。出于好奇,运行一次 N 等于 200 的模拟。这应该会花费大约 30 分钟。
以下是 N = 200 时的输出:

结果令人震惊;现在,200 条轨迹中只有 12 条存活,生存比率仅为 6%!因此,为了获得更好的结果,我们应该对更大的 N 进行模拟。
伍迪·艾伦(Woody Allen)执导的电影 Whatever Works(由拉里·大卫主演)长 92 分钟;在模拟时间中,这相当于 N = 541。对于这个 N = 541,只有 38 条轨迹存活,生存比率为 7%。

那么,真正的预期存活比率是多少?是 20%、6%还是 7%?我们现在根本不知道。数学家提醒我们,大数法则要求较大的样本量,其中“较大”远远超过 541,因此,建议在时间允许的情况下,尽可能使用更大的 N 来运行这个模拟。当然,获取更好的计算机也有助于在相同的时间内进行更多的 N。无论如何,从这个角度看,Hui(1996) 相对较快收敛的 DNT 定价公式得到了些许尊重。
到目前为止,我们使用了与模拟中相同的随机过程进行定价。常识告诉我们,在某些情况下,市场隐含波动率可能会偏高或偏低于预期波动率。毫不奇怪,在这两种条件下运行模拟,N = 200 和 sigma = 5.5%的情况下,结果是更多的存活轨迹,15 条轨迹来自这个种子。而在 N = 200 和 sigma = 6.5%的情况下,结果是较少的存活轨迹:这个种子只有九条轨迹。这再次以非常直观的方式显示了 vega 的高影响力。存活轨迹的数量,可能是 9、12 或 15,主要取决于过程的波动性。存活率为 4.5%、6%或 7.5%。这也引发了一个更哲学性的问题:那风险溢价呢?如果市场需要 vega,可能会出现即使我们预期波动率为 5.5%,也能基于 6%波动率购买 DNT 的情况。在一些紧张的情况下,市场可能真的很渴望 vega。在这些情况下,风险溢价是包含在内的。
衍生品定价总是假设动态对冲,因为我们在寻找这种工具的边际生产成本,然后使用无套利理论。实际上,一些市场参与者正在尝试实施这一策略,并成为衍生品的供应商,就像工厂一样。他们愿意接受任何交易的一方,因为通过几乎持续的动态对冲,他们将消除几乎所有的风险。他们就是市场做市商。然而,并非所有市场参与者都是衍生品工厂;其中有许多人故意寻求敏感性,因此,他们并没有对冲他们的衍生品头寸。第二组被称为市场接受者或终端用户。他们中的一些人寻找敏感性,因为他们已经有了一些,并希望减少这些(自然对冲者)。另一些人最初没有任何敏感性,但希望进行金融投机(投机者)。
有趣的是,衍生品的价格和最终用户的价值之间可能存在显著差异。通过购买 DNT,最终用户可以进行投注,最终要么什么都得不到,要么赢得比最初价格更多的收益。那么,这个赌注是否存在风险溢价,还是类似于赌场?DNT 的实际预期值是否高于风险中性预期值(即价格)?使用价值或“用户体验”可能会有所不同,因为做市商将根据隐含波动率报价。在市场紧张的情况下,对 Vega 的需求可能会推动其价格(即隐含波动率)高于预期波动率。
在这种情况下,任何仍然能够卖出波动率的人都会获得溢价。对于 DNT 来说,获得溢价意味着它的价格将低于其实际预期支付的价值。
那么,双触及期权(DOT)怎么样呢?由于国库券可以看作是 DNT 和 DOT 的总和,如果 DNT 太便宜,则 DOT 一定会太贵。因此,这些异国期权是对波动率的简单投注;如果投机者认为波动率会显著低于隐含波动率,那么购买 DNT 是一种直接的投注。如果投机者预期波动率高于隐含波动率,则 DOT 是合适的投注方式。
从这个角度来看,DNT(双触及期权)类似于一个短跨式期权,而 DOT(双触及期权)则类似于一个长跨式期权;然而,二元期权更容易调整到所需的规模。一个长跨式期权由同等规模、行权价和到期日的长看涨期权和长看跌期权组成。短跨式期权则是镜像:短看涨期权和短看跌期权。一个跨式期权与宽跨式期权非常相似,唯一的区别在于看涨期权的行权价高于看跌期权的行权价。相比于短跨式期权或宽跨式期权,购买 DNT 进行波动率投注要方便得多,因为持有一个长 DNT 期权头寸不需要进一步的抵押调整。DNT 是一种高度杠杆化的产品;然而,最大损失金额已经提前支付,因此它适合在线交易平台的菜单,其中典型客户是小型零售投机者。
基于这一逻辑,风险溢价只会归那些愿意采取不利于其他市场参与者的头寸的玩家。如果波动率需求增加,那么 DNT 将包含风险溢价,但如果波动率供应增加,则 DOT 将包含风险溢价。也有可能市场处于稳定的均衡状态,既没有 DNT 也没有 DOT 包含任何风险溢价。
嵌入结构化产品中的异国期权
大多数情况下,复杂期权以伪装的方式进行交易;它们被嵌入到结构化债券或证书中。复杂的行为被转化为一种更加用户友好的语言,普通投资者更容易理解。例如,二元支付可以转化为票息收益;如果条件允许二元期权支付收益,投资者将获得更高的票息。包含敲出期权的结构可以被称为气囊证书,因为只要长期 KO 期权没有被敲出,它就能提供一些对抗市场损失的保护,类似于气囊在较轻微事故中保护驾驶员。
另一个例子是涡轮证书,它通常只是深度实值行权价的敲出期权的证券化形式,敲出点接近行权价。回望期权可以在与股票指数极端值挂钩的资本保障型产品中找到。
作为一个数值示例,我们来看一份三个月到期的定期存单(CD),它根据外汇市场的表现,支付 3%的票息或 0%。这种资本保障型产品可以看作是国债票据和二元期权的组合。如果这只三个月的国债票据能以 99.75%的价格购买,那么每一美元上有 0.25 美分可以用来购买二元期权。到期时的资本将由国债票据部分提供,而二元期权将负责支付有条件的 3%票息。
此时,任何二元期权都能起作用;购买 DNT 也可以,但参数太多。银行必须精细调整所有参数,以使整个结构对投资者有吸引力。在市场做市商的风险中性世界里,一个触发点为 L=0.9200、3 个月到期的期权与 L=0.9195、略长于 3 个月到期的期权几乎相同:
dnt1(0.9266, 1000000, 0.9600, 0.9200, 0.06, 90/365, 0.0025, -0.025)
[1] 50241.58
dnt1(0.9266, 1000000, 0.9600, 0.9195, 0.06, 94/365, 0.0025, -0.025)
[1] 50811.61
这是期权中一个非常常见的特征,包括敲出事件;额外的时间通常可以补偿将障碍进一步推离现货价格的行为。在风险中性世界中,S/L 距离总是被一个因子所除,因此存在权衡;我们可以降低 L,但作为交换,我们应该增加到期时间。在现实世界中,最终用户的预期是由他们的主观或感知概率驱动的。假设我们不打算动态对冲我们的 DNT,我们更倾向于选择 L = 0.9195 和 T = 94 天,而不是 L = 0.9200 和 T = 90 天。
这就是为什么 L、U 和 T 应设置为有利于让产品对最终用户具有吸引力的方式。而且,如果复杂期权嵌入到某个结构中,结构本身也应当容易销售。最终,大多数结构将被切分成更小的零售化产品,如 1000 美元的名义金额。当然,每一份“蛋糕”将是相同的,因此对银行来说,它可以看作是一个巨大的产品。
回到设置 L、U 和 T 的问题,我们很容易看出,DNT 的价格是 L、U 和 T(以及波动率)的严格单调函数。在特定的市场条件下(S、r、b 和波动率),假设我们设置 L = 0.9195 和 T = 94 天。现在,我们可以提出以下反向定价问题:对于什么 U,DNT 的价格将是支付金额的 33%?
这将是隐含的上限障碍,所谓隐含是指价格已经给定。这里有一个奇怪的答案:并不确定是否存在这样的隐含 U!这是因为如果我们开始增加上限障碍,DNT 的价格将收敛于无触及(No-Touch,NT)期权的价格。如果这个 NT 的价值小于 33%,那么没有 U 能让我们的 DNT 价值达到 33%。我们使用fExoticOptions包中的BinaryBarrierOption函数来定价无触及期权,相关代码如下所示:
dnt1(0.9266, 1000000, 1.0600, 0.9200, 0.06, 94/365, 0.0025, -0.025)
[1] 144702
a <- BinaryBarrierOption(9, 0.9266, 0, 0.9200, 1000000, 94/365, 0.0025, -0.025, 0.06, 1, 1, title = NULL, description = NULL)
(z <- a@price)
[1] 144705.3
在风险中性世界中,如果我们将 U 提高 1000 个点,它将变得几乎完全不相关,因此 DNT 的行为就像一个 NT。
因此,在这种情况下,如果我们希望 DNT 的成本为 33%,我们应该选择一个低于 0.9195 的 L。接下来,我们设置 L = 0.9095 并找到一个 U,使得 DNT 的价值为 33%。在这一部分结束时,我们将展示如何使用implied_U_DNT函数来找到隐含的 U,相关代码如下所示。现在,假设我们出于其他原因选择了 U = 0.9745。
dnt1(0.9266, 100, 0.9705, 0.9095, 0.06, 90/365, 0.0025, -0.025)
[1] 31.44338
这个 DNT 只需要支付 31.44%的支付金额,因此银行仍然有一定的空间从所有辛苦的结构工作中获得一些利润。假设银行能够销售总计 1 亿美元的这种 CD,那么 3 个月后,银行必须向客户支付 1 亿美元(年利率 0%)或 1.0075 亿美元(大约年利率 3%)。这个或有承诺可以通过购买 1 亿美元名义的国库券和支付 75 万美元的 DNT 期权进行对冲。最初,这些工具的成本是银行99.75%100.000.000+31.44338%750.000 = 99.985.825,35 美元;因此,银行获利 14,174.65 美元。
在其他情况下,隐含的到期时间可能是一个有趣的问题。在特定的市场条件下(当 S、r、b 和波动率已知)对于给定的(L,U)对,什么是能让 DNT 成本达到 50%的 T?即使对于一个非常紧密的(L-U)区间,我们也能找到一个足够小的 T,使得 DNT 的价格达到 50%;反之亦然;即使是一个非常宽的(L,U)对,只要有足够的时间,DNT 的价值也会降到 50%。请参见本节末尾的implied_T_DNT。
与 L、U 或 T 不同,我们不能故意选择波动率参数;然而,计算隐含波动率对于定价其他衍生品可能是有用的。这是一个关键的定价概念;风险中性定价基于比较。如果我们知道一个 DNT 期权的价格(以及所有其他参数),我们就可以找出用于定价的波动率。请参见本节末尾的implied_vol_DNT。
接下来,我们将展示许多隐含函数,并最终绘制隐含图表:
implied_DNT_image <- function(S = 0.9266, K = 1000000, U = 0.96,
L = 0.92, sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250) {
S_ <- seq(L,U,length = 300)
K_ <- seq(800000, 1200000, length = 300)
U_ <- seq(L+0.01, L + .15, length = 300)
L_ <- seq(0.8, U - 0.001, length = 300)
sigma_ <- seq(0.005, 0.1, length = 300)
T_ <- seq(1/365, 1, length = 300)
r_ <- seq(-10, 10, length = 300)
b_ <- seq(-0.5, 0.5, length = 300)
p1 <- lapply(S_, dnt1, K = 1000000, U = 0.96, L = 0.92,
sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250)
p2 <- lapply(K_, dnt1, S = 0.9266, U = 0.96, L = 0.92,
sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250)
p3 <- lapply(U_, dnt1, S = 0.9266, K = 1000000, L = 0.92,
sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250)
p4 <- lapply(L_, dnt1, S = 0.9266, K = 1000000, U = 0.96,
sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250)
p5 <- lapply(sigma_, dnt1, S = 0.9266, K = 1000000, U = 0.96,
L = 0.92, Time = 0.25, r = 0.0025, b = -0.0250)
p6 <- lapply(T_, dnt1, S = 0.9266, K = 1000000, U = 0.96, L = 0.92, sigma = 0.06, r = 0.0025, b = -0.0250)
p7 <- lapply(r_, dnt1, S = 0.9266, K = 1000000, U = 0.96, L = 0.92, sigma = 0.06, Time = 0.25, b = -0.0250)
p8 <- lapply(b_, dnt1, S = 0.9266, K = 1000000, U = 0.96, L = 0.92, sigma = 0.06, Time = 0.25, r = 0.0025)
dev.new(width = 20, height = 10)
par(mfrow = c(2, 4), mar = c(2, 2, 2, 2))
plot(S_, p1, type = "l", xlab = "", ylab = "", main = "S")
plot(K_, p2, type = "l", xlab = "", ylab = "", main = "K")
plot(U_, p3, type = "l", xlab = "", ylab = "", main = "U")
plot(L_, p4, type = "l", xlab = "", ylab = "", main = "L")
plot(sigma_, p5, type = "l", xlab = "", ylab = "", main = "sigma")
plot(T_, p6, type = "l", xlab = "", ylab = "", main = "Time")
plot(r_, p7, type = "l", xlab = "", ylab = "", main = "r")
plot(b_, p8, type = "l", xlab = "", ylab = "", main = "b")
}
implied_vol_DNT <- function(S = 0.9266, K = 1000000, U = 0.96, L = 0.92, Time = 0.25, r = 0.0025, b = -0.0250, price) {
f <- function(sigma)
dnt1(S, K, U, L, sigma, Time, r, b) - price
uniroot(f, interval = c(0.001, 100))$root
}
implied_U_DNT <- function(S = 0.9266, K = 1000000, L = 0.92, sigma = 0.06, Time = 0.25, r = 0.0025, b = -0.0250, price = 4) {
f <- function(U)
dnt1(S, K, U, L, sigma, Time, r, b) - price
uniroot(f, interval = c(L+0.01, L + 100))$root
}
implied_T_DNT <- function(S = 0.9266, K = 1000000, U = 0.96, L = 0.92, sigma = 0.06, r = 0.0025, b = -0.0250, price = 4){
f <- function(Time)
dnt1(S, K, U, L, sigma, Time, r, b) - price
uniroot(f, interval = c(1/365, 100))$root
}
library(rootSolve)
implied_DNT_image()
print(implied_vol_DNT(price = 6))
print(implied_U_DNT(price = 4))
print(implied_T_DNT(price = 30))
以下是前面代码的输出:

总结
我们在本章开始时介绍了奇异期权。在简要的理论总结中,我们解释了奇异期权和普通期权是如何相互关联的。奇异期权有很多类型。我们展示了一种与fExoticOptions包一致的分类方法。我们展示了如何构建 Black-Scholes 表面(一个三维图表,包含依赖于时间和标的价格的衍生品价格),适用于任何定价函数。
奇异期权的定价只是第一步。市场做市商在他们的交易簿中持有成千上万种不同的期权。这只有在每个期权可以分解为某些敏感度(即所谓的希腊字母)时才有可能。作为偏导数,希腊字母具有可加性;因此,衍生品组合的希腊字母之和等于其组成部分的希腊字母之和。下一步是估算任何衍生品定价函数的希腊字母。我们的方法可以根据实际市场情况进行校准;对于许多参数,我们已经知道最小的变化量。例如,银行间 AUDUSD 外汇汇率的最小变化是 0.0001。即使是多个偏导数,比如 gamma 或 vanna,也可以通过这种数值方法计算出来。
在本章的下半部分,我们集中讨论了一种特殊的奇异期权:双无触(DNT)二元期权。之所以专注于 DNT 期权,主要是因为 DNT 期权的流行性,以及它们中包含的许多技巧,这些技巧对许多其他类型的奇异期权也具有参考意义。我们展示了两种定价 DNT 期权的方法。首先,我们实现了Hui (1996)的封闭形式解,其中价格是一个无限级数的结果。收敛速度通常非常快,但这并非总是如此。我们展示了一种实际的方法,说明如何在不浪费太多计算时间的情况下处理收敛问题。定价 DNT 的另一种方式是通过静态复制,一个 DKO 看涨期权和一个 DKO 看跌期权来实现。为了定价这些 DKO 期权,我们使用了fExoticOptions包。我们发现两种 DNT 定价方法的结果几乎没有区别。
我们通过使用 2014 年第二季度的 AUDUSD 外汇汇率的 5 分钟频率开盘-最低-最高-收盘类型时间序列,展示了 DNT 期权在实际数据中的表现。我们通过模拟估算了 DNT 的生存概率,以展示如何根据波动率的供需紧张情况将风险溢价纳入 DNT 或 DOT 中。最后,我们展示了一些实用的微调方法,通过引入函数来找到隐含参数,以便在构建结构化产品时找到具有特定价格的 DNT 的缺失参数。
参考文献
-
Black, F. 和 Scholes, M. [1973]: 期权定价与企业负债,《政治经济学杂志》,81(3),第 637-654 页
-
Carr, P., Ellis, K. 和 Gupta, V. [1998]: 外来期权的静态对冲,《金融学杂志》,53,第 1165-1190 页
-
Derman, E., Ergener, D. 和 Kani, I. [1995]: 静态期权复制,《衍生品杂志》,2(4),第 78-95 页
-
DeRosa, D. F. [2011]: 外汇期权。Wiley Finance
-
Haug, E. G. [2007a]: 期权定价公式完全指南,第二版。麦格劳-希尔公司
-
Haug, E. G. [2007b]: 衍生品模型的模型。John Wiley & Sons
-
Hui, C. H. [1996]: 单触式双障碍二元期权价值,《应用金融经济学》,1996 年,第 6 期,第 343-346 页
-
Merton, R. [1973]: 理性期权定价理论,《贝尔经济学与管理科学杂志》,4(1),第 141-183 页
-
Taleb, N. N., [1997]: 动态对冲。John Wiley & Sons
-
Wilmott, P., [2006]: 量化金融,第二版。John Wiley & Sons
第八章:最优对冲
在前几章讨论了理论背景后,我们将重点讨论衍生品交易中的一些实际问题。
衍生品定价,如Daróczi 等人(2013)所详细描述,第六章,衍生品定价,基于一个由交易证券组成的复制组合,该组合提供与衍生品资产相同的现金流。换句话说,衍生品的风险可以通过持有一定数量的基础资产和无风险债券来完美对冲。远期和期货合约可以静态对冲,而期权的对冲则需要不时地重新平衡组合。布莱克-舒尔斯-默顿(BSM)模型(布莱克和舒尔斯,1973,默顿,1973)所呈现的完美动态对冲在现实中存在一些局限性。
本章中,我们将深入探讨静态和动态环境下衍生品的对冲细节。将介绍离散时间交易的效果以及交易成本的存在。在离散时间对冲的情况下,期权的合成复制成本变得具有随机性;因此,风险和交易成本之间存在显著的权衡。最优对冲期根据优化的不同目标来推导,并且不仅受到市场因素的影响,还受到投资者特定参数(如风险厌恶)的影响。
衍生品的对冲
对冲是指创建一个能够抵消原始风险敞口的组合。由于风险是通过未来现金流的波动来衡量的,因此对冲的目标通常是减少整个组合价值的方差。Daróczi 等人(2013)的第一章提出了在存在基差风险的情况下的最佳对冲决策,即对冲工具与待对冲头寸不同。这种情况通常发生在商品风险对冲中,因为商品是在交易所交易的,交易所只提供标准化的(到期日、数量和质量)合约。
最优对冲比率是对冲工具占风险敞口的比例,其目的是最小化整个头寸的波动性。在本章中,我们将讨论衍生品头寸的对冲,假设基础资产也在场外交易市场交易;因此,风险敞口和对冲衍生品之间不会存在不匹配,也就不会产生基差风险。
衍生品的市场风险
远期或期货合约的价值取决于基础资产的现货价格、到期时间、无风险利率和行权价格;对于普通期权,基础资产的波动率也会影响期权价格。该说法仅在基础资产在衍生品交易到期之前没有现金流(没有收入和没有成本)的情况下成立;否则,这些(包括收入和支出)现金流也会影响价格。为了简化起见,本文将假设没有现金流(如非分红股票)来讨论衍生品定价,尽管将模型扩展到其他基础资产(如货币和商品)时,公式需要做一些修改,但基本逻辑不受影响。由于行权价格在到期期间是稳定的,因此只有其他四个因素的变化才会导致衍生品价值的变化。衍生品对这些变量的敏感度通过希腊字母来表示,即根据给定变量的偏导数,详细内容请参见 Daróczi 等人(2013) 第六章,衍生品定价。
Black-Scholes-Merton 模型假设无风险利率和基础资产的波动率是常数,因此随着时间变化是确定性的,唯一影响衍生品价值的随机变量是基础资产的现货价格。由现货价格波动引发的风险可以通过持有确切的 delta 数量来消除,delta 是衍生品价格对基础资产现货价格的敏感度(见方程式 1):

方程式 1
无论 delta 是否稳定或随时间变化,都取决于导数,并导致不同的(静态或动态)对冲策略(Hull, 2009)在以下章节中进行展示。
静态 delta 对冲
远期协议的对冲是直接的,因为它对双方都有约束力。处于长期远期头寸时,我们可以确定在到期时将会买入,而空头头寸则意味着将基础资产卖出。因此,我们可以通过按衍生品数量卖出(长期远期)或买入(短期远期)基础资产来完美对冲我们的远期头寸。我们可以通过对长期远期头寸的价值求导来检查远期的 delta。

方程式 2
这里,LF 代表长期远期,S 表示现货价格,K 是行权价格,即约定的远期价格。现值由 PV 表示。
所以 delta 等于 1,并且与实际市场状况无关。
然而,期货合约的价值是实际期货价格(F)与行权价格(S)之间的差异,因为有每日的头寸结算;因此,它的 delta 是 F/S 并且会随着时间变化。因此,可能需要稍微调整头寸,但在没有随机利率的情况下,delta 过程是可以预见的(Hull, 2009)。
动态 delta 对冲
对于期权来说,标的资产的交割是不确定的。它取决于持有多头头寸方的决定;即购买期权的一方。不出所料,前面提到的静态买入并持有策略无法对冲一个有条件的索赔。在二项式模型框架中,期权头寸总是对冲到下一个 Δt 时期,而在 Black-Scholes-Merton 模型中,Δt 收敛于零;因此,期权对冲头寸必须在每一时刻重新平衡。然而,在现实世界中,实际资产只能在离散的时间点进行交易,因此对冲投资组合也会在离散的时间点进行调整。让我们看一下这一点在一个普通的 平价看涨期权(ATM) 上的后果,该期权是在一个不支付股息的股票上写的。
R 包含一个名为 OptHedge 的包,用于估算期权的价值以及在离散时间间隔内对看涨和看跌期权的对冲策略;然而,我们的目的是展示交易周期长度的影响。因此,我们将使用我们自己的函数进行计算。
首先,我们安装要使用的包:
install.packages("fOptions")
library(fOptions)
然后,我们可以使用已知的代码在选择的参数集上检查看涨期权的 BS 价格:
GBSOption(TypeFlag = "c", S = 100, X = 100, Time = 1/2, r = 0.05, b = 0.05, sigma = 0.3)
我们根据 Black-Scholes 公式接收给定的参数和看涨期权的价格:
Parameters:
Value:
TypeFlag c
S 100
X 100
Time 0.5
r 0.05
b 0.05
sigma 0.3
Option Price:
9.63487
基于 BS 模型,看涨期权的价格为 9.63487。
在实际操作中,期权的价格通常在标准化市场上报价,而隐含波动率可以从 Black-Scholes 公式中推导出来。一个预期未来波动率低于隐含波动率的交易员,可以通过卖出期权并同时进行 delta 对冲来获利。在以下场景中,我们展示了在遵循 几何布朗运动(GBM) 的股票上,对前述期权的空头头寸进行 delta 对冲。我们假设 BSM 模型的所有假设成立,除了连续时间交易。为了对冲空头期权,我们需要持有 delta 数量的股票,随着 delta 的变化,我们需要定期重新平衡我们的投资组合,在以下情况中,每周重新平衡一次,这使得期权的生命周期内共有 26 次重新平衡。重新平衡的频率应根据标的资产的流动性和波动性进行调整。
让我们看一下股票价格的一个可能未来路径以及 delta 的发展。price_simulation函数使用给定参数生成价格过程:初始股价(S[0]),漂移(mu),GBM 过程的波动率(sigma)以及看涨期权的剩余参数(K,Time)和选定的再平衡周期(Δt)。在模拟现货价格过程后,该函数会计算每个中间日期的 delta 和期权价格,并绘制出来。通过使用set.seed函数,我们可以创建可重复的模拟:
set.seed(2014)
library(fOptions)
Price_simulation <- function(S0, mu, sigma, rf, K, Time, dt, plots = FALSE) {
t <- seq(0, Time, by = dt)
N <- length(t)
W <- c(0,cumsum(rnorm(N-1)))
S <- S0*exp((mu-sigma²/2)*t + sigma*sqrt(dt)*W)
delta <- rep(0, N-1)
call_ <- rep(0, N-1)
for(i in 1:(N-1) ){
delta[i] <- GBSGreeks("Delta", "c", S[i], K, Time-t[i], rf, rf, sigma)
call_[i] <- GBSOption("c", S[i], K, Time-t[i], rf, rf, sigma)@price}
if(plots){
dev.new(width=30, height=10)
par(mfrow = c(1,3))
plot(t, S, type = "l", main = "Price of underlying")
plot(t[-length(t)], delta, type = "l", main = "Delta", xlab = "t")
plot(t[-length(t)], call_ , type = "l", main = "Price of option", xlab = "t")
}
}
然后,我们设置函数的参数:
Price_simulation(100, 0.2, 0.3, 0.05, 100, 0.5, 1/250, plots = TRUE)
我们将得到一个股票价格的潜在路径、实际 delta 以及相应的期权价格:

我们可以看到一个可能的未来场景,在这个场景中,现货价格上涨并迅速达到价内水平,因此期权在到期时被行使。看涨期权的 delta 随着股票价格的波动而变化,并最终收敛为 1。如果现货价格上涨,看涨期权的行使概率增加,为了复制看涨期权,我们需要买入更多股票,而股票价格下跌则导致 delta 降低,表明需要卖出股票。总的来说,当股票贵时我们买入,股票便宜时我们卖出。期权的价格源自对冲成本。再平衡周期越短,我们需要跟踪的价格变动越少。
对冲成本定义为购买和卖出股票所需的累计净成本的现值(参见Hull, 2009),以对冲该头寸。总成本将包括两部分:购买股票的金额和为融资该头寸所支付的利息。根据 BSM 模型,我们使用无风险利率进行复利计算。我们将看到,对冲成本依赖于未来价格的波动,通过模拟多个股票价格路径,我们可以绘制成本分布。更高的股票价格波动会导致更高的对冲成本波动。
Cost_simulation函数计算已卖出看涨期权的对冲成本:
cost_simulation = function(S0, mu, sigma, rf, K, Time, dt){
t <- seq(0, Time, by = dt)
N <- length(t)
W <- c(0,cumsum(rnorm(N-1)))
S <- S0*exp((mu-sigma²/2)*t + sigma*sqrt(dt)*W)
delta <- rep(0, N-1)
call_ <- rep(0, N-1)
for(i in 1:(N-1) ){
delta[i] <- GBSGreeks("Delta", "c", S[i], K, Time-t[i], rf, rf, sigma)
call_[i] <- GBSOption("c", S[i], K, Time-t[i], rf, rf, sigma)@price
}
在以下命令中,share_cost表示为了维持对冲头寸而购买基础资产的成本,interest_cost是融资该头寸的成本:
share_cost <- rep(0,N-1)
interest_cost <- rep(0,N-1)
total_cost <- rep(0, N-1)
share_cost[1] <- S[1]*delta[1]
interest_cost[1] <- (exp(rf*dt)-1) * share_cost[1]
total_cost[1] <- share_cost[1] + interest_cost[1]
for(i in 2:(N-1)){
share_cost[i] <- ( delta[i] - delta[i-1] ) * S[i]
interest_cost[i] <- ( total_cost[i-1] + share_cost[i] ) * (exp(rf*dt)-1)
total_cost[i] <- total_cost[i-1] + interest_cost[i] + share_cost[i]
}
c = max( S[N] - K , 0)
cost = c - delta[N-1]*S[N] + total_cost[N-1]
return(cost*exp(-Time*rf))
}
我们可以使用前面定义的函数生成不同的未来价格过程,并基于此计算对冲的成本。向量A收集了几种可能的对冲成本,并绘制其直方图作为概率分布。接下来,我们介绍了每周(A)和每日(B)再平衡的对冲策略:
call_price = GBSOption("c", 100, 100, 0.5, 0.05, 0.05, 0.3)@price
A = rep(0, 1000)
for (i in 1:1000){A[i] = cost_simulation(100, .20, .30,.05, 100, 0.5, 1/52)}
B = rep(0, 1000)
for (i in 1:1000){B[i] = cost_simulation(100, .20, .30,.05, 100, 0.5, 1/250)}
dev.new(width=20, height=10)
par(mfrow=c(1,2))
hist(A, freq = F, main = paste("E = ",round(mean(A), 4) ," sd = ",round(sd(A), 4)), xlim = c(6,14), ylim = c(0,0.7))
curve(dnorm(x, mean=mean(A), sd=sd(A)), col="darkblue", lwd=2, add=TRUE, yaxt="n")
hist(B, freq = F, main = paste("E = ",round(mean(B), 4) ," sd = ",round(sd(B), 4)), xlim = c(6,14), ylim = c(0,0.7))
curve(dnorm(x, mean=mean(B), sd=sd(B)), col="darkblue", lwd=2, add=TRUE, yaxt="n")
输出为生成的成本结果的直方图:

左侧的直方图显示每周策略的成本分布,而右侧的直方图则属于每日再平衡策略。
如我们所见,缩短Δt可以减少对冲成本的标准差,这表明更频繁的再平衡投资组合。值得注意的是,随着周期的缩短,不仅对冲成本的波动性降低,预期值也较低,接近 BS 价格。
比较 delta 对冲的表现
我们可以通过对成本仿真函数进行轻微修改,进一步研究再平衡周期的影响,这样可以选择相同的未来路径。通过这种方式,我们可以比较不同再平衡周期的策略。
delta 对冲的表现度量由Hull (2009)定义为写期权和对冲期权成本的标准差与期权理论价格的比率。
Cost_simulation 函数需要修改,以便我们可以一起计算多个再平衡周期:
library(fOptions)
cost_simulation = function(S0, mu, sigma, rf, K, Time, dt, periods){
t <- seq(0, Time, by = dt)
N <- length(t)
W = c(0,cumsum(rnorm(N-1)))
S <- S0*exp((mu-sigma²/2)*t + sigma*sqrt(dt)*W)
SN = S[N]
delta <- rep(0, N-1)
call_ <- rep(0, N-1)
for(i in 1:(N-1) ){
delta[i] <- GBSGreeks("Delta", "c", S[i], K, Time-t[i], rf, rf, sigma)
call_[i] <- GBSOption("c", S[i], K, Time-t[i], rf, rf, sigma)@price
}
S = S[seq(1, N-1, by = periods)]
delta = delta[seq(1, N-1, by = periods)]
m = length(S)
share_cost <- rep(0,m)
interest_cost <- rep(0,m)
total_cost <- rep(0, m)
share_cost[1] <- S[1]*delta[1]
interest_cost[1] <- (exp(rf*dt*periods)-1) * share_cost[1]
total_cost[1] <- share_cost[1] + interest_cost[1]
for(i in 2:(m)){
share_cost[i] <- ( delta[i] - delta[i-1] ) * S[i]
interest_cost[i] <- ( total_cost[i-1] + share_cost[i] ) * (exp(rf*dt*periods)-1)
total_cost[i] <- total_cost[i-1] + interest_cost[i] + share_cost[i]
}
c = max( SN - K , 0)
cost = c - delta[m]*SN + total_cost[m]
return(cost*exp(-Time*rf))
}
在以下命令中,修改后的cost_simulation函数用于不同的再平衡周期,并生成包含预期值(E)、置信区间上下限、对冲成本波动性(v)以及表现度量(ratio)的表格,按照六个再平衡周期(0.5 天、1 天、2 天、1 周、2 周、4 周)排序。我们还会得到两张图,分别是每个策略的直方图,以及包含拟合到分布的正态曲线的图表:
dev.new(width=30,height=20)
par(mfrow = c(2,3))
i = 0
per = c(2,4,8,20,40,80)
call_price = GBSOption("c", 100, 100, 0.5, 0.05, 0.05, 0.3)@price
results = matrix(0, 6, 5)
rownames(results) = c("1/2 days", "1 day", "2 days", "1 week", "2 weeks", "4 weeks")
colnames(results) = c("E", "lower", "upper", "v", "ratio")
for (j in per){
i = i+1
A = rep(0, 1000)
set.seed(10125987)
for (h in 1:1000){A[h] = cost_simulation(100, .20, .30,.05, 100, 0.5, 1/1000,j)}
E = mean(A)
v = sd(A)
results[i, 1] = E
results[i, 2] = E-1.96*v/sqrt(1000)
results[i, 3] = E+1.96*v/sqrt(1000)
results[i, 4] = v
results[i, 5] = v/call_price
hist(A, freq = F, main = "", xlab = "", xlim = c(4,16), ylim = c(0,0.8))
title(main = rownames(results)[i], sub = paste("E = ",round(E, 4) ," sd = ",round(v, 4)))
curve(dnorm(x, mean=mean(A), sd=sd(A)), col="darkblue", lwd=2, add=TRUE, yaxt="n")
}
print(results)
dev.new()
curve(dnorm(x,results[1,1], results[1,4]), 6,14, ylab = "", xlab = "cost")
for (l in 2:6) curve(dnorm(x, results[l,1], results[l,4]), add = TRUE, xlim = c(4,16), ylim = c(0,0.8), lty=l)
legend(legend=rownames(results), "topright", lty = 1:6)
在我们的仿真模型中,输出结果如下:
E lower upper v ratio
1/2 days 9.645018 9.616637 9.673399 0.4579025 0.047526
1 day 9.638224 9.600381 9.676068 0.6105640 0,06337
2 days 9.610501 9.558314 9.662687 0.8419825 0,087389
1 week 9.647767 9.563375 9.732160 1.3616010 0,14132
2 weeks 9.764237 9.647037 9.881436 1.8909048 0,196256
4 weeks 9.919697 9.748393 10.091001 2.7638287 0,286857
随着我们更频繁地进行对冲仓位再平衡,对冲成本的标准差变小。在 95%显著性水平下,周度与月度再平衡之间的预期值差异也很明显。在较短周期中,我们没有发现预期值存在显著差异:

前面图像中显示的图表与之前的分析(周度和日度再平衡)相似,但这里我们有更多的再平衡周期。再平衡频率的影响通过对冲成本的分布表现出来。

我们可以在一张图表上比较给定再平衡周期的成本分布,如前面一节所示。
通过减少仿真次数,可以减少时间消耗。
在交易成本存在的情况下进行对冲
如前所述,增加投资组合调整次数会导致对冲成本波动性的降低。随着Δt趋近于 0,对冲成本接近于通过 BS 公式得出的期权价格。到目前为止,我们忽略了交易成本,但在这里我们去除这一假设,分析交易成本对期权对冲的影响。随着再平衡变得更加频繁,交易成本增加了对冲成本,但同时,更短的再平衡周期减少了对冲成本的波动性。因此,值得更详细地研究这一权衡,并基于此定义最优的再平衡策略。通过在定义函数时修改参数,可以在代码中添加绝对(每次交易固定)或相对(与交易规模成比例)的交易成本:
cost_simulation = function(S0, mu, sigma, rf, K, Time, dt, periods, cost_per_trade)
然后,可以按如下方式编写绝对交易成本的计算方法:
share_cost[1] <- S[1]*delta[1] + cost_per_trade
interest_cost[1] <- (exp(rf*dt*periods)-1) * share_cost[1]
total_cost[1] <- share_cost[1] + interest_cost[1]
for(i in 2:m){
share_cost[i] <- ( delta[i] - delta[i-1] ) * S[i] + cost_per_trade
interest_cost[i] <- ( total_cost[i-1] + share_cost[i] ) * (exp(rf*dt*periods)-1)
total_cost[i] <- total_cost[i-1] + interest_cost[i] + share_cost[i]
}
对于相对成本的情况,程序代码如下:
share_cost[1] <- S[1]*delta[1]*(1+trading_cost)
interest_cost[1] <- (exp(rf*dt*periods)-1) * share_cost[1]
total_cost[1] <- share_cost[1] + interest_cost[1]
for(i in 2:m){
share_cost[i] <- (( delta[i] - delta[i-1] ) * S[i]) + abs(( delta[i] - delta[i-1] ) * S[i]) * trading_cost
interest_cost[i] <- ( total_cost[i-1] + share_cost[i] ) * (exp(rf*dt*periods)-1)
total_cost[i] <- total_cost[i-1] + interest_cost[i] + share_cost[i]
}
在引用cost_simulation函数时,必须给定绝对或相对成本。让我们检查每次交易 0.02 的绝对成本的影响(我们假设成本单位与交易规模相同)。为了缩短时间消耗,这里仅使用了 100 条模拟路径。
我们需要在循环中更改cost_simulation函数的参数:
for (i in 1:100)
A[i] = cost_simulation(100, .20, .30,.05, 100, 0.5, 1/1000,j,.02)
然后,我们得到了如下所示的表格:
E lower upper v ratio
1/2 days 12.083775 11.966137 12.20141 0.6001933 0.06229386
1 day 10.817594 10.643468 10.99172 0.8883994 0.09220668
2 days 10.244342 9.999395 10.48929 1.2497261 0.12970866
1 week 9.993442 9.612777 10.37411 1.9421682 0.20157700
2 weeks 10.305498 9.737017 10.87398 2.9004106 0.30103266
4 weeks 10.321880 9.603827 11.03993 3.6635388 0.38023748
通过固定交易成本 0.02 计算时,预期的对冲成本大幅增加。最短的再平衡周期受影响最大,因为更多的交易会增加成本。标准差也更高,尤其是在周期短于一周的情况下。
我们可以通过在代码中应用以下更改,观察 1%的相对交易成本的影响:
for (i in 1:100)
A[i] = cost_simulation(100, .20, .30,.05, 100, 0.5, 1/1000,j, 0.01)
在最短(每日或更频繁)再平衡周期的情况下,预期的对冲成本进一步增加,但我们也发现波动性有了更显著的上升(如下所示的输出表格):
E lower upper v ratio
1/2 days 13.56272 13.26897 13.85646 1.498715 0.1555512
1 day 12.53723 12.28596 12.78850 1.282005 0.1330589
2 days 11.89854 11.59787 12.19921 1.534010 0.1592144
1 week 11.37828 10.96775 11.78880 2.094506 0.2173881
2 weeks 11.55362 10.95111 12.15612 3.073993 0.3190487
4 weeks 11.43771 10.69504 12.18038 3.789128 0.3932724
交易成本的存在抵消了更频繁再平衡所带来的波动性降低效果,因此最优的再平衡周期需要通过权衡这些效果来确定。
对冲优化
为了找到最佳的再平衡周期长度,我们必须定义优化标准和需要最大化或最小化的度量。对冲的常见目标是减少风险,即通过对冲成本的方差来衡量的风险。因此,最优的对冲策略应最小化对冲成本的波动性。优化的另一个目标可以是最小化成本的期望值。正如我们所看到的,在没有交易成本的情况下,通过越来越频繁地调整对冲投资组合,这些目标是可以同时实现的。另一方面,交易成本不仅会提高成本的期望值,还会提高波动性,当再平衡过于频繁时,波动性可能会大幅上升。
在金融领域,这是一种广泛使用的方法,特别是在需要在期望值与波动性之间做出权衡时,以定义效用函数并将最优解设为最大效用。例如,在投资组合理论中,假定存在一个个人效用函数,该函数受到回报期望值的正向影响,并受到回报方差的负向影响。我们也可以通过定义一个包含对冲成本期望值及其方差的效用函数来运用相同的技巧。然而,在我们的案例中,两个因素都会对交易者的效用产生负面影响;因此,两个参数都必须具有正号,且该函数应被最小化。因此,目标函数将是以下所定义的效用函数:

方程 3
这里,x 是对冲成本作为一个随机变量,E 表示其期望值,Var 代表其方差,α 是风险厌恶参数。更高的 α 表示更为厌恶风险的投资者/交易者。
平均方差优化的替代方案可以是将期望(成本)值最小化作为主要目标,并设置边界条件,保持选定的风险度量低于预定值。在这里,我们选择了在险价值(Value-at-Risk,简称 VaR)作为控制变量,这是一种下行风险度量,定义为在预定概率下的最大损失或最坏结果,并且在选定的时间范围内计算。
以下代码通过 1,000 次模拟,计算了不同再平衡周期(1-80 Δt)下的成本分布。Δt 的单位是一天的四分之一,因此 Δt 为 1 表示每天四次再平衡;最长的 Δt 为 80,表示一个为期 20 天的周期。该函数收集期望值、标准差和分布的 95 百分位,并以文本格式给出四种不同优化情景的结果,同时绘制结果图表:
n_sim <- 1000
threshold <- 12
cost_Sim <- function(cost = 0.01, n = n_sim, per = 1){a <- replicate(n, cost_simulation(100, .20, .30,.05, 100, 0.5, 1/1000,per,cost));
l <- list(mean(a), sd(a), quantile(a,0.95))}
A <- sapply(seq(1,80) ,function(per) {print(per); set.seed(2019759); cost_Sim(per = per)})
e <- unlist(A[1,])
s <- unlist(A[2,])
q <- unlist(A[3,])
u <- e + s²
A <- cbind(t(A), u)
z1 <- which.min(e)
z2 <- which.min(s)
z3 <- which.min(u)
(paste("E min =", z1, "cost of hedge = ",e[z1]," sd = ", s[z1]))
(paste("s min =", z2, "cost of hedge = ",e[z2]," sd = ", s[z2]))
(paste("U min =", z3, "u = ",u[z3],"cost of hedge = ",e[z3]," sd = ", s[z3]))
matplot(A, type = "l", lty = 1:4, xlab = "Δt", col = 1)
lab_for_leg = c("E", "Sd", "95% quantile","E + variance")
legend(legend = lab_for_leg, "bottomright", cex = 0.6, lty = 1:4)
abline( v = c(z1,z2,z3), lty = 6, col = "grey")
abline( h = threshold, lty = 1, col = "grey")
text(c(z1,z1,z2,z2,z3,z3,z3),c(e[z1],s[z1],s[z2],e[z2],e[z3],s[z3],u[z3]),round(c(e[z1],s[z1],s[z2],e[z2],e[z3],s[z3],u[z3]),3), pos = 3, cex = 0.7)
e2 <- e
e2[q > threshold] <- max(e)
z4 <- which.min(e2)
z5 <- which.min(q)
if( q[z5] < threshold ){
print(paste(" min VaR = ", q[z4], "at", z4 ,"E(cost | VaR < threshold = " ,e[z4], " s = ", s[z4]))
} else {
print(paste("optimization failed, min VaR = ", q[z5], "at", z5 , "where cost = ", e[z5], " s = ", s[z5]))
}
最后的优化搜索的是在 VaR 在q显著性水平(q百分位数)不超过预定阈值的条件下,能够实现的最小成本。由于不一定存在此最小值,如果优化失败,则给出 q-VaR 的最小值作为结果。
绝对交易成本情况下的最优对冲
任务是寻找在交易成本情况下,针对已调查参数的普通看涨期权,最优的再平衡周期长度。假设每笔交易的交易成本为 0.01。
前面函数的输出是一个矩阵A,其中包含属于不同再平衡周期的分布参数,以及根据不同标准的最优解。
矩阵A的第一行和最后一行如下所示:
[,1] [,2] [,3] [,4]
[1,] 14.568 0.3022379 15.05147 14.65935
[2,] 12.10577 0.4471673 12.79622 12.30573
...
[79,] 10.00434 2.678289 14.51381 17.17757
[80,] 10.03162 2.674291 14.41796 17.18345
方括号中的数字表示以Δt为单位的再平衡周期。接下来的列包含了预期值、标准差、95 百分位数和预期值与标准差的和。四个优化过程的结果在下一个输出中总结:
"E min = 50 cost of hedge = 9.79184040508574 sd = 2.21227796458088"
"s min = 1 cost of hedge = 14.5680033393436 sd = 0.302237879069942"
"U min = 8 u = 11.0296321604941 cost of hedge = 10.2898541853535 sd = 0.860103467694771"
" min VaR = 11.8082026178249 at 14 E(cost | VaR < threshold = 10.0172915117802 s = 1.12757856083913"
下图展示了再平衡周期(以Δt为单位)下的结果。虚线表示标准差,实线表示预期成本,而点划线和虚线分别表示效用函数(方程 3)在 alpha 参数为 1 和 95 百分位数时的值。
尽管优化依赖于参数,但该图表展示了在存在交易成本的情况下,预期成本与波动性之间的权衡:

预期成本的最小值(9.79)与 BS 价格 9.63 相差不远。此时,最优的再平衡周期为 50Δt,即 12.5 天。此时,标准差为 2.21。
波动性最小化导致最频繁的再平衡,这意味着每天进行 4 次再平衡;此时,标准差的最小值为 0.30,但频繁交易大幅增加了成本。预期成本为 14.57,比之前的情况高出约 50%。
基于方程 3 中定义的效用函数的优化模型考虑了对冲的两个方面,前面的输出显示 8 个Δt长的再平衡周期是最优的,即恰好 2 天。我们可以达到一个预期值为 10.29,这仅略微超过最小值,标准差为 0.86。
前述输出的最后一行展示了使用风险价值(Value-at-Risk)限制的优化结果。我们应用了 95%的 VaR,并在 95%的情况下寻找最小的预期成本,该成本始终低于 12。根据这一结果,最佳的调整周期为 14 Δt,即 3.5 天。此时,成本的预期值略低(10.02),与之前的情况相比,结果因标准差略高(1.13)而有所偏移。
相对交易成本下的最佳对冲
在这一部分,我们解决了与上一部分相同的优化问题,唯一的区别是交易成本现在为交易额的 1%。所有其他参数保持不变。
输出包含了矩阵A,其中数据相同:
[,1] [,2] [,3] [,4]
[1,] 16.80509 2.746488 21.37177 24.34829
[2,] 14.87962 1.974883 18.20097 18.77978
...
[79,] 11.2743 2.770777 15.89386 18.9515
[80,] 11.31251 2.758069 16.0346 18.91945
由于成本与交易规模相关,我们不仅在预期值上得到了 U 型曲线,在标准差上也出现了 U 型曲线。这表明过于频繁的交易在波动率最小化方面也是不理想的。
与之前的优化相比,另一个主要区别是 VaR 的阈值无法保持(如下代码所示):
"E min = 56 cost of hedge = 11.1495374978655 sd = 2.40795704676431"
"s min = 9 cost of hedge = 12.4747301348104 sd = 1.28919873150291"
"U min = 14 u = 13.9033123535802 cost of hedge = 12.0090095949856 sd = 1.37633671701175"
"optimization failed, min VaR = 14.2623891995575 at 21 where cost = 11.7028044352096 s = 1.518297863428"
以下截图展示了前述命令的输出:

最低预期成本为 11.15,标准差为 2.41,发生在 56 Δt时,表明最佳的重新平衡周期为 14 天。
最低波动率为 1.23,在Δt为 9 时,预期值为 12.47。均值-方差优化结果显示,重新平衡周期为 14 Δt(3.5 天),标准差为 1.38,预期值为 12.01。
如前所述,第四次优化失败;95% VaR 的最小值为 14.26,这可以在 21 Δt(5.25 天)时实现;预期成本为 11.7,标准差为 1.52。
优化结果表明,在存在交易成本的情况下,单纯追求波动率降低会导致成本的巨大上升;因此,最佳的对冲策略还必须考虑这一影响。
进一步扩展
该模型可以通过研究其他价格过程进一步推广。金融资产的回报通常并非如 BSM 模型所假设的那样服从正态分布,而其尾部比高斯曲线预测的要粗。这一现象可以通过 GARCH 模型(广义自回归条件异方差模型)来描述,其中方差具有自相关性,导致波动率的聚集。另一种捕捉极端回报高概率的方法是将随机跳跃加入到过程中。将这些过程应用于模型将使衍生品的对冲成本进一步增加,从而提高预期值,并增加成本分布的方差。
我们可以看到,现货价格的变化会导致 delta 的变化,delta 的变化可以通过 gamma 来衡量,gamma 是期权价格相对于现货价格的第二导数。一个 gamma 中性组合无法仅通过持有期权和标的资产来实现,因为标的资产的 gamma 为零,但我们必须购买相同标的资产的任何到期日或行权价的期权。
此外,如果我们忽略波动率恒定的假设,衍生品的价值将不仅受到标的资产现货价格变化和剩余到期时间变化的影响,还会受到标的资产波动率变化的影响。波动率变化的影响可以通过 vega 来衡量,vega 是期权价格相对于波动率的第一导数。vega 值较高会导致波动率对期权价格的显著影响(Hull, 2009)。这可能导致一种情况:当标的资产价格上涨时,认购期权的价值应该增加,但隐含波动率却下降,这时期权的价格也可能下降。为了抵消 vega 的影响,可以购买相同标的资产的其他期权,或者我们可以使用一个名为 VIX 指数的指数来对冲波动率,VIX 是一个包含期权隐含波动率的交易指数。
本章致力于分析 delta 对冲;详细讲解 gamma 和 vega 的中和超出了我们的关注范围。
总结
在本章中,我们展示了对冲衍生品时遇到的一些实际问题。尽管 Black-Scholes-Merton 模型假设连续时间交易,从而导致对冲组合的连续再平衡且没有交易成本,但在实际中,交易发生在离散时间,并且确实存在交易成本。因此,对冲的成本取决于标的资产现货价格的未来路径;因此,它不再是分析公式所给出的单一值,而是一个可以通过其概率分布描述的随机变量。在本章中,我们模拟了不同的路径,计算了对冲成本,并展示了假设不同再平衡频率下的概率分布。我们发现,在没有交易成本的情况下,随着再平衡周期的缩短,波动性降低。另一方面,交易成本不仅可以提高对冲成本的期望值,还可以增加其方差。我们展示了几种优化算法,用于找到最佳对冲策略。
我们在 R 语言中创建了几个用户定义的函数,用于模拟价格波动并生成成本分布。最后,我们根据给定的优化模型应用了数值优化。
参考文献
-
Black, F. 和 Scholes, M. [1973]:《期权定价与公司负债》。《政治经济学杂志》,81(3),第 637-654 页。
-
霍尔, J. C. [2009]: 《期权、期货与其他衍生品》。皮尔森,普伦蒂斯霍尔出版社。
-
梅顿, R. [1973]: 《理性期权定价理论》。《贝尔经济学与管理科学杂志》,4(1),第 141-183 页。
-
Száz, J [2009]: 《外汇期权与股票期权定价》,Jet Set 出版社,布达佩斯。
第九章:基本面分析
现在,全球金融危机似乎已经接近尾声,大多数投资者正在回归股票市场。在这样做的过程中,你面临着选择哪些股票在未来一段时间内将跑赢其他股票的问题。为了找到合适的投资资产进行购买,你有两个基本选择。一方面,你可以依赖于历史价格走势中的任何趋势和模式。在基于趋势和模式制定投资建议时,你进行的是技术分析。另一方面,你可以通过分析公司的财务表现、战略位置或未来计划,来判断哪些公司会超过市场表现。这就是基本面分析。
本章将帮助你了解如何使用 R 来识别成功的基本面交易策略,以进行股票投资。我们将从应用基本的统计方法开始,逐步过渡到更高级、更复杂的方法,同时讲解如何将你的基本面投资理念转化为可统计检验的假设。
基本面分析的基础
在寻找可能的投资资产时,市场为你提供了广泛的选择。你可以选择债券、艺术品、房地产、货币、商品、衍生品,或者可能是最著名的资产类别——股票。股票代表着对某个公司(发行人)一定部分的所有权。
然而,我们该买哪些股票呢?我们应在什么时候买进或卖出?这些决策至关重要,因为它们将决定你的投资组合的回报。对此问题有两种不同的观点。
技术分析建立在历史价格走势之上,并认为可以识别某些模式,这些模式有助于预测未来价格的变动。相反,基本面分析则侧重于公司及其所有权本身的价值,而不是它的市场价格。在这里,我们相信,迟早,市场价格必须反映股票的公允价值,而这种公允价值可以通过我们拥有股票时收集到的未来现金流来计算,就像任何其他类型的投资一样。
技术分析侧重于基于历史模式分析投资者行为如何推动未来价格变动,而基本面分析则识别出价格应该跟随的趋势,这些趋势源于对公司未来表现的预测。因此,在进行基本面分析时,我们需要回顾我们的公司财务和会计知识。
即使只是检查某只股票的公允价格,我们也可能花费数天时间来建模未来表现,估算销售增长、费用、投资、融资策略的变化和资本成本,以获取有效的折现率用于现金流预测。在开发交易策略时,我们需要审查数千个潜在投资项目,因此不可能做如此深入的分析。即使尝试也可能很棘手。如果你为所有股票建立大型电子表格模型,当你完成时,最初为第一家公司的假设可能已经过时,你不得不重新开始,而完全不考虑第一次版本模型的结果。因此,我们不能真正预测未来的财务报表,而必须基于历史经验来识别良好的投资模式。我们将尝试将之前的基本面比率与历史价格发展联系起来,并期望这些联系在未来也能成立。
这是理解的关键,我们不是要找到值得投资的好公司,而是要找到那些非常可能被错误定价的股票。所以,我们要找到被低估的股票购买,或者如果市场允许做空,我们要找到被高估的股票卖出。在本章的其余部分,我们将仅关注上行潜力,但你也可以使用相同的技术来识别具有巨大下行潜力的股票。找到那些在过去 12 个月内股价上涨的公司基本面特征,可能帮助我们基于当前的财务报表识别明年良好的投资机会。
所以,在构建基本面股票策略时,我们需要遵循以下步骤:
-
收集可能的股票投资的财务报表数据。
-
计算基本面比率以标准化数据。
-
确定比率与未来价格发展的关系。
-
遵循测试策略,即在同一时期对另一组可能的股票进行结果计算,和/或对不同时间段的相同股票进行计算。
仅仅执行这些步骤一次是不够的。应用一个在过去一年(或数年)内表现良好的策略,假设公司内外没有发生剧烈变化,也没有在公司所在的经济环境中发生根本性变化。因为市场往往会发生变化,公司也必须随之调整。这意味着,去年最好的做法现在可能只是普通或一般水平。因此,即使我们的投资策略在数年内运作良好,我们也可能会看到其效果逐渐或甚至剧烈变化。因此,定期重新检查和更新是至关重要的。
收集数据
构建所需的数据库可能是最大挑战之一。在这里,我们不仅需要经过股息调整的价格数据,还需要财务报表数据。第四章,大数据 – 高级分析 介绍了如何访问一些开放的数据源,但这些数据源很少能提供你所需的所有信息。
另一种选择是使用专业的金融数据提供商作为数据来源。这些平台允许你创建定制的表格并导出到 Microsoft Excel。为了本章的演示,我们使用了 Bloomberg 终端。第一步,我们将数据导出到 Microsoft Excel。
电子表格可能是构建来自不同来源的数据库的极好工具。无论你如何准备电子表格中的数据,都需要注意,由于输出格式(xls,xlsx,xlsm,xlsb)的变化和高级格式化功能(例如合并单元格),这并不是将数据导入 R 的最佳形式。相反,最好将数据保存为逗号分隔格式的文件或 CSV 文件。这样可以通过以下命令轻松读取:
d <- read.table("file_name", header = T, sep = ",")
这里,= T 表示你的数据库有一个标题行,sep = "," 表示你的数据是以逗号分隔的。请注意,一些本地化版本的 Excel 可能使用不同的分隔符,比如分号。在这种情况下,使用 sep = ";"。如果你的文件不在 R 的工作目录中,你需要指定文件的完整路径作为 file_name 的一部分。
如果你希望坚持使用 Excel 文件,接下来的方法在大多数情况下可能有效。安装 gdata 包,扩展 R 的功能,使软件能够读取 xls 或 xlsx 文件中的信息:
install.packages("gdata")
library(gdata)
之后,你可以按照以下方式读取 Excel 文件:
d <- read.xls("file_name", n)
这里,第二个参数 n 表示你想要读取的工作簿中的工作表。
为了说明构建基础交易策略的过程,我们将使用纳斯达克综合指数的成分公司。在编写本章时,共有 21,931 家公司被纳入其中。
为了为我们的策略创建一个坚实的基础,我们应该首先清理我们的数据库。否则,极端值可能会造成严重的偏差。例如,如果一家公司在一年前的 P/E(每股市盈率)为 150,并且在过去 12 个月内价格快速上涨,大家可能不会感到惊讶,但现在找到这样的股票可能是不可能的,所以我们的策略可能会毫无价值。策略应该帮助我们找到应该投资的股票,尤其是在选择不再显而易见的情况下(当然,高 P/E 的股票也可能会亏损),所以我们只会保留没有极端值的股票。我们应用了以下限制:
-
P/E(每股市盈率)低于 100
-
股东年总回报率(TRS),即价格涨幅加上股息收益率,低于 100%
-
长期债务/总资本比小于 100%(没有负的股东资本)
-
股票的市价/账面价值比(P/BV)大于 1,即股票的市场价值高于账面价值(没有必要清算公司)
-
营业收入/销售额小于 100%,但大于 0(历史表现可以在长期内持续)
通过这种方式,只有那些不太可能被清算或破产的公司留下来,并且它们的表现显然是可持续的,长期来看非常稳定。在应用这些筛选条件后,全球剩下 7198 家公司。
下一步是选择我们在定义策略时可能使用的比率。根据历史经验,我们从一年前的财务报表中选取了 15 个比率,再加上公司所属行业的名称以及过去 12 个月的总股东回报。
检查剩余数据是否适合我们的目标可能是明智之举。例如,箱型图可以揭示大部分股票是否显示出巨大的正或负回报,或者是否存在行业之间的巨大差异,这可能会导致我们将某个繁荣的行业描述为不适合投资的策略。幸运的是,在这里我们没有遇到这样的情况:(图 1)
d <- read.csv2("data.csv", stringsAsFactors = F)
for (i in c(3:17,19)){d[,i] = as.numeric(d[,i])}
boxplot_data <- split( d$Total.Return.YTD..I., d$BICS.L1.Sect.Nm )
windows()
par(mar = c(10,4,4,4))
boxplot(boxplot_data, las = 2, col = "grey")
以下图表是前述代码的结果:

图 1
另外,检查是否应引入新的变量也是一个不错的主意。一个可能但缺失的分类可以控制公司规模,因为许多模型假设低资本化股票由于流动性差而需要更高的回报率。为此,我们可以使用散点图,相关的代码和输出如下:
model <- lm(" Total.Return.YTD..I. ~ Market.Cap.Y.1", data = d)
a <- model$coefficients[1]
b <- model$coefficients[2]
windows()
plot(d$Market.Cap.Y.1,d$Total.Return.YTD..I., xlim = c(0, 400000000000), xlab = "Market Cap Y-1", ylab = "Total Return YTD (I).")
abline(a,b, col = "red")

我们无法看到资本化和总股东回报(TRS)之间的明显趋势。我们也可以尝试在数据上拟合一条曲线,并计算R²来评估拟合的好坏,但该图表并没有显示出任何强烈的关联。R 平方值表示你的估算解释的方差百分比,因此,任何大于 0.8 的值都很好,而低于 0.2 的值则意味着表现较弱。
揭示关联
为了开始调查具有巨大上行潜力的股票,我们必须检查一年前各项比率与次年总回报之间的关系。为了本章的研究,我们选择了以下比率。我们取自一年前的数据,以便将其与去年的总股东回报(TRS)进行对比:
-
一年前的现金/资产比
-
一年前的净固定资产/总资产
-
一年前的资产/每千名员工
-
一年前的价格/现金流(过去五年的平均值)
-
一年前的价格/现金流
-
一年前的营业收入/净销售额
-
一年前的股息支付比率
-
一年前的资产周转率
-
一年前的市价/账面价值比(P/BV)
-
一年前的营业收入/净销售额
-
一年前的收入增长(过去 1 年)
-
一年前的长期债务/资本比
-
一年前的债务/EBITDA
-
市值 一年前
-
P/E 一年前
计算 Pearson 相关系数可能是一个不错的起点:
d_filt <- na.omit(d)[,setdiff(1:19, c(1,2,18))]
cor_mtx <- cor(d_filt)
round(cor_mtx, 3)
在查看相关性表时,有两个重要结论需要得出。它们如下:
-
只有四个财务比率与 TRS 存在显著相关性,但即便如此,连接也非常弱;即,它们的相关性在 -0.08 到 +0.08 之间。这意味着我们的比率与 TRS 之间没有明确的线性关系。
-
选择的财务比率相当独立。在 105 个(15*14/2)潜在连接中,只有 15 个是显著的。即便这些都位于 -0.439 和 +0.425 之间,且只有 8 个的绝对值大于 0.2。
所以,我们看到,建立一个好的策略并不容易。仅仅依赖一个单一的比率将无法带我们前进。我们将选择更复杂的方法。
包含多个变量
构建一个业绩预测模型的方法之一是使用多变量回归模型。线性估计应只包括变量之间具有最小线性关系的情况。正如我们刚才看到的那样,我们的解释变量或多或少彼此独立,这是一个好消息。不过,坏消息是,这些变量单独与因变量 TRS 的相关性都很低。
为了得到最佳的线性估计,我们可以从几种方法中选择。一种选择是首先包括所有变量,并让 R 步步剔除最不显著的变量(逐步法)。另一种广泛使用的方法是,R 只从一个变量开始,并按步骤进入下一个解释力最强的变量(后退法)。在这里,我们选择了后退法,因为第一种方法不能得出显著的模型:
library(MASS)
vars <- colnames(d_filt)
m <- length(vars)
lin_formula <- paste(vars[m], paste(vars[-m], collapse = " + "), sep = " ~ ")
fit <- lm(formula = lin_formula, data = d_filt)
fit <- stepAIC(object = fit, direction = "backward", k = 4)
summary(fit)
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 6.77884 1.11533 6.078 1.4e-09 ***
Cash.Assets.Y.1 -0.08757 0.03186 -2.749 0.006022 **
Net.Fixed.Assets.to.Tot.Assets.Y.1 0.07153 0.01997 3.583 0.000346 ***
R.D.Net.Sales.Y.1 0.30689 0.07888 3.891 0.000102 ***
P.E.Y.1 -0.09746 0.02944 -3.311 0.000943 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 19.63 on 2591 degrees of freedom
Multiple R-squared: 0.01598, Adjusted R-squared: 0.01446
F-statistic: 10.52 on 4 and 2591 DF, p-value: 1.879e-08
后退法最终得出的 R 平方值为 1.6%,仅意味着回归无法解释 TRS 总方差的 1.6%。换句话说,模型的表现极差。请注意,糟糕的表现是由于解释变量和 TRS 之间的弱(线性)关系。如果你有一些与 TRS 关系更强的变量,你的线性回归结果会更好。R 平方值超过 50% 时,你很有可能通过购买在模型中具有正符号的显著解释变量的高值,同时购买在模型中具有负符号的变量的低值,来构建一个优秀的选股策略。由于我们在这里不能使用这种方法,我们必须遵循不同的逻辑。
分离投资目标
构建投资策略的另一种方法可能是将好的投资目标分开,并检查它们之间的共同点。找到表现良好的股票之间相似性的一种好方法是根据 TRS 值创建组,并比较低绩效和高绩效集群。第一步应该是分析以下代码:
library(stats)
library(matrixStats)
h_clust <- hclust(dist(d[,19]))
plot(h_clust, labels = F, xlab = "")
以下树状图是前面代码的输出结果:

基于树状图,三个簇分离得非常好,但为了将其中最大的簇分割成两个子簇,我们可能需要将簇的数量增加到七个。为了保持概览,我们应该尽量保持簇的数量尽可能低,因此首先,我们将尝试仅使用 k-means 方法创建三个簇:
k_clust <- kmeans(d[,19], 3)
K_means_results <- cbind(k_clust$centers, k_clust$size)
colnames(K_means_results) = c("Cluster center", "Cluster size")
K_means_results
我们的结果相当令人鼓舞。我们的三个簇包含 1000 到 4000 个元素,我们可以非常清晰地识别出表现优异的、表现差的和中等表现的公司:
Cluster center Cluster size
1 9.405869 3972
2 48.067540 962
3 -16.627188 2264
接下来,我们需要检查这三个组之间的平均比率值是否存在显著差异。为此,我们将使用 Anova 表格。这个统计工具将比较组间均值的偏差和各组内的标准差。一旦分类有效,你会发现组间均值差异巨大,但在同一簇内的公司之间差异较小:
for(i in c(3,4,6,10,12,14,16,17)) { print(colnames(d)[i]); print(summary(
aov(d[,i]~k_clust$cluster , d))) }
输出:
[1] "Cash.Assets.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 7491 7491 41.94 1e-10 ***
Residuals 7195 1285207 179
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
1 observation deleted due to missingness
[1] "Net.Fixed.Assets.to.Tot.Assets.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 19994 19994 40.26 2.36e-10 ***
Residuals 7106 3529208 497
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
90 observations deleted due to missingness
[1] "P.CF.5Yr.Avg.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 24236 24236 1.2 0.273
Residuals 4741 95772378 20201
2455 observations deleted due to missingness
[1] "Asset.Turnover.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 7 6.759 11.64 0.00065 ***
Residuals 7115 4133 0.581
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
81 observations deleted due to missingness
[1] "OI...Net.Sales.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 1461 1461.4 10.12 0.00147 **
Residuals 7196 1038800 144.4
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
[1] "LTD.Capital.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 1575 1574.6 4.134 0.0421 *
Residuals 7196 2740845 380.9
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
[1] "Market.Cap.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 1.386e+08 138616578 2.543 0.111
Residuals 7196 3.922e+11 54501888
[1] "P.E.Y.1"
Df Sum Sq Mean Sq F value Pr(>F)
k_clust$cluster 1 1735 1735.3 8.665 0.00325 **
Residuals 7196 1441046 200.3
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
在输出中,R 通过在 F 检验概率(Pr)后添加星号(*)来标记显著性。所以,从前面的表格中你已经知道,六个变量在不同簇之间表现出显著差异。要查看每个簇的平均值,你需要输入以下代码:
f <- function(x) c(mean = mean(x, na.rm = T), N = length(x[!is.na(x)]), sd = sd(x, na.rm = T))
output <- aggregate(d[c(19,3,4,6,10,12,14,16,17)], list(k_clust$cluster), f)
rownames(output) = output[,1]; output[,1] <- NULL
output <- t(output)
output <- output[,order(output[1,])]
output <- cbind(output, as.vector(apply(d[c(19,3,4,6,10,12,14,16,17)], 2, f)))
colnames(output) <- c("Underperformers", "Midrange", "Overperformers", "Total")
options(scipen=999)
print(round(output,3))
我们的输出结果如下。如你所见,每个变量都有三行(均值、元素数量和标准差)。这就是为什么表格这么长的原因。
| 表现差的 | 中等表现 | 表现优异的 | 总计 | |
|---|---|---|---|---|
| Total.Return.YTD..I..mean | -16.627 | 9.406 | 48.068 | 6.385 |
| Total.Return.YTD..I..N | 2264.000 | 3972.000 | 962.000 | 7198.000 |
| Total.Return.YTD..I..sd | 12.588 | 8.499 | 17.154 | 23.083 |
| Cash.Assets.Y.1.mean | 15.580 | 13.112 | 12.978 | 13.870 |
| Cash.Assets.Y.1.N | 2263.000 | 3972.000 | 962.000 | 7197.000 |
| Cash.Assets.Y.1.sd | 14.092 | 12.874 | 13.522 | 13.403 |
| Net.Fixed.Assets.to.Tot.Assets.Y.1.mean | 26.932 | 29.756 | 31.971 | 29.160 |
| Net.Fixed.Assets.to.Tot.Assets.Y.1.N | 2252.000 | 3899.000 | 957.000 | 7108.000 |
| Net.Fixed.Assets.to.Tot.Assets.Y.1.sd | 21.561 | 22.469 | 23.204 | 22.347 |
| P.CF.5Yr.Avg.Y.1.mean | 18.754 | 19.460 | 28.723 | 20.274 |
| P.CF.5Yr.Avg.Y.1.N | 1366.000 | 2856.000 | 521.000 | 4743.000 |
| P.CF.5Yr.Avg.Y.1.sd | 57.309 | 132.399 | 281.563 | 142.133 |
| Asset.Turnover.Y.1.mean | 1.132 | 1.063 | 1.052 | 1.083 |
| Asset.Turnover.Y.1.N | 2237.000 | 3941.000 | 939.000 | 7117.000 |
| Asset.Turnover.Y.1.sd | 0.758 | 0.783 | 0.679 | 0.763 |
| OI...Net.Sales.Y.1.mean | 13.774 | 14.704 | 15.018 | 14.453 |
| OI...Net.Sales.Y.1.N | 2264.000 | 3972.000 | 962.000 | 7198.000 |
| OI...Net.Sales.Y.1.sd | 11.385 | 12.211 | 12.626 | 12.023 |
| LTD.Capital.Y.1.mean | 17.287 | 20.399 | 17.209 | 18.994 |
| LTD.Capital.Y.1.N | 2264.000 | 3972.000 | 962.000 | 7198.000 |
| LTD.Capital.Y.1.sd | 18.860 | 19.785 | 19.504 | 19.521 |
| P.E.Y.1.mean | 20.806 | 19.793 | 19.455 | 20.067 |
| P.E.Y.1.N | 2264.000 | 3972.000 | 962.000 | 7198.000 |
| P.E.Y.1.sd | 14.646 | 13.702 | 14.782 | 14.159 |
正如我们在前面的方差分析(Anova)表中所看到的,在八个财务比率中的六个比率上,我们发现了三组之间的显著差异。这种方法有助于发现即使是非线性的连接(与相关性比率相反)。一个很好的例子是现金/资产(Cash.Assets);表现优异者和中间组的值非常相似,但表现不佳者则有明显更高的(可能未使用的)现金量。这意味着,当现金/资产低于某一水平时,我们可以推测该股票不是一个好的投资。我们也会在资产周转率中发现相同的模式。
价格/现金流(P/CF)的 5 年平均值是我们如何发现那些仅通过相关性检查时仍然隐藏的连接的另一个很好的例子。这个比率呈现 J 型曲线,即最低值出现在中间组,而最高值出现在表现优异的组。
根据这些结果,最佳的投资目标可能同时具有较低的现金比率和财务杠杆(长期债务/资本),但具有较高的固定资产比率和 P/CF 比率,而 P/E 和资产周转率则仅为平均水平。简而言之,最佳公司有效利用其当前资本;它们的资产周转率平均且没有过多的自由现金。它们仍有增加杠杆的空间,并且具有较好的现金流增长前景,这反映在较高的 P/CF 比率上。在测试此选择方法之前,我们应检查是否可以通过添加更精确的规则来区分潜在投资,或者通过删除其中的一些标准来简化它。
设置分类规则
我们采用不同的逻辑来制定决策规则,以便稍后对比两个结果。我们来选择哪些股票提供了最好的回报。决策树或分类树非常适合这个目的。在这里,R 将从给定的变量列表中挑选出那些能够创建最有效决策规则的变量。与之前构建联合规则不同,首先,它会选择一个变量,通过该变量我们可以根据 TRS 将股票分组。然后,对于每个子组,它会选择第二个最有效的变量,以此类推。输出结果就是一种决策树:
d_tree <- d[,c(3:17,19)]
vars <- colnames(d_tree)
m <- length(vars)
tree_formula <- paste(vars[m], paste(vars[-m], collapse = " + "), sep = " ~ ")
library(rpart)
tree <- rpart(formula = tree_formula, data = d_tree, maxdepth = 5 ,cp = 0.001)
tree <- prune(tree, cp = 0.003)
par(xpd = T)
plot(tree)
text(tree, cex = .5, use.n = T, all = T)
在我们的案例中,生成的树状图有五个层级,如下图所示。在每个节点中,我们会看到所创建子组的平均 TRS 值。决策规则也会标示:如果逻辑语句为真,则向左分支走;如果为假,则向右分支走。如图所示,我们将专注于高回报的可能性。我们需要查看树的底部,看看创建了哪些子组,并找出哪些子组的 TRS 特别高:

我们的数据库最终生成了三个特别高平均总回报率(TRS)的子组。根据树状图,我们需要首先检查现金/资产比率。
现金比率高于(或等于)1.6%的公司应根据净固定资产/总资产的比例进一步划分。如果比例高于 12.3%且资产/员工比率低于 398,且资产周转率低于 1.66,那么我们只需要确保前一年收入的年增长超过 43.5%,就能得到一个包含 63 家公司的子组,平均总回报率(TRS)为 19%。
如果现金/资产比率高于(或等于)1.6%且净固定资产/总资产比例低于 12.3%,我们需要查看前一年的年收入增长率。对于那 11 家公司,其中的比例超过 3.77%,且市值超过 2874 亿美元,我们将发现平均总回报率为 34.6%。
还有一个超额表现的第三组。现金比率低于 1.6 的 348 家公司,以及资产/员工比率高于 2156 的公司,产生了平均 19%的总回报率(TRS)。
考虑到这三个组别的元素数量与总分析公司数量的比例,第一个和最后一个组可能会为我们提供一个现实的投资策略。包含 11 家公司的组只占总数的 0.15%,因此,它可能是随机或意外事件的结果。
总结一下,高现金比率(超过 1.6)应与超过 12.3%的固定资产比率、低于 398 的资产/员工值、低于 1.66 的资产周转率以及前一年年收入增长超过 43.5%的条件相匹配。如果你的现金比率低于 1.6,那么资产/员工比率应高于 2156,才能从我们的投资组合中挑选股票。
注意,这里仅包含了五个变量在我们的投资决策程序中,而之前我们设置了八个变量的星座。此外,还需注意,这两个决策过程中只有三个比率(现金/资产比率、固定资产比率和资产周转率)被使用。我们的下一步可能是比较这两种方法的效率。
回测
“回测”一词指的是在历史数据集上计算交易策略的结果。在我们的案例中,我们将使用相同的数据集,因此我们会高估其效果,因为我们的统计模型是基于完全相同的数据进行优化的。在现实生活中,我们可能会选择不同的时间段或不同的股票组(或两者)来更客观地衡量效率。
无论我们如何将表现优秀的公司分开,投资想法的测试遵循相同的逻辑。您将结果转化为规则,挑选符合要求的公司(通常来自不同的样本)并将其放入一个集群,然后创建另一个集群以包含所有其他公司。最后,比较两组的均值和/或中位数表现。
为了测试决策树的选择规则,我们需要创建一个符合以下要求的公司子集:现金比率高于 1.6,固定资产比率超过 12.3%,资产/员工比率低于 398,且上一年收入增长率至少为 43.5%。然后,我们需要添加现金比率低于 1.6 且资产/员工比率高于 2156 的公司:
d$condition1 <- (d[,3] > 1.6)
d$condition2 <- (d[,4] > 12.3)
d$condition3 <- (d[,5] < 398)
d$condition4 <- (d[,10] < 1.66)
d$condition5 <- (d[,13] > 43.5)
d$selected1 <- d$condition1 & d$condition2 & d$condition3 & d$condition4 & d$condition5
d$condition6 <- (d[,3] < 1.6)
d$condition7 <- (d[,5] > 2156)
d$selected2 <- d$condition6 & d$condition7
d$tree <- d$selected1 | d$selected2
为此,我们将创建两个新的变量(每个子集一个),如果满足要求,它们的值为 1;否则,它们的值为 0。接下来,我们将计算第三个变量,它是前两个变量的和。这样,我们将得到两个集群:1 表示符合投资条件的公司,0 表示所有其他公司:
f <- function(x) c(mean(x), length(x), sd(x), median(x))
report <- aggregate( x = d[,19], by = list(d$tree), FUN = f )$x
colnames(report) = c("mean","N","standard deviation","median")
report <- rbind(report, f(d[,19]))
rownames(report) <- c("Not selected","Selected","Total")
print(report)
一旦我们准备好了重新分类,ANOVA 表将帮助我们比较被选中和未被选中的公司的表现。为了确保差异显著不是由于异常值引起的,最好也比较中位数。在我们的案例中,分类似乎运行良好,因为即使是在中位数之间,我们也有巨大的差异:
mean N standard deviation median
Not selected 5.490854 6588 22.21786 3.601526
Selected 19.620651 260 24.98839 15.412807
Total 6.384709 7198 23.08327 4.245684
测试基于集群的投资想法稍微复杂一些。这里,我们仅看到表现更好的公司集群在某些方面的平均值与其他两个组有所不同。需要注意的是,这些差异并不是我们用来创建集群的依据;这只是我们反转逻辑并说财务比率的标准可能会导致更好的表现者分离。
我们需要遍历所有八个显示出显著差异的变量,并创建一个接受范围。使用非常窄的范围可能会导致需要选择的股票数量非常少;而使用过于宽泛的范围则会使组间的 TRS 差异消失。再次强调,检查中位数可能有助于判断。
为了获取我们之前识别的三个集群的均值和中位数,我们将使用以下代码。为了在打印表格时节省空间,我们将三个组编号如下:
-
表现不佳者
-
中等表现者
-
表现优秀者。
下面是代码:
d$cluster = k_clust$cluster
z <- round(cbind(t(aggregate(d[,c(19,3,4,6,10,12,14,16,17)], list(d$selected) ,function(x) mean(x, na.rm = T))),
t(aggregate(d[,c(19,3,4,6,10,12,14,16,17)], list(d$selected) ,function(x) median(x, na.rm = T))))[-1,], 2)
> colnames(z) = c("1-mean","2-mean","3-mean","1-median", "2-median", "3-median")
> z
1-mean 2-mean 3-mean 1-median 2-median 3-median
Total.Return.YTD..I. -16.62 9.41 48.07 -13.45 8.25 42.28
Cash.Assets.Y.1 15.58 13.11 12.98 11.49 9.07 8.95
Net.Fixed.Assets.to.Tot.Assets.Y.1 26.93 29.76 31.97 21.87 24.73 26.78
P.CF.5Yr.Avg.Y.1 18.75 19.46 28.72 11.19 10.09 10.08
Asset.Turnover.Y.1 1.13 1.06 1.05 0.96 0.89 0.91
OI...Net.Sales.Y.1 13.77 14.71 15.02 10.59 11.23 11.49
LTD.Capital.Y.1 17.28 20.41 17.21 11.95 16.55 10.59
Market.Cap.Y.1 278.06 659.94 603.10 3.27 4.97 4.43
P.E.Y.1 20.81 19.79 19.46 16.87 15.93 14.80
以下表格展示了我们根据 Anova 表为集群制定的规则。由于差异较小或范围重叠,我们从标准规则中删除了三个变量。记住,您的主要任务是将表现优秀的公司与表现不佳的公司分开,因此与中等范围的重叠是更可接受的(在中等范围确实处于中间时设置更宽的接受范围),而不是与表现不佳的公司重叠。
| 现金/资产 | 净固定资产占总资产比率 | P/CF 5 年平均 | 资产周转率 | 营业收入/净销售 | 长期债务/资本 | 市值(百万) | 市盈率 | |
|---|---|---|---|---|---|---|---|---|
| 最小值 | 无 | 23 | 删除 | 无 | 11 | 删除 | 删除 | 无 |
| 最大值 | 14 | 无 | 删除 | 1.7 | 无 | 删除 | 删除 | 20 |
表 1
使用以下代码,我们将首先将所有要求整理为一个变量。然后,创建一个最终的对比表:
d$selected <- (d[,3] <= 14) & (d[,4] >= 23) & (d[,10] <= 1.7) & (d[,12] >= 11) & (d[17] <= 20)
d$selected[is.na(d$selected)] <- FALSE
h <- function(x) c(mean(x, na.rm = T), length(x[!is.na(x)]), sd(x, na.rm = T), median(x, na.rm = T))
backtest <- aggregate(d[,19], list(d$selected), h)
backtest <- backtest$x
backtest <- rbind(backtest, h(d[,19]))
colnames(backtest) = c("mean", "N", "Stdev", "Median")
rownames(backtest) = c("Not selected", "Selected", "Total")
print(backtest)
mean N Stdev Median
Not selected 5.887845 6255 23.08020 3.710650
Selected 9.680451 943 22.84361 7.644033
Total 6.384709 7198 23.08327 4.245684
如你所见,我们选定的公司平均回报率为 9.68%,而中位数为 7.6%。在这里,我们可以得出结论,基于决策树开发的策略在均值(19.05%)和中位数(14.98%)方面的表现更好。为了检查重叠情况,我们将计算一个交叉表:
d$tree <- tree$where %in% c(13,17)
crosstable <- table(d$selected, d$tree)
rownames(crosstable) = c("cluster-0","cluser-1")
colnames(crosstable) = c("tree-0","tree-1")
crosstable <- addmargins(crosstable)
crosstable
tree-0 tree-1 Sum
cluster-0 5970 285 6255
cluser-1 817 126 943
Sum 6787 411 7198
在这里,我们看到这两种策略相差甚远:只有 126 家公司同时在两种策略下被选中。但这些公司是否特别呢?确实。这些股票实现了 19.9%的平均总回报率,且中位数为 14.4%,其计算如下:
mean(d[d$selected & d$tree,19])
[1] 19.90455
median(d[d$selected & d$tree,19])
[1] 14.43585
行业特定投资
到目前为止,我们将整个样本视为一个整体。专注于某些行业可能是一个合理的决定。请注意,选择合适的投资行业不应基于过去的表现模式;我们需要分析与全球经济趋势的共动性,涵盖若干年的数据,然后,根据我们对未来时期的预测,挑选出前景最好的行业。这种方法帮助你确定投资组合中各行业的适当权重,但之后,你仍然需要选择那些可能超越其他行业的个股。
当然,一旦选定某一行业,我们可能会得出不同于整体样本的投资规则。因此,我们可以通过分别对每个行业执行之前展示的步骤,进一步提高投资表现。
同时,请记住,你在数据选择时越具体(时间段、行业和公司规模),所创建的策略在其他样本或未来的表现就越不可能很好。通过增加策略构建的自由度(对子样本重新进行所有统计测试),你使得建议几乎完美地适应当前样本,而这个样本可能反映了一些随机事件的影响。由于这些随机效应永远不会再发生,在某个限制之后,增加更多的灵活性实际上会使最终结果变差。
为了举例说明,我们选择了通讯行业。如果在这里应用决策树技术,我们将得到如下图形。之后,我们需要投资于那些在过去一年中,收入增长率低于 21%但高于 1.31%的公司,同时净固定资产比率至少为 8.06%的公司:
d_comm <- d[d[,18] == "Communications",c(3:17,19)]
vars <- colnames(d_comm)
m <- length(vars)
tree_formula <- paste(vars[m], paste(vars[-m], collapse = " + "), sep = " ~ ")
library(rpart)
tree <- rpart(formula = tree_formula, data = d_comm, maxdepth = 5 ,cp = 0.01, control = rpart.control(minsplit = 100))
tree <- prune(tree, cp = 0.006)
par(xpd = T)
plot(tree)
text(tree, cex = .5, use.n = T, all = T)
print(tree)

与此同时,基于给定时期的一般样本构建策略可能会导致某些在特定年份表现突出的行业权重过大,而当然,无法保证未来几年同样偏好这些行业。因此,在构建我们的策略后,我们应该交叉检查该策略背后是否存在严重的行业依赖性。
一个交叉表,控制了行业与基于决策树的投资策略之间的关系,揭示了我们在能源和公用事业行业中大幅过度加权。与此同时,基于聚类的策略则额外加权了材料行业。后者的代码如下所示:
cross <- table(d[,18], d$selected)
colnames(cross) <- c("not selected", "selected")
cross
not selected selected
Communications 488 11
Consumer Discretionary 1476 44
Consumer Staples 675 36
Energy 449 32
Financials 116 1
Health Care 535 37
Industrials 1179 53
Materials 762 99
Technology 894 7
Utilities 287 17
prop.table(cross)
not selected selected
Communications 0.0677966102 0.0015282023
Consumer Discretionary 0.2050569603 0.0061128091
Consumer Staples 0.0937760489 0.0050013893
Energy 0.0623784385 0.0044456794
Financials 0.0161155877 0.0001389275
Health Care 0.0743262017 0.0051403168
Industrials 0.1637954987 0.0073631564
Materials 0.1058627396 0.0137538205
Technology 0.1242011670 0.0009724924
Utilities 0.0398721867 0.0023617672
我们也可能对我们策略在各行业中的表现感兴趣。为此,我们应该查看所有行业中被选中和未选中公司的平均 TRS。要创建这样一个表格,我们需要使用以下命令。输出结果展示了基于决策树的策略表现(0 表示未选中,1 表示选中):
t1 <- aggregate(d[ d$tree,19], list(d[ d$tree,18]), function(x) c(mean(x), median(x)))
t2 <- aggregate(d[!d$tree,19], list(d[!d$tree,18]), function(x) c(mean(x), median(x)))
industry_crosstab <- round(cbind(t1$x, t2$x),4)
colnames(industry_crosstab) <- c("mean-1","median-1","mean-0","median-0")
rownames(industry_crosstab) <- t1[,1]
industry_crosstab
mean-1 median-1 mean-0 median-0
Communications 10.4402 11.5531 1.8810 2.8154
Consumer Discretionary 15.9422 10.7034 2.7963 1.3154
Consumer Staples 14.2748 6.5512 4.5523 3.1839
Energy 17.8265 16.7273 5.6107 5.0800
Financials 33.3632 33.9155 5.4558 3.5193
Health Care 26.6268 21.8815 7.5387 4.6022
Industrials 29.2173 17.6756 6.5487 3.7119
Materials 22.9989 21.3155 8.4270 5.6327
Technology 43.9722 46.8772 7.4596 5.3433
Utilities 11.6620 11.1069 8.6993 7.7672
如前所述输出所示,我们的策略在所有行业中的表现都相当不错;尽管在消费品行业,选中的公司的中位数与未选中的公司相差不大。在其他情况下,我们可能会发现,在某些行业中,我们的结果并不理想,选中的公司 TRS 甚至可能低于另一组公司。在这种情况下,我们将为那些表现较弱的行业构建单独的选股模型。
总结
在本章中,我们研究了如何使用 R 构建基于基本面的投资策略。在构建并加载我们的数据库到 R 中后,我们首先检查了我们的某些变量是否与 TRS 有强关联。然后,我们检查了它们的线性组合是否表现良好并进行了控制。
由于两种方法均未产生可接受的结果,我们将逻辑颠倒过来。我们基于 TRS 表现创建了公司的聚类;然后,我们检查了表现优异者的典型特征。我们还使用了决策树来寻找最佳方法,将 TRS 最高的公司分开。然后,基于结果,我们描述了选股规则并进行了回测。
我们的示例表明,即使单独的解释变量与表现之间没有强烈的线性关系,仍然可以构建有效的基本面选股策略。在应用这些技术时,请记住其局限性:过多的灵活性可能会适得其反。如果您通过为模型提供过多自由度而获得对历史数据集的近乎完美拟合,那么该模型在未来的表现可能会非常糟糕。
参考文献
-
Brealey, Richard – Myers, Stewart – Marcus, Alan (2011):《公司财务基础》McGraw-Hill/Irwin;第 7 版
-
Ross, Stephen – Westerfield, Randolph – Jordan, Bradford D. (2009):《公司财务基础》(标准版)McGraw-Hill/Irwin;第 9 版
-
科勒, 蒂姆 – 古德哈特, 马克 – 韦塞尔斯, 大卫 (2010): 《估值:公司价值的衡量与管理》,第五版,约翰·威立与子公司,纽约
-
达莫达兰, 阿斯瓦斯 (2002): 《投资估值:确定任何资产价值的工具与技巧》,约翰·威立与子公司,纽约
第十章:技术分析、神经网络与对数最优投资组合
在本章中,我们简要介绍了几种可能有助于提升投资组合表现的方法:技术分析、神经网络和对数最优投资组合。这些方法的共同理念是,过去的价格波动可能有助于预测未来的趋势。换句话说,我们隐含地假设价格并不遵循马尔可夫过程(例如随机游走),而是具有某种长期的记忆,因此,过去的模式也可能在未来重现,总之,市场并非完全有效。
在第一部分,我们介绍了技术分析中最常用的工具,并展示了一些如何在 R 环境中编程实现这些工具的示例。在第二部分,我们概述了神经网络的概念以及如何通过 R 的内置函数进行设计。技术分析和神经网络被应用于比特币数据库,因此我们聚焦于单一资产,研究可靠的买卖信号。最后,在第三部分,我们讨论了所谓的对数最优投资组合策略,这些策略使我们能够优化多个资产的投资组合(在我们的示例中是一些纽约证券交易所的股票)以实现长期收益。
本章的主要目标是提供一个关于这些概念的宏观视角,介绍最常用的工具,并提供一些编程示例。因此,我们在此特别强调,由于篇幅所限,我们仅打算为您提供一些领域的洞见,并激发您查阅相关文献,进一步学习并尝试更多工具。
市场效率
市场的效率意味着所有信息都已经反映在当前的价格中。市场效率的弱形式要求最新的价格已经包含了从过去的价格图表和交易量中可以获得的所有信息。显然,如果市场在至少这种弱形式上是有效的,那么收益将是完全独立的,基于技术分析、神经网络和对数最优投资组合理论的策略将毫无价值,见Hull (2009),股价行为模型。
然而,某一市场的效率完全是一个经验性问题。你永远无法确定资产回报在现实世界中是否真的是完全独立的。因此,你不应将市场效率视为事实,而应该鼓励你自己进行测试,发明并实施新的技术启发式策略。如果你基于过去的交易数据调整的策略证明足够稳健,并且在未来表现良好,那么市场将通过提高你投资组合的风险/回报比来慷慨地回报你的努力,最终你将获得额外的利润。研究表明,例如,发展中的货币市场由于流动性差和央行干预,其效率较低,参见Tajaddini-Crack (2012);而大多数技术分析策略在更成熟的美国股市中并不奏效,参见Bajgrowicz-Scaillet (2012),Zapranis-Prodromos (2012)。此外,同样的研究表明,当技术交易成功时,将其与基本面分析结合使用效果会更好,参见Zwart et al. (2009)。
尽管今天仍被视为一种“伪经”,技术分析在基本面投资者中仍然广泛使用。这主要是因为它具有自我实现的特性:市场参与者知道越来越多的同行在使用技术分析工具,因此他们也会关注这些工具。如果例如一个主要指数图表上的 200 日移动平均线被突破,它很可能会成为头条新闻,并引发一波卖盘。
技术分析
技术分析 (TA) 可以帮助你取得更好的结果,但前提是不要高估其预测能力。技术分析尤其擅长预测短期趋势和指示市场情绪。基本面投资者(以及本章的其中一位作者)使用它们来选择买入和卖出的时机:基于他们对市场方向的基本面观点,技术分析在选择短期最佳时机上是一个宝贵的工具。它还可以避免一些常见的交易缺陷,如选错仓位大小(趋势强度的指示)、犹豫不决(只有在有信号时才卖出)以及无法果断操作(但当有信号时,就应该卖出)。
在我们深入技术细节之前,需要记住的三条黄金规则:
-
每个市场都有自己有效的工具组合:例如,头肩形态大多出现在股票图表中,而支撑和阻力水平则调节外汇市场的交易,在各个市场中,每个资产可能都有其特定性。因此,作为经验法则,使用量身定制的指标集和神经网络,针对你所关注的具体资产。
-
不劳无获:请记住,没有什么是灵丹妙药,如果一个人能够在 60%的交易中持续获利,那么她就找到了一个可行且回报丰厚的交易策略。
-
避免冲动交易:也许这比一切都更重要。你可能因为上次交易的亏损而感到痛苦,但不要让它影响你未来的决策。只有在有信号时才进行交易。如果你考虑开设实盘交易账户,广泛阅读有关资金管理(风险管理、仓位大小、杠杆)以及交易心理学(贪婪、恐惧、希望、后悔)的资料。
技术分析工具包
技术分析充满了各种工具,但大多数可以分为四大类。我们建议你使用那些经典工具,因为这些工具被专业人士更为广泛地采用,更有可能触发价格波动(即自我实现),此外,它们通常也更易于使用。
-
支撑-阻力和价格通道:价格水平往往会影响交易:战略性水平可能作为支撑,防止价格跌破,或作为阻力,阻碍价格进一步上涨。将平行线应用于趋势的主要条件(上升趋势的底部,下降趋势的顶部)可以定义价格通道——这些是顶部-底部分析的工具,就像下一个类别的图表形态一样。由于这些通常更难编程,我们不会详细讨论它们。
-
图表形态——头肩形、碟形:听起来熟悉吗?或许因为它们容易识别,图表形态成为了技术分析中最广为人知的工具。它们分为三类:趋势形成者(旗帜、旗形)、趋势反转者(双顶)和决策点信号(三角形)。这些形态也非常直观,几乎无法编程,因此超出了本章的范围。
-
蜡烛图形态:由于蜡烛图是最广泛使用的图表,技术分析师开始在其中发现信号,并为这些信号命名,例如晨星、三白兵或著名的关键反转。与其他任何技术分析工具相比,蜡烛图形态只有在与其他信号结合时才具有意义,在大多数情况下是与战略性价格水平相结合。它们可以是由两到五根蜡烛组成的组合。
-
指标:这是我们将在接下来的章节中最常涉及的一种类型。技术指标易于编程,是高频交易(HFT)的基础,后者是一种基于算法决策和快速市场订单的策略。这些指标分为四类:动量型、趋势跟踪型、资金流(基于成交量)和波动性型。
本章将介绍一种结合了(3)和(4)类元素的策略,我们将通过指标帮助寻找潜在的趋势变化,并在关键反转处识别信号。
市场
尽管每个人应该根据自己的情况探索适合各自市场的技术分析工具,但一些通用的观察还是可以总结出来的。
-
股票通常会形成漂亮的图表形态,并且对蜡烛图形态以及战略性移动平均线交叉非常敏感。信息不对称是一个重要问题,尽管比起商品市场,它的影响较小,但不可预测的价格波动仍然能在新闻发布时改变价格走势。
-
外汇(FX)在全球范围内持续交易,并且高度去中心化,这意味着两件事。首先,没有整体交易量数据,因此人们应该对市场的流动性有一个大致的了解,以评估价格变化的重要性——例如在夏季,流动性较低,因此即使是较小的买入也能引发波动。其次,不同的人在不同的时间进行交易,每个人的习惯也不同。例如在 EURJPY 市场,在美国和欧洲的交易时段,十位数和整数通常是心理支撑位,而在亚洲交易时段则会转向 8(8 是一个幸运数字)。从技术分析工具的角度来看:除了三角形和旗形,重要的支撑-阻力位、价格通道、区间思维、突破启动动态和斐波那契比例通常被使用。
绘制图表 - 比特币
如果交易程序没有提供图表工具,图表程序可能会很贵,并且并不总是提供复杂的技术分析工具。为了解决这个问题,你可以使用 R 来追踪你的图表,并且可以编程实现你喜欢的所有指标——如果它们还没有内置的话。
现在我们来看一个例子:绘制比特币的图表。比特币是一种加密货币,2014 年夏天其价格从不到 1 美元上涨至 1162 美元,并且在许多新成立的、因此还很初级的交易所中交易。这给许多小投资者带来了一个问题:如何追踪图表?即使他们能忍受 BitStamp 平台的不便,细粒度数据也仅以电子表格格式提供,直到今天仍然如此。
你可以从bitcoincharts.com/获取数据。在这里,我们包含了一段代码,它会实时绘制数据,因此就像是一个实时图表工具。通过这个有用的技巧,你可以避免花费数百美元购买专业软件。我们绘制的是蜡烛图(也称为 OHLC 图),这是最常用的图表类型。在开始之前,这里有一张图解释它们是如何工作的。

这里提供了用于获取实时数据并绘制 OHLC 图表的程序代码。
我们将使用RCurl包从互联网上获取数据。首先,让我们来看一下以下函数:
library(RCurl)
get_price <- function(){
首先,我们使用RCurl包中的getURL函数将整个网站作为字符串读取:
a <- getURL("https://www.bitcoinwisdom.com/markets/bitstamp/btcusd", ssl.verifypeer=0L, followlocation=1L)
如果我们查看 HTML 代码,就可以轻松找到我们要找的比特币价格。该函数将其作为数字值返回。
n <- as.numeric(regexpr("id=market_bitstampbtcusd>", a))
a <- substr(a, n, n + 100)
n <- as.numeric(regexpr(">", a))
m <- as.numeric(regexpr("</span>", a))
a <- substr(a, n + 1, m - 1)
as.numeric(a)
}
或者我们也可以通过 XML 包获取完全相同的信息,XML 包是用来解析 HTML 和 XML 文件并提取信息的:
library(XML)
as.numeric(xpathApply(htmlTreeParse(a, useInternalNodes = TRUE), '//span[@id="market_bitstampbtcusd"]', xmlValue)[[1]])
获取价格数据的这一做法当然仅用于演示目的。实时价格数据应由我们的经纪商提供(我们仍然可以使用 R 语言)。现在,让我们来看一下如何绘制实时蜡烛图:
DrawChart <- function(time_frame_in_minutes,
number_of_candles = 25, l = 315.5, u = 316.5) {
OHLC <- matrix(NA, 4, number_of_candles)
OHLC[, number_of_candles] <- get_price()
dev.new(width = 30, height = 15)
par(bg = rgb(.9, .9, .9))
plot(x = NULL, y = NULL, xlim = c(1, number_of_candles + 1),
ylim = c(l, u), xlab = "", ylab = "", xaxt = "n", yaxt = "n")
abline(h = axTicks(2), v = axTicks(1), col = rgb(.5, .5, .5), lty = 3)
axis(1, at = axTicks(1), las = 1, cex.axis = 0.6,
labels = Sys.time() - (5:0) * time_frame_in_minutes)
axis(2, at = axTicks(2), las = 1, cex.axis = 0.6)
box()
allpars = par(no.readonly = TRUE)
while(TRUE) {
start_ <- Sys.time()
while(as.numeric(difftime(Sys.time(), start_, units = "mins")) <
time_frame_in_minutes) {
OHLC[4,number_of_candles] <- get_price()
OHLC[2,number_of_candles] <- max(OHLC[2,number_of_candles], OHLC[4,number_of_candles])
OHLC[3,number_of_candles] <- min(OHLC[3,number_of_candles], OHLC[4,number_of_candles])
frame()
par(allpars)
abline(h = axTicks(2), v=axTicks(1), col = rgb(.5,.5,.5), lty = 3)
axis(1, at = axTicks(1), las = 1, cex.axis = 0.6, labels = Sys.time()-(5:0)*time_frame_in_minutes)
axis(2, at = axTicks(2), las = 1, cex.axis = 0.6)
box()
for(i in 1:number_of_candles) {
polygon(c(i, i + 1, i + 1, i),
c(OHLC[1, i], OHLC[1, i], OHLC[4, i], OHLC[4, i]), col = ifelse(OHLC[1,i] <= OHLC[4,i], rgb(0,0.8,0), rgb(0.8,0,0)))
lines(c(i+1/2, i+1/2), c(OHLC[2,i], max(OHLC[1,i], OHLC[4,i])))
lines(c(i+1/2, i+1/2), c(OHLC[3,i], min(OHLC[1,i], OHLC[4,i])))
}
abline(h = OHLC[4, number_of_candles], col = "green", lty = "dashed")
}
OHLC <- OHLC[, 2:number_of_candles]
OHLC <- cbind(OHLC, NA)
OHLC[1,number_of_candles] <- OHLC[4,number_of_candles-1]
}
}
要完全理解这段代码,可能需要一些时间和编程经验。简而言之,算法做了以下几件事:在一个无限循环中,读取价格数据并将其存储在一个包含四行的矩阵中,作为 OHLC(开盘价、最高价、最低价、收盘价)。每当该矩阵的最后一列被重新计算时,会确保H为该时间间隔内观察到的最高价格,L为最低价格。当由time_frame_in_minutes变量确定的时间到达时,矩阵的列会滚动,最旧的观察数据(第一列)会被丢弃,每一列会被下一列替换。然后,第一列会被填充为 NA,除了 O(开盘价)外,其它值会被填充为 NA,而 O 价格被视为前一列的收盘价,这样图表就能连续显示。
剩余的代码只是用于通过“多边形”方法绘制蜡烛图。(我们也可以使用内置函数来实现,稍后会看到。)
让我们调用这个函数,看看会发生什么:
DrawChart(30,50)
查看更多关于数据操作的内容,请参考第四章,大数据 – 高级分析。
内置指标
R 语言有许多内置指标,如简单移动平均(SMA)、指数移动平均(EMA)、相对强弱指数(RSI)和著名的 MACD。这些构成了技术分析的核心部分,它们的主要目的是通过可视化相对基准,帮助你了解你的资产是否被过度买入、相对表现良好,或者与某个参考时期相比处于战略水平。这里简要解释了每个指标的作用,以及如何将它们添加到你的图表中。
SMA 和 EMA
移动平均是所有指标中最简单的:它们显示了基于滚动数据的平均价格水平。例如,如果你追踪 15 根蜡烛的 SMA,它将给你前 15 根蜡烛的平均价格水平。显然,如果当前蜡烛的时间到了并且开始新的一根蜡烛,SMA 将重新计算一个新的平均值,去掉之前的第一根蜡烛,加入最新的一根蜡烛。SMA 和 EMA 的区别在于,SMA 对所有蜡烛的权重相等,而 EMA 则赋予蜡烛指数权重——因此得名:它对当前蜡烛的权重高于之前的蜡烛。如果你想要一个更紧密联系当前价格水平并能快速反应价格变动的基准,这是一个不错的做法。这些是直接绘制在图表上的叠加指标。
RSI
相对强弱指数(RSI)是一种带状指标:其值在 0 到 100 之间变化,并且在这个范围内有三个带区。当 RSI 在 0 到 30 之间时,表示资产被超卖;当 RSI 在 70 到 100 之间时,表示资产被超买。RSI 试图通过使用相对强弱比率来判断价格变化的强度:即上涨收盘的平均价格除以下跌收盘的平均价格(也就是绿蜡烛的平均收盘价除以红蜡烛的平均收盘价)。该平均值的计算周期可以有所不同,70 是最常用的。

如公式所示,这个指标通常在强趋势中发出信号。由于价格可能会维持在超买或超卖水平,因此使用此指标时需要小心,最好与其他类型的指标或图表形态(如趋势反转,也称为失败摆动)结合使用。如果例如它显示你持有的资产处于超买状态,你也可以考虑减少仓位或寻找警示信号。
这里你可以看到如何绘制这个指标和一个移动平均线:
library(quantmod)
bitcoin <- read.table("Bitcoin.csv", header = T, sep = ";", row.names = 1)
bitcoin <- tail(bitcoin, 150)
bitcoin <- as.xts(bitcoin)
dev.new(width = 20, height = 10)
chartSeries(bitcoin, dn.col = "red", TA="addRSI(10);addEMA(10)")

从上面的图表来看,我们可以得出结论,在这段时间内,由于 RSI 趋向保持在低位区域,并且多次触及极限水平,市场变得相当超卖。
MACD
MACD(Mac Dee)代表移动平均收敛-发散。它是一个由慢速(26 根蜡烛)和快速(12 根蜡烛)指数移动平均线组成的趋势跟踪指标:它发出信号的频率较低,但这些信号往往更为准确。当快速 EMA 穿越慢速 EMA 时,MACD 发出信号。如果快速 EMA 从下方穿越慢速 EMA,则为买入信号;如果快速 EMA 从上方穿越慢速 EMA,则为卖出信号(12 根蜡烛的平均价格低于 26 根蜡烛的长期平均价格)。EMA(12)的位置标志着趋势的整体方向——例如,如果它位于 EMA(26)之上,则市场处于看涨状态。重要的限制:MACD 在区间整理时会发出虚假信号,只应在强趋势中使用。有些人还使用两条线之间距离变化的方向,这通常通过红色或绿色的直方图绘制:当同色的条形图有四根时,趋势的强度得到确认。
对于技术分析,你可以使用不同的 R 包:quantmod、ftrading、TTR等。我们主要依赖quantmod。在这里,你可以看到如何在一个之前保存的数据集(名为Bitcoin.csv)上绘制 MACD:
library(quantmod)
bitcoin <- read.table("Bitcoin.csv", header = T, sep = ";", row.names = 1)
bitcoin <- tail(bitcoin, 150)
bitcoin <- as.xts(bitcoin)
dev.new(width = 20, height = 10)
chartSeries(bitcoin, dn.col = "red", TA="addMACD();addSMA(10)")

你可以在图表下方看到 MACD,在强烈的下行趋势中,它发出了有效的信号。
蜡烛图形态:关键反转
现在你对 R 语言的技术分析功能有了大致了解,让我们编写一个相对简单的策略。以下脚本可以识别关键反转,这是一种蜡烛图形态,出现在关键价格水平。
为此,我们应用了以下双重逻辑:首先,我们给战略价格水平提供了一个主观定义。例如,我们将价格走势的底部单调上升(底部指的是蜡烛体的最低点)且当前的 MA(25) 水平高于 25 根蜡烛之前的 MA(25) 水平的价格运动识别为成熟的上升趋势。在此我们强调,这并不构成标准的技术分析工具的一部分,其参数已根据我们所处理的实际图表(比特币图表)选择。如果您想将其应用于其他资产,建议您调整这些参数,以便获得最佳匹配。这本身并不是一个趋势识别算法:它仅作为我们信号系统的一部分。
如果该算法在成熟趋势中识别到一个可能会因蜡烛图模式出现而突破的战略价格水平,我们便开始寻找关键反转。关键反转是一种趋势反转的蜡烛图模式,它发生在之前趋势的最后一根蜡烛与趋势本身的方向一致(上升趋势为绿色,下降趋势为红色),但价格突然转向,下一根蜡烛的方向与趋势相反,并且其蜡烛体比前一根更大。趋势反转蜡烛应该至少与前一根蜡烛一样高,或者如果报价不连续,对于上升趋势来说略高于前一根蜡烛的收盘价,下降趋势则略低于前一根收盘价。请参见我们下面的图形,展示了上升趋势中的关键反转:

这里是识别该模式的函数代码。
在比特币部分中,我们之前使用多边形方法手动创建蜡烛图。在这里,我们使用了 quantmod 包和 chartSeries 函数来更轻松地完成相同的工作,并通过 OHLC 函数使其更加灵活。
library(quantmod)
OHLC <- function(d) {
windows(20,10)
chartSeries(d, dn.col = "red")
}
以下函数将时间序列和两个索引(i 和 j)作为参数,并判断从 i 到 j 是否为上升趋势:
is.trend <- function(ohlc,i,j){
首先:如果 MA(25) 没有增加,那么就不是上升趋势,因此我们返回 FALSE。
avg1 = mean(ohlc[(i-25):i,4])
avg2 = mean(ohlc[(j-25):j,4])
if(avg1 >= avg2) return(FALSE)
在这个简单的算法中,如果蜡烛体的底部低于前一根和后一根蜡烛的底部,那么该蜡烛被称为谷底。如果这些谷底形成一个单调不减的序列,那么我们就有一个上升趋势。
ohlc <- ohlc[i:j, ]
n <- nrow(ohlc)
candle_l <- pmin(ohlc[, 1], ohlc[, 4])
valley <- rep(FALSE, n)
for (k in 2:(n - 1))
valley[k] <- ((candle_l[k-1] >= candle_l[k]) & (candle_l[k+1] >= candle_l[k]))
z <- candle_l[valley]
if (all(z == cummax(z))) return(TRUE)
FALSE
}
这就是趋势识别。接下来是趋势反转。首先,我们使用之前的函数检查上升趋势的条件。然后我们检查最后两根蜡烛图是否符合反转模式。就这样。
is.trend.rev <- function(ohlc, i, j) {
if (is.trend(ohlc, i, j) == FALSE) return(FALSE)
last_candle <- ohlc[j + 1, ]
reverse_candle <- ohlc[j + 2, ]
ohlc <- ohlc[i:j, ]
if (last_candle[4] < last_candle[1]) return(FALSE)
if (last_candle[4] < max(ohlc[,c(1,4)])) return(FALSE)
if (reverse_candle[1] < last_candle[4] |
reverse_candle[4] >= last_candle[1]) return(FALSE)
TRUE
}
我们走出困境了。现在,我们可以在实际数据中使用它。我们只需读取比特币数据并运行趋势反转识别。如果出现反转趋势并且至少有 10 根蜡烛图,我们就会绘制它。
bitcoin <- read.table("Bitcoin.csv", header = T, sep = ";", row.names = 1)
n <- nrow(bitcoin)
result <- c(0,0)
for (a in 26:726) {
for (b in (a + 3):min(n - 3, a + 100)) {
if (is.trend.rev(bitcoin, a,b) & b - a > 10 )
result <- rbind(result, c(a,b))
if (b == n)
break
}
}
z <- aggregate(result, by = list(result[, 2]), FUN = min)[-1, 2:3]
for (h in 1:nrow(z)) {
OHLC(bitcoin[z[h, 1]:z[h, 2] + 2,])
title(main = z[h, ])
}
信号评估与仓位管理
我们的代码成功识别了四个关键反转点,包括比特币价格的历史转折点,为我们提供了一个不错的卖空信号。我们可以得出结论,信号是成功的,剩下的就是明智地使用它们。
了解比特币的基本原理(其作为货币的接受度受到削弱,并被从此前的核心市场如中国驱逐),如果跟随信号(图表上的最后一根蜡烛),你本可以获得不错的利润,信号如下:

技术分析在设定止盈和止损时非常有用,换句话说,它有助于管理你的仓位。如果你选择在信号处卖出,你可以按以下方式设定这些点位。
系统信号显示你可能想在 2013 年 12 月 5 日的最后一根蜡烛图上卖出,价格为$1023.9,接下来的图表中用箭头标出。你决定继续并开盘。由于比特币价格波动较大,尤其是在之前的趋势呈指数增长后,你决定将止损设置在历史高点 1163,因为你不想让虚假的波动导致你被迫平仓。
在下方的图表中,你可以看到这种方法是合理的,在价格下跌后,波动性显著增加,影线也变得更长。

到 2013 年底,如果你连接蜡烛图实体的顶部(用白色手工绘制),可以画出一条假设的趋势线。这条线似乎有效,且在底部形成了另一条趋势线,坡度更低,形成了一个三角形。我们说,当价格在达到三角形长度的 3/4 之前突破其边界时,图表上的三角形有效。
发生的情况是:2013 年 12 月 26 日,日线图突破了趋势线,并形成了一根大绿蜡烛(箭头指示的地方)。MACD 线交叉,发出了强烈的看涨信号,我们在蜡烛实体顶部平仓,价格为 747.0——如果没有提前平仓的话。因此,我们赚取了$276.9,或 27%的交易回报。
资金管理说明
让我们来看看这个交易的风险配置,展示如何利用技术分析来管理你的风险暴露。做到这一点的最佳方法是计算风险回报比,公式如下:

分母很容易定义,这是仓位上的可能损失,(1163.0-1023.9)= $139.1,假设止损被激活。分子,即可能的收益,可以通过斐波那契回撤来估算,这是一种使用黄金分割预测价格可能反转的工具,特别适用于这种指数趋势。你可以在bitcoinwisdom.com/的图表中看到它:

如果将趋势的高度视为 100%,你可以预期价格在趋势突破时会触及斐波那契水平。由于关键反转是一个强烈信号,我们选择 38.2%的位置,即 747.13 美元,因此我们预期价格会跌至该点。风险/收益比的分子是(1023.9 - 747.1)= 276.8 美元,最终结果为 276.8 / 139.1 = 1.99,意味着每冒 1 美元的风险,就有 1.99 美元的预期利润。这是一个相当不错的潜力,交易应该被批准。
每当你考虑进入一个仓位时,计算一下你所面临的风险与预期收益的比值。如果比值低于 3/2,那么这个仓位并不是最优的;如果比值低于 1,你应该完全放弃这笔交易。改善风险/收益比的可能方式包括设置更紧的止损或选择更强的信号。如果你希望在交易中取得成功,技术分析提供了一些有用的风险管理策略,不要忽视它们。
总结
技术分析,特别是所展示的图表分析方法,是一种高度直观的、图形化的金融资产分析方式。它利用支撑/阻力位、图表和蜡烛图形态以及指标来预测未来的价格走势。R 语言使我们能够免费获取实时数据,并将其绘制为 OHLC 图表,添加指标,并接收自动化信号以识别关键反转信号、蜡烛图形态。我们用这些信号之一展示了如何手动管理一个实际的仓位,并证明了技术分析的吸引力在于它不仅告诉你何时开仓,还能告诉你何时平仓,并通过风险管理实践计算信号的强度。
神经网络
由于其先进的数学背景,神经网络(NN)在学术界停留了很长时间,后来随着更实用的格式的出现——例如 R 语言内置函数,神经网络迅速获得了普及。神经网络是可以适应的人工智能软件,能够在数据中检测复杂模式:它就像一位经验丰富的老交易员,具备良好的市场直觉,但并不总是能够向你解释为什么他坚信你应该做空道琼斯工业平均指数(DIJA)。
网络架构由多个节点通过连接相互连接组成。网络通常有 3 层或 4 层:输入层、隐藏层和输出层,每一层可以包含多个神经元。第一层的节点数对应模型的解释变量的数量,而最后一层的节点数等于响应变量的数量(对于二元目标变量通常为 2 个神经元,对于连续目标变量通常为 1 个神经元)。模型的复杂性和预测能力由隐藏层的节点数决定。通常,一个层的每个节点与下一层的所有其他节点都有连接,这些连接(见图)代表权重。每个神经元从上一层接收输入,并通过非线性函数将其转换为下一层的输入。
一个具有单一隐藏层的前馈神经网络几乎可以在任何复杂问题中发挥作用 (Chauvin-Rumelhart, 1995),这也是为什么它被研究人员广泛使用的原因。(Sermpinis et al., 2012;Dai et al., 2012) Atsalakis-Valavanis (2009) 指出,属于前馈神经网络(FFNN)家族的多层感知器(MLP)模型可能是预测金融时间序列最有效的模型。下图展示了根据 (Dai et al., 2012) 提出的一个三层 MLP 神经网络的结构。

连接权重(边的值)首先被赋予初始值。预测值与实际输出值之间的误差通过网络进行反向传播,以更新权重。监督学习过程然后尝试最小化期望输出和预测输出之间的误差(通常是 MSE、RMSE 或 MAPE)。由于具有一定数量隐藏层神经元的网络可以学习学习数据上的任何关系(即使是异常值和噪声),通过提前停止学习算法,可以防止过度学习。网络的学习过程在测试集达到最小值时停止。然后,使用给定的参数,网络必须在验证集上运行,见(Wang et al., 2012)。
在创建和执行自己的神经网络时,有许多实际问题需要解决,例如选择合适的网络拓扑结构、选择和转换输入变量、减少输出方差,以及最重要的是缓解过拟合问题。过拟合是指当训练集上的误差非常小,但当我们在新数据上拟合网络时误差较大。这意味着网络仅仅记住了训练样本,而没有成功理解关系的总体结构。为了避免过拟合,我们需要将数据拆分为三个子集:训练集、验证集和测试集。训练集通常占总数据的 60-70%,用于学习和拟合网络参数。验证数据集(10-20%)用于最小化过拟合效应并调整参数,例如选择神经网络中的隐藏节点数量。测试数据集(10-20%)仅用于测试最终解决方案,以确认网络的预测能力。
预测比特币价格
让我们看看它在实践中的运作方式。此示例应用了基于比特币收盘价预测的交易策略。分析选择了 2013 年 8 月 3 日到 2014 年 5 月 8 日之间的时间段。数据集共有 270 个数据点,前 240 个数据点作为训练样本,剩下的 30 个数据点作为测试样本(预测模型在该 9 个月时间序列的最后一个月上进行了测试)。
首先,我们从Bitcoin.csv中加载数据集,该文件可以在书籍的官方网站上找到。
data <- read.csv("Bitcoin.csv", header = TRUE, sep = ",")
data2 <- data[order(as.Date(data$Date, format = "%Y-%m-%d")), ]
price <- data2$Close
HLC <- matrix(c(data2$High, data2$Low, data2$Close), nrow = length(data2$High))
在第二步中,我们计算对数收益,并安装TTR库来生成技术指标。
bitcoin.lr <- diff(log(price))
install.packages("TTR")
library(TTR)
为建模选定的六个技术指标已被研究人员和专业交易员广泛且成功地使用。
rsi <- RSI(price)
MACD <- MACD(price)
macd <- MACD[, 1]
will <- williamsAD(HLC)
cci <- CCI(HLC)
STOCH <- stoch(HLC)
stochK <- STOCH[, 1]
stochD <- STOCH[, 1]
我们为训练和验证数据集创建输入和目标矩阵。训练和验证数据集包括 2013 年 8 月 3 日(700)到 2014 年 4 月 8 日(940)之间的收盘价和技术指标。
Input <- matrix(c(rsi[700:939], cci[700:939], macd[700:939],will[700:939], stochK[700:939], stochD[700:939]), nrow = 240)
Target <- matrix(c(bitcoin.lr[701:940]), nrow = 240)
trainingdata <- cbind(Input, Target)
colnames(trainingdata) <- c("RSI", "CCI", "MACD", "WILL", "STOCHK", "STOCHD", "Return")
现在,我们安装并加载caret包来划分我们的学习数据集。
install.packages("caret")
library(caret)
我们将学习数据集按 90-10%的比例(训练-验证)进行划分。
trainIndex <- createDataPartition(bitcoin.lr[701:940], p = .9, list = FALSE)
bitcoin.train <- trainingdata[trainIndex, ]
bitcoin.test <- trainingdata[-trainIndex, ]
我们安装并加载了nnet包。
install.packages("nnet")
library(nnet)
通过网格搜索过程选择合适的参数(隐藏层中的神经元数量、学习率)。网络的输入层包含六个神经元(与解释变量的数量一致),而隐藏层则测试了包含 5、12、…、15 个神经元的网络。网络有一个输出:比特币的日收益率。模型在较低的学习率(0.01、0.02、0.03)下进行测试。使用的收敛标准是:如果达到第 1000 次迭代,则停止学习过程。选择在测试集上具有最低 RMSE 的网络拓扑作为最优模型。
best.network <- matrix(c(5, 0.5))
best.rmse <- 1
for (i in 5:15)
for (j in 1:3) {
bitcoin.fit <- nnet(Return ~ RSI + CCI + MACD + WILL + STOCHK + STOCHD, data = bitcoin.train, maxit = 1000, size = i, decay = 0.01 * j, linout = 1)
bitcoin.predict <- predict(bitcoin.fit, newdata = bitcoin.test)
bitcoin.rmse <- sqrt(mean ((bitcoin.predict – bitcoin.lr[917:940])²))
if (bitcoin.rmse<best.rmse) {
best.network[1, 1] <- i
best.network[2, 1] <- j
best.rmse <- bitcoin.rmse
}
}
在这一步,我们为测试数据集创建了输入矩阵和目标矩阵。测试数据集包括 2013 年 4 月 8 日(940)到 2014 年 5 月 8 日(969)之间的收盘价格和技术指标。
InputTest <- matrix(c(rsi[940:969], cci[940:969], macd[940:969], will[940:969], stochK[940:969], stochD[940:969]), nrow = 30)
TargetTest <- matrix(c(bitcoin.lr[941:970]), nrow = 30) Testdata <- cbind(InputTest,TargetTest)
colnames(Testdata) <- c("RSI", "CCI", "MACD", "WILL", "STOCHK", "STOCHD", "Return")
最后,我们在测试数据上拟合了最佳的神经网络模型。
bitcoin.fit <- nnet(Return ~ RSI + CCI + MACD + WILL + STOCHK + STOCHD, data = trainingdata, maxit = 1000, size = best.network[1, 1], decay = 0.1 * best.network[2, 1], linout = 1)
bitcoin.predict1 <- predict(bitcoin.fit, newdata = Testdata)
我们重复并平均该模型 20 次,以消除异常网络。
for (i in 1:20) {
bitcoin.fit <- nnet(Return ~ RSI + CCI + MACD + WILL + STOCHK + STOCHD, data = trainingdata, maxit = 1000, size = best.network[1, 1], decay = 0.1 * best.network[2, 1], linout = 1)
bitcoin.predict <- predict(bitcoin.fit, newdata = Testdata)
bitcoin.predict1 <- (bitcoin.predict1 + bitcoin.predict) / 2
}
我们计算了“买入持有”基准策略和神经网络在测试数据集上的结果。
money <- money2 <- matrix(0,31)
money[1,1] <- money2[1,1] <- 100
for (i in 2:31) {
direction1 <- ifelse(bitcoin.predict1[i - 1] < 0, -1, 1)
direction2 <- ifelse(TargetTest[i - 1] < 0, -1, 1)
money[i, 1] <- ifelse((direction1 - direction2) == 0, money[i-1,1]*(1+abs(TargetTest[i - 1])),
money[i-1,1]*(1-abs(TargetTest[i - 1])))
money2[i, 1] <- 100 * (price[940 + I - 1] / price[940])
}
我们根据基准和神经网络策略在测试数据集(1 个月)上绘制了投资价值曲线。
x <- 1:31
matplot(cbind(money, money2), type = "l", xaxt = "n",
ylab = "", col = c("black", "grey"), lty = 1)
legend("topleft", legend = c("Neural network", "Benchmark"),
pch = 19, col = c("black", "grey"))
axis(1, at = c(1, 10, 20, 30),
lab = c("2014-04-08", "2014-04-17", "2014-04-27", "2014-05-07"))
box()
mtext(side = 1, "Test dataset", line = 2)
mtext(side = 2, "Investment value", line = 2)

策略评估
我们注意到,在这个示例中,神经网络策略在实现的回报率上超越了“买入持有”策略。通过神经网络,我们在一个月内获得了 20%的回报,而采用被动的买入持有策略仅为 3%。然而,我们没有考虑到交易成本、买卖差价和价格影响,这些因素可能会显著降低神经网络的利润。
日志最优投资组合
与前面的观点相反,假设市场上有有限数量的风险资产。这些资产在没有任何交易成本的情况下进行持续交易。投资者分析历史市场数据,并基于这些数据,在每一天结束时重新调整她的投资组合。她如何在长期内最大化财富?如果回报在时间上是独立的,那么市场在弱意义上是有效的,回报的时间序列没有记忆。如果回报也是独立同分布(i.i.d.),则最优策略是根据例如马科维茨模型(见Daróczi et al. 2013)设定投资组合权重,并在整个时间范围内保持这些权重不变。在这种情况下,任何调整都将对长期的投资组合价值产生负面影响。
现在,让我们暂时放弃纵向独立性的假设,允许资产回报中存在隐藏的模式,因此市场不是有效的,分析历史价格变动是有意义的。我们保留的唯一假设是,资产回报由一个平稳且遍历的过程生成。可以证明,最佳的选择是所谓的对数最优投资组合,参见Algoet-Cover (1988)。更精确地说,没有其他投资策略在渐近过程中能获得比对数最优投资组合更高的预期回报。问题在于,为了确定对数最优投资组合,必须知道生成过程。
但是,在我们对基础随机过程的性质一无所知的情况下,如何在更现实的环境中进行操作呢?如果一种策略能够确保在渐近过程中,平均增长率趋近于对任何(!)生成过程为平稳且遍历的情况下,对数最优策略的增长率,则称该策略为普遍一致。这虽然令人惊讶,但确实存在普遍一致的策略,参见Algoet-Cover (1988)。因此,基本思想是寻找过去的模式,这些模式与最近观察到的模式相似,并在此基础上预测未来的回报,并优化相对于该预测的投资组合。相似性的概念可以通过不同的方式来定义,因此我们可以采用不同的方法,例如划分估计器、基于核心函数的估计器和最近邻估计器。为便于说明,下一节我们将展示一个简单的普遍一致策略,该策略基于Györfi 等人(2006)的核心函数方法。
一种普遍一致的非参数投资策略
假设市场上有 d 只不同的股票进行交易。包含投资组合权重的向量b可以每天重新排列。我们假设投资组合权重是非负的(不允许卖空),并且权重的总和始终为 1(投资组合必须是自我融资的)。向量x包含价格相对值
,其中P代表第i天的收盘价。投资者的初始财富为S[0],因此她在第n期末的财富如下:

其中,
是两个向量的标量积,n 是我们跟随投资策略的天数,W[n] 是 n 天内的平均对数回报,B 表示所有应用的 b 向量。因此,任务是确定一种重新分配规则,使得 W[n] 在长期内最大化。这里,我们展示了一种简单的普遍一致策略,具有这种吸引人的特性。设 J[n] 表示与最近观察到的那一天在欧几里得距离上相似的日期集合。它由以下公式确定:

其中,r[l] 是由第 l 位专家选择的最大允许距离(半径)。根据第 l 位专家在第 n 天的对数最优投资组合可以用以下方式表示:

为了获得一个均衡且稳健的策略,我们定义了不同的专家(投资组合经理),他们具有不同的半径,并根据权重向量q将财富分配给不同的专家。权重可以相等,也可以根据专家的历史表现或其他特征来确定。通过这种方式,我们结合了多个专家的意见,我们在第 n 天的财富为:

假设我们是专家,并且在 1997 到 2006 年期间,跟随上述策略,投资于四只纽约证券交易所的股票(aph, alcoa, amerb, 和 coke)以及美国国债,并使用一年期的滚动时间窗口。数据可以从以下链接获取:www.cs.bme.hu/~oti/portfolio/data.html。首先,我们将数据读入。
all_files <- list.files("data")
d <- read.table(file.path("data", all_files[[1]]),
sep = ",", header = FALSE)
colnames(d) = c("date", substr(all_files[[1]], 1, nchar(all_files[[1]]) - 4))
for (i in 2:length(all_files)) {
d2 <- read.table(file.path("data", all_files[[i]]), sep = ",", header = FALSE)
colnames(d2) = c("date", substr(all_files[[i]], 1, nchar(all_files[[i]])-4))
d <- merge(d, d2, sort = FALSE)
}
该函数根据我们预先设置的半径(r)计算与投资组合权重相关的预期价值。
log_opt <- function(x, d, r = NA) {
x <- c(x, 1 - sum(x))
n <- ncol(d) - 1
d["distance"] <- c(1, dist(d[2:ncol(d)])[1:(nrow(d) - 1)])
if (is.na(r)) r <- quantile(d$distance, 0.05)
d["similarity"] <- d$distance <= r
d["similarity"] <- c(d[2:nrow(d), "similarity"], 0)
d <- d[d["similarity"] == 1, ]
log_return <- log(as.matrix(d[, 2:(n + 1)]) %*% x)
sum(log_return)
}
该函数计算特定日期的最优投资组合权重。
log_optimization <- function(d, r = NA) {
today <- d[1, 1]
m <- ncol(d)
constr_mtx <- rbind(diag(m - 2), rep(-1, m - 2))
b <- c(rep(0, m - 2), -1)
opt <- constrOptim(rep(1 / (m - 1), m - 2), function(x) -1 * log_opt(x, d), NULL, constr_mtx, b)
result <- rbind(opt$par)
rownames(result) <- today
result
}
现在,我们对所有找到的相似日期进行投资组合权重优化。同时,我们还计算了每一天投资组合的实际价值。
simulation <- function(d) {
a <- Position( function(x) substr(x, 1, 2) == "96", d[, 1])
b <- Position( function(x) substr(x, 1, 2) == "97", d[, 1])
result <- log_optimization(d[b:a,])
result <- cbind(result, 1 - sum(result))
result <- cbind(result, sum(result * d[b + 1, 2:6]), sum(rep(1 / 5, 5) * d[b + 1, 2:6]))
colnames(result) = c("w1", "w2", "w3", "w4", "w5", "Total return", "Benchmark")
for (i in 1:2490) {
print(i)
h <- log_optimization(d[b:a + i, ])
h <- cbind(h, 1 - sum(h))
h <- cbind(h, sum(h * d[b + 1 + i, 2:6]), sum(rep(1/5,5) * d[b + 1 + i, 2:6]))
result <- rbind(result,h)
}
result
}
A <- simulation(d)
最后,让我们绘制随时间变化的投资价值。
matplot(cbind(cumprod(A[, 6]), cumprod(A[, 7])), type = "l",
xaxt = "n", ylab = "", col = c("black","grey"), lty = 1)
legend("topright", pch = 19, col = c("black", "grey"),
legend = c("Logoptimal portfolio", "Benchmark"))
axis(1, at = c(0, 800, 1600, 2400), lab = c("1997-01-02", "2001-03-03", "2003-05-13", "2006-07-17"))
我们得到以下图表:

策略评估
从上面的图表中可以看到,我们的对数最优策略优于始终保持投资组合权重相等且固定的被动基准策略。然而,值得注意的是,在前一种情况下,不仅平均回报率较高,投资价值的波动性也要大得多。
数学上已证明,存在一些非参数化的投资策略,这些策略能够有效地揭示已实现回报中的隐藏模式,并利用它们实现投资者财富的“几乎”最优增长率。为了这一目标,我们不需要知道基础过程;唯一的假设是该过程是平稳的且是遍历的。然而,我们不能确定这一假设在现实中是否成立。同样重要的是要强调,这些策略仅在渐近意义上是最优的,但我们对潜在路径的短期特征知之甚少。
摘要
本章不仅概述了技术分析,还讨论了一些相应的策略,如神经网络和 log-optimal 投资组合。这些方法在某种意义上是相似的:应用它们时,我们隐含地假设过去的情况可能会在未来重现;因此,我们大胆挑战市场效率的概念,建立了一个主动交易策略。在这种框架下,我们讨论了预测单一资产(比特币)价格、优化交易时机以及动态优化多个风险资产(纽约证券交易所股票)投资组合的问题。我们展示了一些基于 R 工具包的简单算法,相比于被动的买入并持有策略,它们能够产生显著的额外收益。然而,我们也注意到,一个全面的绩效分析不仅要关注平均回报,还要考虑相应的风险。因此,我们建议在优化策略时,注意市场下行、波动性以及其他风险度量。当然,您还必须意识到所呈现方法的局限性:您不能确定已知的回报生成过程;如果频繁交易,您需要支付大量的交易成本;而且,随着财富的增加,您会遭遇更多的不利价格影响等等。然而,我们确实希望您能够获得新的启发和有用的提示,以便发展出您自己的复杂交易策略。
参考文献
-
Algoet, P.; Cover, T. (1988) 渐近最优性,log-optimal 投资的渐近等分性质,《概率年刊》,16,第 876-898 页
-
Atsalakis, G. S. Valavanis, K. P. (2009) 股票市场预测技术调查-第二部分:软计算方法。《应用专家系统》,36(3),第 5932-5941 页
-
Bajgrowicz, P; Scaillet, O. (2012) 技术性交易再探:虚假发现、持久性测试与交易成本,《金融经济学杂志》,第 106 卷,第 473-491 页
-
Chauvin, Y.; Rumelhart, D. E. (1995) 反向传播:理论、架构与应用。新泽西州:劳伦斯·厄尔鲍姆协会。
-
Dai, W.; Wu, J-Y.; Lu, C-J. (2012) 结合非线性独立成分分析与神经网络预测亚洲股市指数。《应用专家系统》,39(4),第 4444-4452 页
-
Daróczi, G. 等 (2013) 《定量金融的 R 语言入门》,Packt
-
Györfi, L.; Lugosy, G.; Udina, F. (2006) 非参数核基序列投资策略,《理论与应用金融国际期刊》,第 10 卷,第 505-516 页
-
Sermpinis, G.; Dunis, C.; Laws, J.; Stasinakis, C. (2012) 利用随机神经网络组合和时变杠杆预测和交易欧元/美元汇率,《决策支持系统》,第 54 卷(第 1 期),第 316-329 页
-
Tajaddini, R.; Falcon Crack, T. (2012) 动量型交易策略在新兴货币市场中的有效性,《国际金融市场、机构与货币期刊》,第 22 卷,第 521-537 页
-
Wang, J. J.; Wang, J. Z.; Zhang, Z. G.; Guo, S. P. (2012) 基于混合模型的股票指数预测,《欧米茄》,第 40 卷(第 6 期),第 758-766 页
-
Zapranis, A.; T. E. Prodromos (2012) 一种新颖的基于规则的技术模式识别机制:识别和评估美国股市中的盘形和支撑位,《专家系统与应用》,第 39 卷,第 6301-6308 页
-
Zwart, G.; Markwat, T.; Swinkels, L.; van Dijk, D. (2009) 新兴货币市场中基本面和技术信息的经济价值,《国际货币与金融期刊》,第 28 卷,第 581-604 页
第十一章:资产负债管理
本章介绍了 R 在商业银行资产负债管理(ALM)中的应用。银行的 ALM 职能传统上与银行账本头寸的利率风险和流动性风险管理相关。利率定位和流动性风险管理都需要对银行产品进行建模。如今,专业的 ALM 部门使用复杂的企业风险管理(ERM)框架,这些框架能够整合各种风险类型的管理,并为 ALM 提供适当的工具,以指导资产负债表的管理。我们的总体目标是建立一个简化的 ALM 框架,展示如何使用 R 完成某些 ALM 任务。这些任务基于利率和流动性风险管理以及非到期账户的建模。
本章结构如下。我们从 ALM 分析的数据准备过程开始。规划和度量过程需要关于银行账本、市场状况和商业战略的特殊信息。本部分建立了一个数据管理工具,由主要的输入数据集组成,并将数据提取成我们在本章其余部分使用的形式。
接下来,我们将处理利率风险的度量。在银行业中,量化银行账本中的利率风险有两种常见方法。较简单的技术使用重新定价缺口表分析来管理利率风险敞口,并计算平行收益曲线冲击以预测净利息收入(NII)并计算股本市场价值(MVoE)。更先进的方法使用资产负债表的动态模拟和利率发展的随机模拟。选择使用哪种工具取决于目标和资产负债表的结构。
例如,一家储蓄银行(负债方有客户定期存款,资产方有固定收益债券投资)关注其股本市场价值风险,而一家企业银行(拥有浮动利率头寸)则专注于净利息收入风险。我们将演示如何使用 R 有效地提供重新定价缺口表和净利息收入预测。
我们的第三个主题与流动性风险有关。我们定义了三种类型的流动性风险:结构性、资金性和应急性风险。结构性流动性风险源于资产和负债方不同的合同到期。商业银行通常收集短期客户存款,并将获得的资金投入到长期客户贷款中。因此,银行面临负债方的滚动风险,因为无法确定有多少到期的短期客户资金将被展期,这可能危及银行的偿付能力。资金流动性风险发生在展期过程中;它指的是更新资金成本的不确定性。在日常业务中,即使银行能够展期到期的同业存款,交易成本也高度依赖市场上的流动性状况。应急性风险指的是客户在不可预见情境下的行为。例如,应急性风险可能表现为定期存款的突然提取或客户贷款的提前偿还。虽然 ALM 可以通过调节银行的头寸适当处理结构性和资金性流动性风险,但应急性流动性风险只能通过储备流动性资产来对冲。我们展示了如何建立流动性缺口表并预测净融资需求。
在本章的最后一节,我们将重点讨论非到期产品的建模。客户产品可以根据其到期结构和利率行为进行分类。典型的非到期负债产品示例包括活期存款和没有提前通知期的储蓄账户。客户可以随时提取他们的资金,而银行则有权调整提供的利率。在资产端,透支和信用卡具有类似的特征。非到期产品的复杂模型使得资产负债管理(ALM)工作变得相当具有挑战性。实际上,非到期产品的建模意味着现金流配置的映射、需求的利率弹性估算,以及在内部资金转移定价(FTP)系统中分析流动性相关的成本。这里,我们展示了如何衡量非到期存款的利率敏感性。
数据准备
复杂的 ERM 软件是银行业必不可少的工具,用于量化净利息收入和股本市场风险,并特别准备有关资产负债组合、再定价缺口和流动性头寸的报告。我们使用 R 语言设置了一个简化的模拟和报告环境,重现了商业使用的 ALM 软件解决方案的关键特性。
典型的 ALM 数据处理流程遵循所谓的提取、转换和加载(ETL)逻辑。

提取(这是第一阶段)意味着银行已经从本地数据仓库(DWH)、中台、控制系统或会计系统中收集了交易级别和账户级别的源数据。整个资产负债表的源数据(在这里称为组合)也被提取,以节省计算时间、内存和存储空间。此外,单笔交易级别数据按给定维度(例如,按货币面额、利率行为、摊销结构等)进行了汇总。市场数据(如收益曲线、市场价格和波动率表面)也被准备为原始数据集。下一步是设置模拟参数(例如,收益曲线冲击和续期业务的交易量增量),我们称之为策略。为了简化起见,这里我们将策略简化为保持现有组合,因此资产负债表在预测期内保持不变。
在转换阶段,组合、市场和策略数据集被结合在一起用于进一步分析,并转化为新的结构。用我们的话来说,这意味着现金流表是通过使用组合和市场描述符生成的,并且它被转化为一种狭义的数据形式。
在加载时,结果被写入到报告表格中。通常,用户可以定义哪些组合维度和风险度量的值应被加载到结果数据库中。我们将在接下来的章节中展示如何衡量和记录流动性风险和利率风险。
数据源初看之下
我们将列出资产负债表项目的数据源称为“组合”。市场数据(如收益曲线、市场价格和波动率表面)也被准备为原始数据集。让我们将初始数据集导入 R。首先,我们需要从 Packt Publishing 的链接下载本章将使用的数据集和函数。现在,让我们导入存储在本地文件夹中的示例组合和市场数据集,这些数据集以标准csv格式存储,并在代码中如下所示使用:
portfolio <- read.csv("portfolio.csv")
market <- read.csv("market.csv")
选定的数据集包含需要转换为适当格式的日期。我们通过as.Date函数转换日期格式:
portfolio$issue <- as.Date(portfolio$issue, format = "%m/%d/%Y")
portfolio$maturity <- as.Date(portfolio$maturity, format = "%m/%d/%Y")
market$date <- as.Date(market$date, format = "%m/%d/%Y")
使用head(portfolio)命令打印导入的portfolio数据集的前几行。其结果如下:
head(portfolio)
id account account_name volume
1 1 cb_1 Cash and balances with central bank 930
2 2 mmp_1 Money market placements 1404
3 3 mmp_1 Money market placements 996
4 4 cl_1 Corporate loans 515
5 5 cl_1 Corporate loans 655
6 6 cl_1 Corporate loans 560
ir_binding reprice_freq spread issue maturity
1 FIX NA 5 2014-09-30 2014-10-01
2 FIX NA 7 2014-08-30 2014-11-30
3 FIX NA 10 2014-06-15 2014-12-15
4 LIBOR 3 301 2014-05-15 2016-04-15
5 LIBOR 6 414 2014-04-15 2016-04-15
6 LIBOR 3 345 2014-03-15 2018-02-15
repayment payment_freq yieldcurve
1 BULLET 1 EUR01
2 BULLET 1 EUR01
3 BULLET 1 EUR01
4 LINEAR 3 EUR01
5 LINEAR 6 EUR01
6 LINEAR 3 EUR01
该数据框的列代表识别号(行号)、账户类型和产品特征。前三列表示产品标识符、账户标识符(或简称)以及账户的长名称。通过使用levels函数,我们可以轻松列出与典型商业银行产品或资产负债表项目相关的账户类型:
levels(portfolio$account_name)
[1] "Available for sale portfolio"
[2] "Cash and balances with central bank"
[3] "Corporate loans"
[4] "Corporate sight deposit"
[5] "Corporate term deposit"
[6] "Money market placements"
[7] "Other non-interest bearing assets"
[8] "Other non-interest bearing liabilities"
[9] "Own issues"
[10] "Repurchase agreements"
[11] "Retail overdrafts"
[12] "Retail residential mortgage"
[13] "Retail sight deposit"
[14] "Retail term deposit"
[15] "Unsecured money market funding"
portfolio数据集还包含欧元名义金额、利率绑定类型(固定利率或 LIBOR)、账户的重新定价频率(以月为单位,如果利率绑定为 LIBOR),以及利率的基点差值。此外,其他列描述了产品的现金流结构。列包括发行日期(即第一次重新定价的日期)、到期日期、本金偿还结构类型(一次性偿还、线性偿还或年金偿还),以及偿还频率(以月为单位)。最后一列存储利率曲线的标识符,用于计算未来浮动利率支付。
实际的利率存储在market数据集中。让我们列出前几行数据来检查其内容:
head(market)
type date rate comment
1 EUR01 2014-09-01 0.3000000 1M
2 EUR01 2014-12-01 0.3362558 3M
3 EUR01 2015-03-01 -2.3536463 6M
4 EUR01 2015-09-01 -5.6918763 1Y
5 EUR01 2016-09-01 -5.6541774 2Y
6 EUR01 2017-09-01 1.0159576 3Y
第一列表示收益率曲线的类型(例如,收益率来自债券市场或银行间市场)。type列必须与portfolio中的列相同,以便将两个数据集连接起来。date列显示当前利率的到期日,rate表示利率的基点值。如您所见,此时的收益率曲线非常不寻常,因为某些期限的收益率曲线点为负值。最后一列存储收益率曲线期限的标签。
数据集反映了银行投资组合的当前状态以及当前的市场环境。在我们的分析中,实际日期为 2014 年 9 月 30 日。我们将其声明为一个名为NOW的日期变量:
NOW <- as.Date("09/30/2014", format = "%m/%d/%Y")
现在,我们已经完成了源数据的准备。这是作者为说明目的创建的示例数据集,展示了一个假设的商业银行资产负债表结构的简化版本。
现金流生成函数
在导入资产负债表的静态数据和当前的收益率曲线后,我们使用这些信息来生成银行的总现金流。首先,我们利用远期收益率曲线计算浮动利率;然后,我们可以分别生成本金和利息现金流。为此,我们预定义了基本函数,以根据支付频率计算本金现金流,并提取浮动利率用于可变利率产品。此脚本也可以在 Packt Publishing 提供的链接中找到。
将其复制到本地文件夹,并从工作目录运行预定义函数的脚本。
source("bankALM.R")
这个源文件加载了xts、zoo、YieldCurve、reshape和car包,并在必要时安装这些必需的包。让我们看一下这个脚本文件中我们使用的最重要的函数。cf函数生成一个预定义的现金流结构。例如,生成一个名义金额为 100 欧元、到期日为三年、固定利率为 10%的一次性偿还贷款结构如下:
cf(rate = 0.10, maturity = 3, volume = 100, type = "BULLET")
$cashflow
[1] 10 10 110
$interest
[1] 10 10 10
$capital
[1] 0 0 100
$remaining
[1] 100 100 0
该函数提供了完整的现金流、利息和本金偿还结构,以及每个期间剩余资本的价值。get.yieldcurve.spot函数提供了一条在某些日期序列上的拟合现货收益率曲线。该函数使用了我们之前已经加载的YieldCurve包。让我们定义一个测试日期变量,如下所示:
test.date <- seq(from = as.Date("09/30/2015", format = "%m/%d/%Y"), to = as.Date("09/30/2035", format = "%m/%d/%Y") , by = "1 year")
使用market数据获取并绘制指定日期的拟合现货收益率:
get.yieldcurve.spot(market, test.date, type = "EUR01", now = NOW, showplot = TRUE)
以下截图是前述命令的结果:

前述图表绘制了观察到的收益率曲线(点)和拟合的收益率曲线(线)。查看get.yieldcurve.forward和get.floating函数时,我们发现它们都使用了资产负债表产品的重新定价日期。以下示例生成了一个 20 个时间点的重新定价日期序列。
test.reprice.date <- test.date[seq(from = 1,to = 20, by = 2)]
使用market数据提取远期收益率曲线:
test.forward <- get.yieldcurve.forward(market, test.reprice.date,type = "EUR01", now = NOW)
现在,让我们生成浮动利率,并通过将showplot选项设置为 TRUE,展示远期曲线和test.floating变量之间的差异。
test.floating<-get.floating(test.date, test.reprice.date, market, type = "EUR01", now = NOW, shoplot = TRUE)
以下截图展示了前述命令的输出结果:

如你所见,浮动利率预测由阶梯函数组成。为了定价,浮动利率由实际远期利率替代;然而,浮动利率仅在重新定价时更新。
准备现金流
在接下来的步骤中,我们将展示从我们的portfolio和market数据集中生成的现金流表。cf.table函数调用了前面详细介绍的函数,并提供了具有id标识号的准确产品的现金流。在portfolio数据集中,标识号必须是整数,并且必须按升序排列。实际上,每个标识号应该是给定行的行号。让我们生成所有产品的现金流:
cashflow.table <- do.call(rbind, lapply(1:NROW(portfolio), function(i) cf.table(portfolio, market, now = NOW, id = i)))
由于portfolio数据集包含 147 个产品,运行此代码可能需要几秒钟(10-60 秒)。当准备好时,让我们检查显示前几行的结果:
head(cashflow.table)
id account date cf interest capital remaining
1 1 cb_1 2014-10-01 930.0388 0.03875 930 0
2 2 mmp_1 2014-10-30 0.0819 0.08190 0 1404
3 2 mmp_1 2014-11-30 1404.0819 0.08190 1404 0
4 3 mmp_1 2014-10-15 0.0830 0.08300 0 996
5 3 mmp_1 2014-11-15 0.0830 0.08300 0 996
6 3 mmp_1 2014-12-15 996.0830 0.08300 996 0
现在我们已经完成了现金流表的创建。我们还可以计算产品的现值,以及银行股本的市场价值。让我们在下面的循环中运行pv.table函数:
presentvalue.table <- do.call(rbind, lapply(1:NROW(portfolio), function (i) pv.table(cashflow.table[cashflow.table$id == portfolio$id[i],], market, now = NOW)))
打印表格的初始行以检查结果:
head(presentvalue.table)
id account date presentvalue
1 1 cb_1 2014-09-30 930.0384
2 2 mmp_1 2014-09-30 1404.1830
3 3 mmp_1 2014-09-30 996.2754
4 4 cl_1 2014-09-30 530.7143
5 5 cl_1 2014-09-30 689.1311
6 6 cl_1 2014-09-30 596.3629
结果可能会略有不同,因为Svensson方法可能会产生不同的输出。为了获得股本的市场价值,我们需要将现值相加。
sum(presentvalue.table$presentvalue)
[1] 14021.19
现金流表将负债视为负资产;因此,将所有项相加可以得出合适的结果。
利率风险度量
管理利率风险是资产负债管理中最重要的组成部分之一。利率的变化可能会影响利息收入和股权的市场价值。利率管理关注的是净利息收入的敏感性。净利息收入(NII)等于利息收入与利息支出的差额:

这里,SA 和 SL 分别表示利率敏感资产和负债,NSA 和 NSL 表示非敏感资产和负债。资产和负债的利率分别标注为
和
。传统的利率风险定位方法是基于缺口模型。利率缺口指的是在特定时间段内,带息资产和负债的净资产位置,这些资产和负债会同时重新定价。利率缺口(G)等于:

重新定价缺口表格显示了资产负债表中按重新定价时间和重新定价基础(即 3 个月 EURIBOR 或 6 个月 EURIBOR)分组的这些带息项目。利息收入的变化可以通过带风险项目与利率变化(Δi)的乘积来表征,如下所示:

从利率风险的角度来看,缺口的符号至关重要。正缺口表示当利率上升时收益增加,利率下降时收益减少。重新定价缺口表还可以通过根据参考利率(即 3 个月或 6 个月 EURIBOR)聚合带息资产和负债来捕捉基差风险。利率缺口表可以作为足够的工具来从收益的角度确定风险敞口。然而,缺口模型不能单独作为量化整个资产负债表净利息收入风险的风险度量。利率缺口是管理工具,为利率风险定位提供指导。
在这里,我们展示了如何构建净利息收入和重新定价缺口表格,以及如何创建关于净利息收入期限结构的图表。我们从cashflow.table数据构建利率缺口表格。延续上一节,我们使用预定义的nii.table函数来生成所需的数据形式:
nii <- nii.table(cashflow.table, now = NOW)
考虑到未来 7 年的净利息收入表,我们得到如下表格:
round(nii[,1:7], 2)
2014 2015 2016 2017 2018 2019 2020
afs_1 6.99 3.42 0.00 0.00 0.00 0.00 0.00
cb_1 0.04 0.00 0.00 0.00 0.00 0.00 0.00
cl_1 134.50 210.04 88.14 29.38 0.89 0.00 0.00
cor_sd_1 -3.20 -11.16 -8.56 -5.96 -3.36 -0.81 0.00
cor_td_1 -5.60 -1.99 0.00 0.00 0.00 0.00 0.00
is_1 -26.17 -80.54 -65.76 -48.61 -22.05 -1.98 0.00
mmp_1 0.41 0.00 0.00 0.00 0.00 0.00 0.00
mmt_1 -0.80 -1.60 0.00 0.00 0.00 0.00 0.00
oth_a_1 0.00 0.00 0.00 0.00 0.00 0.00 0.00
oth_l_1 0.00 0.00 0.00 0.00 0.00 0.00 0.00
rep_1 -0.05 0.00 0.00 0.00 0.00 0.00 0.00
ret_sd_1 -8.18 -30.66 -27.36 -24.06 -20.76 -17.46 -14.16
ret_td_1 -10.07 -13.27 0.00 0.00 0.00 0.00 0.00
rm_1 407.66 1532.32 1364.32 1213.17 1062.75 908.25 751.16
ro_1 137.50 187.50 0.00 0.00 0.00 0.00 0.00
total 633.04 1794.05 1350.78 1163.92 1017.46 888.00 736.99
很容易看出哪个账户为银行带来了利息收入或成本。净利率表可以绘制如下:
barplot(nii, density = 5*(1:(NROW(nii)-1)), xlab = "Maturity", cex.names = 0.8, Ylab = "EUR", cex.axis = 0.8, args.legend = list(x = "right"))
title(main = "Net interest income table", cex = 0.8, sub = paste("Actual date: ",as.character(as.Date(NOW))) )par (fig = c(0, 1, 0, 1), oma = c(0, 0, 0, 0),mar = c(0, 0, 0, 0),new = TRUE)
plot(0, 0, type = "n", bty = "n", xaxt = "n", yaxt = "n")
legend("right", legend = row.names(nii[1:(NROW(nii)-1),]), density = 5*(1:(NROW(nii)-1)), bty = "n", cex = 1)
结果如下图所示:

现在,我们可以通过编制再定价缺口表来探索再定价缺口。我们使用预定义的repricing.gap.table函数获取每月缺口,然后用barplot绘制结果。
(repgap <- repricing.gap.table(portfolio, now = NOW))
1M 2M 3M 4M 5M 6M 7M 8M 9M 10M 11M 12M
volume 6100 9283 725 1787 7115 6031 2450 5919 2009 8649 6855 2730
barplot(repgap, col = "gray", xlab = "Months", ylab = "EUR")
title(main = "Repricing gap table", cex = 0.8, sub = paste("Actual date: ",as.character(as.Date(NOW))))
使用前面的代码,我们可以说明未来 12 个月的边际缺口:

我们必须提到,对于利率风险管理,还有更复杂的工具。实际上,风险管理中应用了模拟模型。然而,银行账簿风险在《巴塞尔协议 II》第一支柱下没有明确地要求资本充足率;第二支柱涵盖了银行账簿中的利率风险。监管机构特别强调市场价值评估的风险。风险限额基于特定的压力情景,这些情景可以是确定性的利率冲击或基于历史波动性的风险收益概念。因此,风险测量技术代表了基于情景或随机模拟的方法,侧重于利息收入或股本的市场价值。净利息收入模拟是一种动态的、前瞻性的方法,而股本市场价值的计算则提供了一个静态的结果。股本久期也是衡量银行账簿利率风险的广泛使用的指标。资产和负债的久期被计算出来,以量化股本的久期。ALM 专业人员通常使用有效久期,它在利率敏感度计算中考虑了嵌入期权(如上限、下限等)。
流动性风险测量
传统的流动性风险测量工具是所谓的静态和动态流动性缺口表。流动性缺口表提供了资产负债表的现金流视图,并根据其合同现金流入和现金流出将资产负债表项目组织成到期时间段。每个时段的净现金流缺口显示了银行的结构性流动性头寸。静态视图假设资产负债表逐步减少,而动态流动性表还考虑了来自展期和新业务的现金流。为了简单起见,这里仅演示了流动性头寸的静态视图。
从准备日常现金流头寸开始。有时,我们需要知道在某个特定日期的预测流动性头寸。通过日期聚合cashflow.table非常容易。
head(aggregate(. ~ date, FUN = sum, data = subset(cashflow.table,select = -c(id, account))))
date cf interest capital remaining
1 2014-10-01 930.0387500 0.0387500 930.0000 0.00
2 2014-10-14 0.6246667 0.6246667 0.0000 3748.00
3 2014-10-15 2604.2058990 127.5986646 2476.6072 13411.39
4 2014-10-28 390.7256834 124.6891519 266.0365 23444.96
5 2014-10-30 -3954.2638670 52.6149502 -4006.8788 -33058.12
6 2014-10-31 -0.1470690 -0.1470690 0.0000 -2322.00
其次,我们来准备一个流动性缺口表并创建一个图表。我们还可以使用预定义的函数(lq.table)并检查生成的表格。
lq <- lq.table(cashflow.table, now = NOW)
round(lq[,1:5],2)
1M 2-3M 3-6M 6-12M 1-2Y
afs_1 2.48 3068.51 14939.42 0.00 0.00
cb_1 930.04 0.00 0.00 0.00 0.00
cl_1 3111.11 0.00 649.51 2219.41 2828.59
cor_sd_1 -217.75 -217.73 -653.09 -1305.69 -2609.42
cor_td_1 -1.90 -439.66 -6566.03 0.00 0.00
is_1 -8.69 -17.48 -2405.31 -319.80 -589.04
mmp_1 0.16 2400.25 0.00 0.00 0.00
mmt_1 -0.12 -0.54 -0.80 -1201.94 0.00
oth_a_1 0.00 0.00 0.00 0.00 0.00
oth_l_1 0.00 0.00 0.00 0.00 0.00
rep_1 -500.05 0.00 0.00 0.00 0.00
ret_sd_1 -186.08 -186.06 -558.04 -1115.47 -2228.46
ret_td_1 -4038.96 -5.34 -5358.13 -3382.91 0.00
rm_1 414.40 808.27 1243.86 2093.42 4970.14
ro_1 466.67 462.50 1362.50 2612.50 420.83
total -28.69 5872.72 2653.89 -400.48 2792.63
为了绘制流动性缺口图,我们可以使用barplot函数,代码如下:
plot.new()
par.backup <- par()
par(oma = c(1, 1, 1, 6), new = TRUE)
barplot(nii, density=5*(1:(NROW(nii)-1)), xlab="Maturity", cex.names=0.8, ylab = "EUR", cex.axis = 0.8, args.legend = list(x = "right"))
title(main = "Net interest income table", cex = 0.8, sub = paste("Actual date: ",as.character(as.Date(NOW))) )
par(fig = c(0, 1, 0, 1), oma = c(0, 0, 0, 0),mar = c(0, 0, 0, 0), new = TRUE)
plot(0, 0, type = "n", bty = "n", xaxt = "n", yaxt = "n")
legend("right", legend = row.names(nii[1:(NROW(nii)-1),]), density = 5*(1:(NROW(nii)-1)), bty = "n", cex = 1)
par(par.backup)
barplot函数的输出如下:

图表中的条形图显示了每个时间段的流动性缺口。带有方块的虚线代表净流动性状况(资金需求),而实线黑线显示的是累积流动性缺口。
非到期存款建模
非到期存款(NMD)在银行中的重要性相当高,因为商业银行资产负债表的大部分由具有非合同现金流特征的客户产品组成。非到期存款是特殊的金融工具,因为银行可以随时调整存款账户的利率,而客户则可以随时提取账户中的任何金额,无需提前通知。这些产品的流动性和利率风险管理是资产负债管理(ALM)分析中的关键部分;因此,非到期存款的建模需要特别关注。不确定的到期时间和利率特征使得它们在对冲、内部转移定价和风险建模方面具有较高的复杂性。
存款利率发展模型
在以下代码中,我们使用了从欧洲中央银行统计数据库查询到的奥地利非到期存款时间序列数据,这些数据是公开可用的。我们的数据集包含每月存款利率(cpn)、月末余额(bal)和 1 个月 EURIBOR 固定利率(eur1m)。这些时间序列存储在本地文件夹中的一个csv文件里。相应的命令如下:
nmd <- read.csv("ecb_nmd_data.csv")
nmd$date <- as.Date(nmd$date, format = "%m/%d/%Y")
首先,我们使用以下命令绘制 1 个月 EURIBOR 利率和存款利率的发展:
library(car)
plot(nmd$eur1m ~ nmd$date, type = "l", xlab="Time", ylab="Interest rate")
lines(nmd$cpn~ nmd$date, type = "l", lty = 2)
title(main = "Deposit coupon vs 1-month Euribor", cex = 0.8 )
legend("topright", legend = c("Coupon","EUR 1M"), bty = "n", cex = 1, lty = c(2, 1))
以下截图显示了存款息票与 1 个月 EURIBOR的图表:

我们的第一个目标是估计误差修正模型(ECM),以描述 1 个月 EURIBOR 利率对非到期存款利率的长期解释力。从监管角度来看,近年来,测量市场利率对存款利率的传导效应变得尤为重要。欧洲央行要求欧元区银行在某些压力测试情景中估算这一传导效应。我们使用 Engle-Granger 两步法来估计 ECM 模型。在第一步中,我们使用回归模型估计协整向量。然后,我们获取残差,并在第二步中利用误差修正机制估计 EURIBOR 对存款利率的长期和短期影响。在第一步之前,我们必须测试两个时间序列是否在同一阶数上积分。因此,我们使用urca包对原始和差分时间序列运行 Augmented Dickey-Fuller(ADF)和 KPSS 测试。脚本如下:
library(urca)
attach(nmd)
#Unit root test (ADF)
cpn.ur <- ur.df(cpn, type = "none", lags = 2)
dcpn.ur <- ur.df(diff(cpn), type = "none", lags = 1)
eur1m.ur <- ur.df(eur1m, type = "none", lags = 2)
deur1m.ur <- ur.df(diff(eur1m), type = "none", lags = 1)
sumtbl <- matrix(cbind(cpn.ur@teststat, cpn.ur@cval,
dcpn.ur@teststat, dcpn.ur@cval,
eur1m.ur@teststat, eur1m.ur@cval,
deur1m.ur@teststat, deur1m.ur@cval), nrow=4)
colnames(sumtbl) <- c("cpn", "diff(cpn)", "eur1m", "diff(eur1m)")
rownames(sumtbl) <- c("Test stat", "1pct CV", "5pct CV", "10pct CV")
#Stationarty test (KPSS)
cpn.kpss <- ur.kpss(cpn, type = "mu")
eur1m.kpss <- ur.kpss(eur1m, type = "mu")
sumtbl <- matrix(cbind( cpn.kpss@teststat, cpn.kpss@cval, eur1m.kpss@teststat, eur1m.kpss@cval), nrow = 5)
colnames(sumtbl) <- c("cpn", "eur1m")
rownames(sumtbl) <- c("Test stat", "10pct CV", "5pct CV", "2.5pct CV", 1pct CV")
print(cpn.ur@test.name)
print(sumtbl)
print(cpn.kpss@test.name)
print(sumtbl)
结果,我们得到以下摘要表:
Augmented Dickey-Fuller Test
cpn diff(cpn) eur1m diff(eur1m)
Test stat -0.9001186 -5.304858 -1.045604 -5.08421
1pct CV -2.5800000 -2.580000 -2.580000 -2.58000
5pct CV -1.9500000 -1.950000 -1.950000 -1.95000
10pct CV -1.6200000 -1.620000 -1.620000 -1.62000
KPSS
cpn eur1m
Test stat 0.8982425 1.197022
10pct CV 0.3470000 0.347000
5pct CV 0.4630000 0.463000
2.5pct CV 0.5740000 0.574000
1pct CV 0.7390000 0.739000
对原始时间序列的 ADF 测试的原假设不能拒绝,但测试结果表明存款利率和 1 个月 EURIBOR 时间序列的第一差分不含单位根。这意味着这两个序列都是一阶整合的,它们是 I(1) 过程。KPSS 测试得出了类似的结果。下一步是通过检验简单回归方程的残差来检验这两个 I(1) 序列的协整性,其中我们将存款利率对 1 个月 EURIBOR 利率进行回归。估计协整方程:
lr <- lm(cpn ~ eur1m)
res <- resid(lr)
lr$coefficients
(Intercept) eur1m
0.3016268 0.3346139
对残差进行单位根检验,如下所示:
res.ur <- ur.df(res, type = "none", lags = 1)
summary(res.ur)
###############################################
# Augmented Dickey-Fuller Test Unit Root Test #
###############################################
Test regression none
Call:
lm(formula = z.diff ~ z.lag.1 - 1 + z.diff.lag)
Residuals:
Min 1Q Median 3Q Max
-0.286780 -0.017483 -0.002932 0.019516 0.305720
Coefficients:
Estimate Std. Error t value Pr(>|t|)
z.lag.1 -0.14598 0.04662 -3.131 0.00215 **
z.diff.lag -0.06351 0.08637 -0.735 0.46344
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.05952 on 131 degrees of freedom
Multiple R-squared: 0.08618, Adjusted R-squared: 0.07223
F-statistic: 6.177 on 2 and 131 DF, p-value: 0.002731
Value of test-statistic is: -3.1312
Critical values for test statistics:
1pct 5pct 10pct
tau1 -2.58 -1.95 -1.62
ADF 测试的统计量低于 1% 的临界值,因此我们可以得出残差是平稳的结论。这意味着存款券和 1 个月 EURIBOR 是协整的,因为这两个 I(1) 时间序列的线性组合给出了一个平稳过程。协整的存在很重要,因为它是误差修正模型估计的前提条件。ECM 方程的基本结构如下:

我们估计 X 对 Y 的长期和短期效应;来自协整方程的滞后残差代表误差修正机制。
系数衡量短期修正部分,而
是长期均衡关系的系数,捕捉 X 偏离均衡的修正。现在,我们使用 dynlm 包估计 ECM 模型,该包适用于估计具有滞后的动态线性模型:
install.packages('dynlm')
library(dynlm)
res <- resid(lr)[2:length(cpn)]
dy <- diff(cpn)
dx <- diff(eur1m)
detach(nmd)
ecmdata <- c(dy, dx, res)
ecm <- dynlm(dy ~ L(dx, 1) + L(res, 1), data = ecmdata)
summary(ecm)
Time series regression with "numeric" data:
Start = 1, End = 134
Call:
dynlm(formula = dy ~ L(dx, 1) + L(res, 1), data = ecmdata)
Residuals:
Min 1Q Median 3Q Max
-0.36721 -0.01546 0.00227 0.02196 0.16999
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.0005722 0.0051367 -0.111 0.911
L(dx, 1) 0.2570385 0.0337574 7.614 4.66e-12 ***
L(res, 1) 0.0715194 0.0534729 1.337 0.183
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.05903 on 131 degrees of freedom
Multiple R-squared: 0.347, Adjusted R-squared: 0.337
F-statistic: 34.8 on 2 and 131 DF, p-value: 7.564e-13
1 个月 EURIBOR 的滞后变化在短期内对存款利率的修正为 25.7% (
)。我们不能得出长期均衡偏差没有被修正的结论,因为 beta2 不显著并且具有正值,这意味着错误并没有被修正,而是被 7% 放大。经济解释是我们无法识别 NMD 票息和 1 个月 EURIBOR 利率之间的长期关系,但 EURIBOR 的偏差在短期内通过 25.7% 反映在票息中。
非到期存款的静态复制
对于非到期存款的利率相关风险,一种可能的对冲方法是构建一个复制组合,由零息票工具组成,以模拟非到期存款的利息支付,并通过复制工具的较高收益与存款账户的低利息之间的差额来赚取利润。
假设我们在复制投资组合中包括 1 个月和 3 个月的欧元货币市场工具,以及 1 年、5 年和 10 年的政府基准债券。我们从欧洲央行统计数据仓库查询了收益率的历史时间序列,并将数据存储在本地文件夹中的 csv 文件中。我们将使用以下命令调用该 csv 文件:
ecb.yc <- read.csv("ecb_yc_data.csv")
ecb.yc$date <- as.Date(ecb.yc$date, format = "%d/%m/%Y")
绘制结果:
matplot(ecb.yc$date, ecb.yc[,2:6], type = "l", lty = (1:5), lwd = 2, col = 1, xlab = "Time", ylab = "Yield", ylim = c(0,6), xaxt = "n")
legend("topright", cex = 0.8, bty = "n", lty = c(1:5), lwd = 2, legend = colnames(ecb.yc[,2:6]))
title(main = "ECB yield curve", cex = 0.8)
axis.Date(1,ecb.yc$date)
以下截图显示了欧洲中央银行的收益率曲线:

我们的目标是计算复制投资组合中五个对冲工具的投资组合权重,确保在给定的时间范围内,与存款利息(cpn)相比,保证最低波动率。换句话说,我们希望最小化复制投资组合利息收入的跟踪误差。该问题可以通过以下最小二乘法最小化公式来表达:

这受以下条件的限制:



这里,A 是
矩阵的历史利率,b 是存款利息的向量,x 是投资组合权重的向量。需要最小化的函数是向量 b 和矩阵 A 的列与向量 x 的线性组合之间的平方差。第一个条件是,投资组合权重必须是非负的,并且总和为 1。我们引入了一个额外条件,即投资组合的平均到期时间应该等于 l 常数。向量 m 包含五个对冲工具的到期时间(月)。这一约束的依据是,银行通常假设非到期存款的核心部分会在银行停留较长时间。这个长期部分的期限通常是通过一个量化模型来推导的,该模型可能是 ARIMA 模型,或者是一个依赖于市场利率和存款利息的动态模型。
为了解决优化问题,我们使用来自 quadprog 包的 solve.QP 函数。该函数适用于解决具有等式和不等式约束的二次优化问题。我们重新构造了最小二乘法最小化问题,以推导出 solve.QP 函数的适当参数矩阵(A'A)和参数向量(b'A)。
我们还设置了
,假设复制投资组合的最终到期时间为 5 年,该投资组合通过以下命令模仿了 NMD 投资组合核心部分的流动性特征:
library(quadprog)
b <- nmd$cpn[21:135]
A <- cbind(ecb.yc$EUR1M, ecb.yc$EUR3M, ecb.yc$EUR1Y, ecb.yc$EUR5Y, ecb.yc$EUR10Y)
m <- c(1, 3, 12, 60, 120)
l <- 60
stat.opt <- solve.QP( t(A) %*% A, t(b) %*% A, cbind( matrix(1, nr = 5, nc = 1), matrix(m, nr = 5, nc = 1), diag(5)), c(1, l, 0,0,0,0,0), meq=2 )
sumtbl <- matrix(round(stat.opt$solution*100, digits = 1), nr = 1)
colnames(sumtbl) <- c("1M", "3M", "1Y", "5Y", "10Y")
cat("Portfolio weights in %")
Portfolio weights in % > print(sumtbl)
1M 3M 1Y 5Y 10Y
[1,] 0 51.3 0 0 48.7
我们的结果表明,根据历史校准,我们应该在复制组合中保持 51%的三个月货币市场投资和 49%的十年期政府债券工具,以最小的追踪误差复制非到期存款的票息变化。使用这些投资组合权重,通过以下代码计算我们复制组合的收入和存款账户的支出:
mrg <- nmd$cpn[21:135] - stat.opt$solution[2]*ecb.yc$EUR3M + stat.opt$solution[5]*ecb.yc$EUR10Y
plot(mrg ~ ecb.yc$date, type = "l", col = "black", xlab="Time", ylab="%")
title(main = "Margin of static replication", cex = 0.8 )
以下图表显示了静态复制的保证金:

如你所见,由于使用这种静态策略进行复制,银行能够在 2010 年左右赚取更多利润,那时短期利率和长期利率之间的期限利差异常高。
摘要
在本章中,我们展示了如何利用 R 支持商业银行的资产和负债管理过程。从数据准备到报告的各个任务,R 编程语言都可以提供帮助或解决重复性问题。然而,我们仅简要介绍了如何解决利率和流动性测量问题。我们还提供了一些关于非到期存款利率敏感性统计估计的示例。你可以在以下内容中找到实践知识:
-
从银行投资组合和市场数据生成现金流
-
基本利率风险管理的测量和报告工具
-
基本流动性风险管理的测量和报告工具
-
建模非到期存款的行为
我们认为,本章是本书中银行管理话题的有机组成部分。资产和负债管理带来了银行管理中的特殊问题集,而 R 作为一种开源语言,具有多功能的包库,可以有效地为从业者提供有价值的工具。
参考文献
-
Bessis, Joel (2011):银行风险管理,约翰·威利与子公司。
-
Choudhry, Moorad (2011):银行资产和负债管理:战略、交易、分析,约翰·威利与子公司。
-
Matz, Leonard 和 Neu, Peter (2006):流动性风险测量与管理:全球最佳实践的从业者指南,约翰·威利与子公司。
第十二章:资本充足性
正如我们在上一章中学到的,银行业是一个特别具有风险的行业,客户资金的安全是首要任务。为了确保银行能够实现这一首要目标,整个行业都受到严格的监管。建立规则以避免银行倒闭并保护客户财富,一直是监管者的重要任务。资本充足性或资本要求是为实现这一目标服务的最重要监管工具之一。如果不是最重要的话。鉴于金融行业的高杠杆,银行和其他金融机构不能自由使用其所有资产。这些公司需要保持足够的资本,以确保即使情况恶化,也能安全运营和保持偿付能力。
不同国家有不同的银行监管机构(金融监管机构、中央银行等)和监管标准。然而,随着银行体系的全球化,一个全球统一的标准变得必要。1974 年,巴塞尔银行监管委员会(BCBS)由 G-10 中央银行成立,旨在提供可以适用于全球不同国家的银行监管标准。
自那时以来,这一经济领域发展迅速,越来越多复杂的数学方法被应用于风险管理和资本充足性计算。R 是一个非常强大的工具,它完全能够解决这些复杂的数学和分析问题。因此,许多银行将其作为风险管理的重要工具并不令人惊讶。
巴塞尔协议原则
1988 年,BCBS 在瑞士巴塞尔发布了一项监管框架,设定了银行为最小化破产风险所需持有的最低资本。所谓的第一版巴塞尔协议,现在称为巴塞尔协议 I,于 1992 年在所有 G-10 国家通过法律实施。到 2009 年,27 个司法辖区参与了巴塞尔监管框架(巴塞尔委员会的历史可以在www.bis.org/bcbs/history.htm阅读)。
巴塞尔协议 I
第一版巴塞尔协议主要关注信用风险,并规范了考虑不同资产类别的适当风险加权。根据该协议,银行的资产应根据信用风险进行分类,每个类别的暴露应按定义的措施(0%、20%、50%和 100%)进行加权。得出的风险加权资产(RWA)用于资本充足性的确定。根据巴塞尔协议 I 的法律规定,活跃于国际市场的银行需持有至少 8%的 RWA 资本。这被称为最低资本比率(参见巴塞尔银行监管委员会(章程) www.bis.org/bcbs/charter.htm)。
所谓的表外项目,如衍生品、未使用的承诺和信用证,已被包括在风险加权资产(RWA)中,并应报告。
《巴塞尔协议》旨在随着时间的推移进行修订和完善,以解决除信用风险以外的其他风险问题。此外,协议还进行了修订,以便为资本充足性计算中包含的某些资产类别提供更合适的定义,并承认随后的新识别效应。
《巴塞尔协议 I》还定义了其他资本比率,以量化银行的资本充足性。这些资本比率被视为与所有风险加权资产(RWA)相关的所谓“分级资本”元素。分级资本元素包括根据《巴塞尔协议 I》的定义,对资本进行分组的不同类型的资本。然而,由于各国法律框架的差异,各国的银行监管机构可能会修订在资本计算中所考虑的金融工具的分类。
一级资本包括核心资本,核心资本由符合定义要求的普通股、留存收益和某些优先股组成。二级资本被视为补充资本,包括补充债务、未披露的准备金、重估准备金、一般贷款损失准备金以及混合资本工具;三级资本则被视为短期额外资本。(银行监管与监督委员会(1987):国际资本衡量与资本标准趋同建议,咨询文件,1987 年 12 月,www.bis.org/publ/bcbs03a.pdf)
《巴塞尔协议 II》
《巴塞尔协议 II》于 1999 年发布,作为继《巴塞尔协议 I》之后提出的新的资本充足框架,并于 2004 年发布,以解决某些问题,这些问题在前期的《巴塞尔协议》框架下有些规定较为宽松。
《巴塞尔协议 II》的主要目标是:
-
提供更具风险敏感性的资本分配
-
实施适当的计算方法,不仅适用于信用风险,还适用于市场风险和操作风险。
-
改进信息披露要求,以使资本充足性对市场参与者更加可感知
-
避免监管套利
《巴塞尔协议 II》的框架基于以下三个支柱:
-
委员会旨在通过最低资本要求,发展并扩展标准化的资本充足性计算
-
对金融机构的资本充足性和内部评估过程进行监管评审
-
有效的信息披露以增强市场纪律
最低资本要求
根据标准化方法,可以计算信用风险所需的资本。基于该方法,信用暴露应根据主要考虑外部信用评估机构(ECAI)的相关评级来加权。对于主权、企业、银行或证券公司的债权,可以根据其评级加权为 0%、20%、50%、100%或 150%;然而,对于国际组织如国际货币基金组织(IMF)、国际结算银行(BIS)或欧洲委员会(EC)的债权,风险权重应始终为 0%。
关于担保债权、现金和其他资产,委员会定义了固定的权重,并由当地监管机构实施,监管机构考虑了风险缓解技术。根据不同的资产类别,合格性可以在不同层次上进行考虑,并在各国的地方法令中进行监管。此外,根据标准方法,房地产不被视为担保,而是视为暴露;因此,它也包含在资产类别的监管规定中。
最低资本要求定义为 RWA 的 8%,并考虑在表外项目中使用的转换因素。通过此方法确定的资本要求应足够覆盖信用风险、市场风险和操作风险。
计算信用风险的其他方法是所谓的内部评级法(IRB)方法,包括基础 IRB 和高级 IRB。IRB 方法仅允许经过当地监管机构批准的银行使用。
IRB 方法应用资本函数来确定所需的资本。影响资本函数的关键参数包括违约概率(PD)、违约损失(LGD)、违约暴露(EAD)和到期时间(M)。
违约概率被视为客户在特定时间范围内无法(完全)履行债务义务的可能性。通过 IRB 方法,银行可以根据自行开发的模型或应用外部信用评估机构(ECAI)的评级来估算客户的 PD。
违约损失是指客户违约时相关资产的百分比。LGD 与 EAD 密切相关。违约暴露是指客户违约事件发生时,尚未偿还的债务金额。应用基础 IRB 时,EAD 的计算方法由当地监管机构确定;而在高级 IRB 下,银行可以自行制定计算方法。
到期时间是一个持续时间类型的参数,表示信用期限的平均剩余部分。
高级 IRB 使得能够对暴露和资产进行另一种分类,这可能更能反映银行投资组合的特征。此外,可能应用的信用风险缓解措施的范围也有所扩大。
尽管可以通过应用基础 IRB 或高级 IRB 的不同方法来确定 RWA,但根据巴塞尔 II,无论哪种情况,最低资本要求都是 RWA 的 8%。
操作风险的确定可以通过不同的方法进行。最简单的计算方法是所谓的基本指标法(BIA)。根据这种方法,资本需求被定义为银行过去 3 年的总收入(GI)的平均值,乘以一个给定的系数 Alpha,该系数由立法规定为 15%。
标准化方法(STA)稍微复杂一些。这种方法采用了 BIA 的一些方法;然而,使用 STA 时,要求根据业务领域(LoB)来确定总收入。每个 LoB 的 GI 应该乘以一个固定系数 Beta(12%、15%或 18%,取决于 LoB)。资本需求是各 LoB 的 GI 与 Beta 的乘积之和。
替代标准化方法(ASTA)的目的是避免由于信用风险而产生的双重征税。ASTA 采用了 STA 的方法;然而,对于两个 LoB(零售和商业银行),其计算方式与标准化方法不同。对于这些 LoB,GI 被贷款和预付款(LA)乘以一个固定系数(m 等于 0.035)所代替。
操作风险的最复杂计算方法是高级测量法(AMA)。这种方法既有定量要求也有定性要求,必须满足这些要求。用于估算操作风险的内部模型必须符合安全操作标准,例如基于 1 年期限的 99.9%的风险测量。此外,采用 AMA 的银行还必须提供过去 5 年相关损失的数据。
风险缓解技术只能由使用高级测量法的银行应用,最多可用于资本需求的 20%。这些银行还必须满足一定的严格要求,才能获得采用风险缓解效应的资格。
关于市场风险资本需求的计算,标准化方法基于监管机构定义的措施和技术。对于更高级的方法,风险价值(VaR)的确定被认为是首选的计算方法。
监管审查
巴塞尔 II 定义了当地监管机构的监督和干预责任。它使得监管机构可以规定比第一支柱中确定的更高的资本要求。此外,它还允许监管和管理第一支柱中未描述的剩余风险,如流动性风险、集中风险、战略风险和系统性风险。
国际资本充足性 评估流程(ICAAP)旨在确保银行运营着一个适当复杂的风险管理系统,能够衡量、量化、总结和监控所有潜在的风险。此外,它应当监督银行是否根据内部方法确定了足够的资本,以覆盖所有这些风险。
监管评估流程(SREP)被定义为本地监管机构对机构的风险和资本充足性进行审查的程序。此外,考虑到第二支柱,监管机构需要定期监控第一支柱的资本充足性,并在必要时介入,以确保资本处于可持续水平。
透明度
巴塞尔 II 的第三支柱关注银行的披露要求。它主要针对上市机构,这些机构需要披露关于第一支柱和第二支柱适用范围、风险评估过程、风险敞口和资本充足性的信息。(巴塞尔银行监管委员会(1999):新的资本充足性框架;咨询文件;1999 年 6 月;www.bis.org/publ/bcbs50.pdf。)
巴塞尔协议 III
即使在金融危机之前,审查和根本加强巴塞尔 II 框架的需求已经显现。在危机期间,显而易见的是,银行的流动性状况不充足,杠杆过高。风险管理应更加重要,而信用和流动性风险通常被定价不当。
巴塞尔协议的第三次修订于 2010 年制定,旨在为金融部门提供更稳定、更安全的运营框架。巴塞尔协议 III 及相关的资本要求指令(CRD IV)预计将在 2019 年前纳入本地立法。
尽管实施将分阶段进行,但金融机构被要求在截止日期之前的若干年开始为应用新资本标准做好准备。
巴塞尔协议 III 涉及的监管领域如下:
-
所需资本的构成——实施资本保护缓冲区和逆周期缓冲区
-
杠杆比率的引入
-
流动性指标的实施
-
对手方风险的衡量
-
信用机构和投资公司的资本要求
-
全球审慎标准的实施
为了提高资本质量,巴塞尔协议 III 规定了所需资本的构成。核心一级资本被定义为一级资本的一部分,并实施了一个称为资本保护缓冲区的措施,固定为 2.5%。此外,还引入了一个可自由调整的逆周期缓冲区,在高信用增长时期,认为应额外增加 2.5%的资本。
《巴塞尔 III》还定义了杠杆比率,作为与所有资产及表外项目的损失吸收资本的最低要求,而不考虑风险权重。
《巴塞尔 III》的最重要规定是引入了两个流动性指标。第一个是短期视角下的流动性覆盖率(LCR),预计在 2015 年实施。LCR 是指流动资产与 30 天期间内累积的净现金流之间的比值。最初,LCR 的最低值应为 60%;然而,计划在 2019 年提升至 100%。LCR 的公式如下:

净稳定资金比率(NSFR)预计在 2018 年实施。该指标的目的是避免金融机构的资产和负债之间的到期差距。目标是为涉及负债稳定性的长期资产提供融资。因此,NSFR 被定义为稳定负债对稳定资产的融资比率。NSFR 的度量应在 2019 年达到至少 100%。

为了避免系统性风险,资本要求也与交易对手风险相关。关于交易对手的资本充足性和流动性状况的预期,依据巴塞尔 III 的规定进行框定。关于资本充足性,主要采用内部计算方法的机构将纳入新规定,因为该规定考虑了潜在风险的更详细审查,并考虑了与系统重要金融机构(SIFI)的风险暴露。根据巴塞尔协议的第三次修订,机构应根据某一指标识别 SIFI,并适用监管机构就此类机构设定的要求(参见巴塞尔委员会历史)。
《巴塞尔 III 的主要措施和逐步实施安排》包括在下表中:

风险度量
财务风险是一个有形且可量化的概念,是你在某项金融投资中可能损失的价值。请注意,这里我们严格区分不确定性和风险,后者是可以通过数学统计方法测量的,并且有不同结果的准确概率。然而,金融风险有各种度量方式。最常见的风险度量是某项金融工具回报的标准差。尽管它非常普遍且易于使用,但也存在一些重大缺点。标准差作为风险度量的一个重要问题是,它将上行潜力与下行风险视为相同。换句话说,它同样惩罚那些可能带来巨大正回报和较小负回报的金融工具,而不是波动性较小的资产。
请考虑以下极端示例。假设我们在股市上有两只股票,并且我们可以精确地测量这两只股票在三种不同宏观经济事件下的收益。明年,对于股票 A,一家成熟企业的每股股票在经济增长时带来 5%的收益,在经济停滞时收益为 0%,而在经济衰退时则损失 5%。股票 B 是一家有前景的初创企业的股票;当经济环境良好时,它的股价飙升(+50%),在经济停滞时带来 30%的收益,甚至在经济萎缩时也有 20%的年收益。股票 A 和 B 的回报的统计标准差分别为 4.1%和 12.5%。因此,如果我们根据标准差来做选择,选择股票 A 比选择股票 B 的风险更大。然而,根据我们的常识,显然在所有不同的宏观经济情境下,股票 B 的表现都优于股票 A,因为它在所有情况下都能带来更好的收益。
我们的简单示例完美地展示了标准差作为风险度量的最大问题。标准差未能满足一个一致性风险度量的最简单条件——单调性。我们称σ风险度量为一致的,如果它是归一化的并且满足以下标准。有关一致性风险度量的更多信息,请参阅 Artzner 和 Delbaen 的研究:
-
单调性:如果投资组合X[1]在所有情境下的值都不低于投资组合X[2],那么X[1]的风险应该低于X[2]。换句话说,如果一个工具在所有情况下支付的金额都比另一个工具多,那么它的风险应该更低。
![风险度量]()
-
次可加性:两个投资组合的风险之和应该小于这两个投资组合单独风险的总和。这个标准代表了多样化原则。
![风险度量]()
-
正同质性:将投资组合的值乘以一个常数,会使风险按相同的程度增加。
![风险度量]()
-
平移不变性:向投资组合中添加一个常数值会使风险按相同的数量减少。请参见以下公式:
![风险度量]()
如果标准差不是一个可靠的风险度量,那么我们可以使用什么?这个问题在 1990 年代初由摩根大通 CEO Dennis Weatherstone 提出。他召集公司各部门准备著名的 4:15 报告,报告中他们汇总了所谓的风险价值,报告通常在市场收盘前 15 分钟进行统计。CEO 想要一个汇总的度量,显示公司在下一交易日可能损失的金额。由于这无法完全确定,尤其是在 1987 年黑色星期一之后,分析师们加入了 95%的概率。
显示在指定时间段内,在指定概率(显著性水平)下一个头寸可能损失的金额的图形被称为风险价值(VaR)。尽管它是相对较新的概念,但在风险部门和金融监管机构中已经得到了广泛使用。计算风险价值的方法有几种,可以分为三种不同的方法。在分析法 VaR 计算下,我们假设我们知道基础资产或收益的概率分布。如果我们不想做出这样的假设,我们可以使用历史 VaR 计算方法,基于过去实际实现的收益或资产价值。在这种情况下,隐含假设是,给定工具的过去发展是未来分布的良好估计。如果我们希望使用更复杂的分布函数,而这种函数通过分析难以解决,则蒙特卡洛模拟可能是计算 VaR 的最佳选择。通过这种方法,我们可以假设工具的分析分布,或者使用过去的值。后一种方法被称为历史模拟。
分析法 VaR
在采用分析法计算风险价值(VaR)时,我们需要假设金融工具的收益遵循某种数学概率分布。最常用的是正态分布,这也是我们通常称之为 delta-normal 方法的原因。数学上,X ~ N (μ,σ),其中μ和σ分别是分布的均值和标准差参数。为了计算风险价值,我们需要找到一个阈值(T),使得大于该阈值的所有数据的概率为α(α是显著性水平,通常为 95%、99%、99.9%等)。使用标准正态分布的累积分布函数 F:

这意味着我们需要应用反向累积分布函数来计算1 - α:

虽然我们不知道正态分布的累积分布函数及其反函数的封闭数学公式,但我们可以通过使用计算机来解决这个问题。
我们使用 R 计算苹果股票的 95%置信区间、1 天 VaR,采用 delta-normal 方法,并基于两年的数据集。苹果股票收益的估计均值和标准差分别为 0.13%和 1.36%。
以下代码计算了苹果股票的 VaR:
Apple <- read.table("Apple.csv", header = T, sep = ";")
r <- log(head(Apple$Price,-1)/tail(Apple$Price,-1))
m <- mean(r)
s <- sd(r)
VaR1 <- -qnorm(0.05, m, s)
print(VaR1)
[1] 0.02110003
该阈值,即如果应用于收益时等于 VaR 的值,可以通过以下公式看出。请注意,我们总是取结果的绝对值,因为 VaR 被解释为一个正数:

VaR(95%,1 天)的值为 2.11%。这意味着苹果股票在一天内下跌超过 2.11%的概率为 5%,而不超过 2.11%的概率为 95%。
下图展示了苹果收益的实际分布,并标出了历史风险价值:

历史 VaR
计算风险价值的最简单方法是使用历史方法。在这种方法下,我们假设金融工具过去的收益分布也代表了未来。因此,我们需要找到一个阈值,超过该阈值的α部分值可以找到。在统计学中,这被称为百分位数。如果我们使用一个 95%的 VaR 水平,那么它意味着数据集的较低第五百分位。下面的代码展示了如何在 R 中计算百分位数:
VaR2 <- -quantile(r, 0.05)print(VaR2)
5%
0.01574694
将这一方法应用于苹果股票,我们得到一个较低的第五百分位为 1.57%。风险价值是该百分位的绝对值。因此,我们可以说,苹果股票在一天内下跌超过 1.57%的概率只有 5%,或者该股票将有 95%的可能性下跌不到 1.57%。
蒙特卡罗模拟
计算风险价值的最复杂方法是蒙特卡罗模拟。然而,只有在其他方法无法使用时,这种方法才值得采用。这些原因可能是问题的复杂性或假设了难以处理的概率分布。尽管如此,这依然是展示 R 强大能力的最佳方法,R 可以支持风险管理。
蒙特卡罗模拟可以在金融和其他科学领域中应用。基本方法是建立一个模型,并假设外生变量的解析分布。下一步是根据假设的分布随机生成模型的输入数据。然后,收集结果并用来得出结论。当模拟输出数据准备好后,我们可以按照与使用历史方法相同的程序进行处理。
使用 10,000 步的蒙特卡罗模拟来计算苹果股票的风险价值可能看起来有些过度,但它是为了展示效果。相关的 R 代码如下:
sim_norm_return <- rnorm(10000, m, s)
VaR3 <- -quantile(sim_norm_return, 0.05)
print(VaR3)
5%
0.02128257
我们通过模拟收益的较低第五百分位得出了 2.06%的风险价值。这与使用 delta-normal 方法估算的 2.11%非常接近,这并非巧合。因为假设收益服从正态分布的基本假设是相同的,因此,微小的差异仅仅是模拟随机性的结果。模拟步骤越多,结果越接近 delta-normal 估算。
蒙特卡洛方法的一个变种是历史模拟,其中假设的分布基于金融工具的过去数据。这里数据的生成不是基于分析的数学函数,而是通过独立同分布的方法从历史值中随机选择。
我们还使用了一个包含 10,000 个元素的模拟来预测苹果公司股票的回报。为了随机选择过去的数据,我们给它们分配了编号。下一步是模拟一个介于 1 和 251 之间的随机整数(即历史数据的数量),然后使用一个函数来查找相关的收益率。R 代码如下:
sim_return <- r[ceiling(runif(10000)*251)]
VaR4 <- -quantile(sim_return, 0.05)
print(VaR4)
5%
0.01578806
VaR 的结果为 1.58%,这与原始历史方法得出的值接近,不足为奇。
如今,风险价值(VaR)是金融领域中衡量风险的常见指标。然而,通常情况下,它仍然不符合一致风险度量的标准,因为它未能满足次可加性。换句话说,在某些情况下,它可能会阻碍多元化。然而,如果我们假设回报服从椭圆分布函数,VaR 将证明是一个一致的风险度量。这本质上意味着,正态分布非常适合 VaR 的估算。唯一的问题是,现实中的股票回报相较于高斯曲线来说,呈现较高的尖峰和厚尾(leptokurtic)特征,这是金融中的一种风格化事实。

换句话说,现实中的股票回报往往表现出比正态分布所解释的更极端的损失和收益。因此,发展中的风险分析假设使用更复杂的分布来应对厚尾的股票回报、异方差性以及其他现实收益中的不完美性。
预期损失(ES)的使用也包含在发展中的风险分析中,这实际上是一个一致的风险度量,无论我们假设什么样的分布。预期损失集中在分布的尾部。它衡量的是超出风险价值的分布的期望值。换句话说,α显著性水平下的预期损失是最坏α百分比案例的期望值。从数学角度看,
。
在这里,VaRγ是回报分布的风险价值。
有时,预期损失被称为条件风险价值(CVaR)。然而,这两个术语并不完全相同;如果使用连续分布函数进行风险分析,它们可以作为同义词。尽管 R 语言能够处理像预期损失这样的复杂问题,但这超出了本书的讨论范围。如需了解更多信息,请参见Acerbi, C.; Tasche, D. (2002)的研究。
风险类别
银行面临各种风险,例如客户违约、市场环境变化、再融资困难和欺诈。这些风险被归类为信用风险、市场风险和操作风险。
市场风险
市场价格变动带来的损失由市场风险覆盖。它可能包括银行或金融机构交易账簿头寸的损失,但与银行核心业务相关的利率或货币损失也属于市场风险。市场风险可以包括几个子类别,如股权风险、利率风险、汇率风险和商品风险。流动性风险也涵盖在这个话题中。根据巴塞尔 II 指令的高级方法,覆盖这些风险所需的资本主要基于风险价值计算。
汇率风险是指外汇汇率变动(例如,欧元/美元)的可能损失,或其衍生产品的可能损失,而商品风险则涵盖了商品价格变动(例如,黄金、原油、小麦、铜等)带来的损失。如果银行的外汇敞口在资金和贷款之间存在不匹配,汇率风险也可能影响银行的核心业务。外汇不匹配可能会给银行带来严重的风险,因此监管机构通常会对所谓的外汇敞口的最大金额施加严格限制。这导致银行负债和资产之间的外汇敞口不匹配。可以通过某些对冲交易(如跨货币掉期、货币期货、远期、外汇期权等)来应对这一问题。
股权风险是指股票、股票指数或以股权为基础的衍生产品可能的损失。我们已经看到了如何使用标准差或风险价值来衡量股权风险的示例。现在,我们将展示如何利用前述技术来衡量股权衍生品组合的风险。首先,我们来看一个单一的看涨期权的风险价值,然后我们分析如何通过这种方法处理看涨期权和看跌期权的组合。
首先,假设黑-斯科尔斯模型的所有条件都来源于市场。如需了解更多关于黑-斯科尔斯模型及其条件的信息,请参考John. C. Hull [9]的书籍。当前一只股票的交易价格为 S = 100 美元,该股票不支付股息,并遵循几何布朗运动,μ等于 20%(漂移率),σ等于 30%(波动率)参数。
一只在两年后到期的此股票的平值(ATM)看涨期权,我们希望确定该期权在一年内的 95%风险价值。我们知道该股票价格遵循对数正态分布,而对数回报率则遵循正态分布,具有以下m和s参数:



现在,让我们计算在假设满足 Black-Scholes 条件下衍生品的当前价格。使用 Black-Scholes 公式,期权的两年期价格为 25.98 美元:

请注意,期权价格是标的物现货价格的单调增长函数。
这个特性在解决这个问题时对我们非常有帮助。我们需要的是一个期权价格阈值,低于此阈值时,只有 5% 的概率会发生。然而,由于它是标的物现货价格 S 的单调增长函数,我们只需要知道股票价格的这个阈值在哪里。给定 m 和 s 参数,我们可以使用以下公式轻松找到这个值:

因此,我们现在知道,在一年内(m 和 s 参数的时间周期为一年),股票价格低于 71.29 美元的概率只有 5%。如果我们将 Black-Scholes 公式应用于这个价格,并将期权的到期时间减去一年,我们可以得到期权价格的阈值。

现在,我们知道在一年内,期权价格超过 2.90 美元的概率为 95%。因此,我们最多失去的价值是实际期权价格与阈值之间的差异。所以,期权的 95% VaR(在一年内)如下所示:


因此,给定股票的期权在一年内有 5% 的概率会损失超过 23.08 美元或 88.82%。
以下是 R 代码中显示的计算。请注意,在运行代码之前,我们需要通过以下命令安装 fOptions 库:
install.packages("fOptions")
library(fOptions)
X <- 100
Time <- 2
r <- 0.1
sigma <- 0.3
mu <- 0.2
S <- seq(1,200, length = 1000)
call_price <- sapply(S, function(S) GBSOption("c", S, X, Time, r, r, sigma)@price)
plot(S, call_price, type = "l", ylab = "", main = "Call option price in function of stock prompt price")
以下截图是前述命令的结果:

如果我们想找出某个包含看涨和看跌期权的投资组合的风险价值,情况就没有那么简单了。让我们以之前的例子为例,假设股票价格为 100 美元。现在,我们在投资组合中除了 ATM 看涨期权外,还添加了一个 ATM 看跌期权,形成一个在金融中称为跨式期权(straddle)的复杂仓位。从我们的角度来看,这个投资组合的问题在于股票价格函数的非单调性。如下一张图所示,当期权即将到期时,这个投资组合的价值与股票价格的关系呈抛物线形状,或者类似于一个 V 字形。
因此,之前通过寻找适当的股票价格阈值来计算期权价格阈值的逻辑在这里不起作用。然而,我们可以使用蒙特卡洛模拟方法来推导出所需的值。
首先,让我们使用所谓的买卖平价公式,利用之前计算的看涨期权价格来得到看跌期权的价值。买卖平价公式计算如下:


这里,c 和 p 分别是看涨和看跌期权的价格,两者的行权价为 X,S 是实际的股票价格 Hull (2002)。因此,整个投资组合的价值为 33.82 美元。
现在,我们使用模拟方法收集 10,000 次可能的投资组合价值实现,这些实现是通过随机生成的输入数据集得出的。我们确保股票遵循几何布朗运动,并且对数收益率遵循正态分布,其 m 和 s 参数分别为 15.5% 和 30%。将生成的对数收益率应用于原始股票价格(100 美元),我们将得到一年的模拟股票价格。这可以用来使用 Black-Scholes 公式重新计算看涨和看跌期权的价值。请注意,在这里,我们将原始股票价格替换为模拟股票价格,同时在计算时使用一年的较短到期时间。最后一步,我们生成 10,000 次模拟投资组合价值(c + p)的实现,然后找到较低的第五百分位。这将是期权投资组合价值只有 5% 的情况会低于此阈值。步骤可以在以下代码中看到:
X <- 100
Time <- 2
r <- 0.1
sigma <- 0.3
mu <- 0.2
S <- seq(1,200, length = 1000)
call_price <- sapply(S, function(S) GBSOption("c", S, X, Time, r, r, sigma)@price)
put_price <- sapply(S, function(S) GBSOption("p", S, X, Time, r, r, sigma)@price)
portfolio_price <- call_price + put_price
windows()
plot(S, portfolio_price, type = "l", ylab = "", main = "Portfolio price in function of stock prompt price")
# portfolio VaR simulation
p0 <- GBSOption("c", 100, X, Time, r, r, sigma)@price + GBSOption("p", 100, X, Time, r, r, sigma)@price
print(paste("price of portfolio:",p0))
[1] "price of portfolio: 33.8240537586255"
S1 <- 100*exp(rnorm(10000, mu - sigma² / 2 , sigma))
P1 <- sapply(S1, function(S) GxBSOption("c", S, X, 1, r, r, sigma)@price + GBSOption("p", S, X, 1, r, r, sigma)@price )
VaR <- quantile(P1, 0.05)
print(paste("95% VaR of portfolio: ", p0 - VaR))
前面的命令会产生以下输出:

期望的阈值为 21.45 美元;因此,投资组合的风险价值为 33.82 - 21.45 = 12.37 美元。因此,投资组合在一年内损失超过 12.37 美元的概率仅为 5%。
利率风险来源于银行的核心业务,即银行的贷款和再融资活动。然而,它还包括由于利率不利变动可能导致的债券或固定收益衍生品的损失。利率风险是银行面临的最重要市场风险,因为银行通常使用短期资金(如客户存款、银行间贷款等)来再融资长期资产(如住房贷款、政府债券等)。
计算一个头寸或整个投资组合的风险价值是衡量银行或金融机构市场风险的有用工具。然而,也有其他几种工具可以用来衡量和应对利率风险。例如,分析资产和负债之间的利率敏感性缺口就是一种工具。这种方法是资产负债管理中最早用来衡量和应对利率风险的技术之一,但它比现代的风险衡量方法要准确性差得多。在利率敏感性缺口分析中,资产和负债的项目根据平均到期日或利率重设时间(如果资产或负债是浮动利率)进行分类。然后,在每个时间段类别中比较资产和负债项目,以详细了解利率敏感性不匹配的情况。
基于 VaR(风险价值)的方法是一种更为成熟且精确的衡量银行或金融机构利率风险的方式。该方法同样基于利率敏感性,通过固定收益投资组合的久期(及凸性)来表示,而不是资产和负债之间的到期不匹配。
信用风险
银行面临的主要风险是借款人可能违约,即未按时支付所需款项。在这种情况下,风险在于贷方可能会失去本金、利息及所有相关支付。损失可以是部分的,也可以是完全的,具体取决于担保物和其他缓解因素。违约可能由多种不同情况引起,如零售借款人未能偿还按揭、信用卡或个人贷款;公司、银行或保险公司破产;未能按时支付到期账单;债务人未支付债务证券等。
信用风险的预期损失可以通过三个不同因素的乘积来表示:PD、LGD 和 EAD:

违约概率(PD)是指发生违约事件的可能性。这是所有信用风险模型的关键因素,有多种方法可以用来估算该值。违约损失(LGD)是指在违约情况下,按债务证券面值计算的损失比例。回收率(RR)是 LGD 的逆,表示即使借款人违约,仍然可以回收的金额。回收率受到担保物和其他缓解因素的影响。违约暴露(EAD)是指在违约情况下,暴露于特定信用风险的债务证券面值。
银行和金融机构使用不同的方法来衡量和处理信用风险。为了降低信用风险,所有三个因素(违约概率、违约损失和风险暴露)都可能成为关注的重点。为了控制风险暴露,银行可能会对特定客户群体(消费者、公司和主权国家)进行贷款限额和限制。违约损失可以通过使用担保物(如房地产抵押权、证券和担保)来降低。担保物为贷方提供安全保障,确保他们至少能收回部分款项。还可以通过其他工具来减少违约损失,如信用衍生品和信用保险。
信用违约掉期(CDS)是一种金融掉期协议,作为对第三方违约的保险。CDS 的发行人或卖方同意在债务持有人违约时向买方赔偿。买方支付卖方定期费用,费用为债券或其他债务证券面值的百分比。在发生信用事件时,卖方向买方支付面值并接收债券。如果债务人未违约,则 CDS 交易在到期时终止,卖方无需支付任何款项。

默认风险可以通过对业务合作伙伴和借款人的尽职调查、使用契约和严格的政策来降低。银行使用多种尽职调查方法,从标准化的评分过程到对客户的更复杂的深入研究。通过应用这些方法,银行可以筛选出那些违约概率过高的客户,从而避免其影响资本状况。信用风险也可以通过基于风险的定价来降低。较高的违约概率意味着信用风险的预期损失较高,这部分损失需要通过对特定客户应用的利差来覆盖。银行需要在正常的业务过程中解决这个问题,只需为意外损失准备资本。因此,信用风险的预期损失应成为产品定价的基本组成部分。
估算违约概率是所有银行和金融机构非常重要的问题。我们有几种方法,其中我们将探讨三种不同的方法:
-
隐含概率是通过市场上风险债券或信用违约掉期(例如,Hull-White 方法)的市场定价得出的。
-
结构性模型(例如,KMV 模型)
-
当前和历史信用评级的变动(例如,CreditMetrics)
第一种方法假设市场上有与具有信用风险的工具相关的交易产品作为基础资产。还假设这种风险已完全体现在这些工具的市场定价中。例如,如果一个风险较高的公司债券在市场上交易,那么该债券的价格会低于无风险证券的价格。如果市场上有针对某一债券的信用违约掉期(CDS)交易,那么它也反映了市场对该证券风险的评估。如果市场上有足够的流动性,那么预期的信用风险损失应该等于风险的观察价格。如果我们知道这个价格,我们就可以确定隐含的违约概率。
让我们看一个简短的例子。假设一家 BBB 评级公司发行的面值 1,000 美元的 1 年期零息债券的到期收益率(YTM)为 5%。一只类似特征但无信用风险的 AAA 评级政府短期国债的到期收益率为 3%。我们知道,如果企业债券违约,将会回收 30%的面值。如果市场价格准确,那么该债券的违约概率是多少?
首先,我们需要计算企业债券和政府债券的当前市场价格。企业债券应该以
的价格交易。同样,政府债券应该以
的价格交易。
两只债券之间的价格差异为 18.5 美元。预计的信用损失为违约概率(PD)∙损失给付率(LGD)∙暴露于违约的敞口(EAD)在一年内的预期值。如果我们想通过保险或 CDS 对信用损失进行对冲,那么这个金额的现值将是我们愿意支付的最大金额。因此,两个债券之间的价格差异应等于预计信用损失的现值。LGD 为 70%,因为在违约情况下,面值的 30%会被回收。
因此,
或
。
所以,如果市场定价合理,那么在接下来的一年中,隐含的违约概率为 2.72%。如果市场上有与特定债券相关的信用衍生品,这种方法也可以使用。
结构性方法通过基于暴露于信用风险的金融工具的特征创建数学模型。一个常见的例子是由三位数学家斯蒂芬·凯尔霍费尔、约翰·麦克昆和奥尔德里奇·瓦希切克共同创立的公司开发的 KMV 模型。该公司在 2002 年被穆迪评级公司收购后,目前以穆迪分析的名义运营。
KMV 模型基于梅尔顿的信用模型(1974),该模型将具有信用风险的公司债务和股权证券视为类似于期权的衍生品。基本思想是,如果公司具备偿付能力,那么其资产的市场价值(或企业价值)应当超过其持有的债务的面值。因此,在公司债券到期之前,它们会评估债务的面值和股权的价值(上市公司的市值)。然而,如果资产价值在到期时未能达到债务的面值,股东可能会决定筹集资本或破产。如果发生后者,企业债券的市场价值将等于资产价值,并且股东在清算过程中将一无所获。
破产和资本筹集之间的选择称为破产期权,它具有看跌期权的特点。之所以存在这种期权,是因为股东对公司没有超过他们所投资金额的责任(股价不能跌至负值)。更具体地说,公司的债券价值是没有信用风险的债券和破产期权的组合,从债权人的角度来看,这是一个空头看跌期权(长期债券 + 空头看跌期权)。
公司的股权可以视为一个看涨期权(长期看涨期权)。公司的资产价值是所有方程式的总和,如下公式所示:
,其中 D 是公司债务的面值,V 是资产价值,c 是股权的市场价值(在此指代看涨期权),p 是破产期权的价值。

KMV 模型
实际操作中,资产价值和股权的波动率都是计算有风险公司债券实际价值所必需的。上市公司的股权波动率可以通过股票价格波动轻松估算,但资产波动率则无法获取,因为实际经济商品通常不在公开市场上交易。资产的市场价值由于同样的原因也很难估算。因此,KMV 方法有两个方程和两个未知数。这两个方程是基于 Black-Scholes 理论的条件
,该理论基于 Black-Scholes 方程,和
,该方程基于伊藤引理,其中E和V分别是股权和资产的市场价值,D是债券的面值,σ[E]和σ[V]是股权和资产的波动率。
是E对V的导数,它等于N(d[1]),这是基于 Black-Scholes 理论的结果。两个未知数是V和σ[V]。
现在,让我们来看一个例子,其中一家公司股权的市场价值(市值)为 30 亿美元,波动率为 80%。该公司拥有一系列面值为 100 亿美元的零息债券,且这些债券将在一年后到期。无风险对数收益率为 5%。
上述方程的解可以通过 R 语言求得,如下所示:
install.packages("fOptions")
library(fOptions)
kmv_error <- function(V_and_vol_V, E=3,Time=1,D=10,vol_E=0.8,r=0.05){
V <- V_and_vol_V[1]
vol_V <- V_and_vol_V[2]
E_ <- GBSOption("c", V, D, Time, r, r, vol_V)@price
tmp <- vol_V*sqrt(Time)
d1 <- log(V/(D*exp(-r*Time)))/tmp + tmp/2
Nd1 <- pnorm(d1)
vol_E_ <- Nd1*V/E*vol_V
err <- c(E_ - E, vol_E_ - vol_E)
err[1]²+err[2]²
}
a <- optim(c(1,1), fn = kmv_error)
print(a)
该公司债券的总价值为 94 亿美元,收益率到期的对数为 6.44%,而资产的价值为 124 亿美元,波动率为 21.2%。
第三种估算违约概率的方法是基于评级的方法。该方法从不同金融工具或经济实体(公司、主权国家和机构)的信用评级出发进行估算。CreditMetrics 分析最初由摩根大通的风险管理部门于 1997 年开发。从那时起,它不断发展,现在已经成为其他风险管理工具中广泛使用的工具。CreditMetrics 的基本思路是估算一个实体的信用评级如何随时间变化,以及这种变化如何影响该实体所发行的证券的价值。该方法从分析评级历史开始,然后创建一个所谓的转移矩阵,其中包含信用评级可能发展的概率。有关 CreditMetrics 的更多信息,请参阅 MSCI 发布的技术书籍(1987 年银行监管与监督实践委员会)。
操作风险
第三类主要风险是操作风险。这指的是银行、金融机构或其他公司在运营过程中可能遭遇的所有损失。它包括自然灾害、内部或外部欺诈(例如银行抢劫)、系统故障或故障、以及工作流程不当等导致的损失。以下是这些风险可以被划分为的四个不同类别:
-
低影响且低概率:如果风险及其对操作的潜在影响都很低,那么就不值得花力气去处理它。
-
低影响且高概率:如果风险事件发生得过于频繁,意味着公司的一些流程应当进行重组,或者应将其纳入某项操作的定价中。
-
高影响且低概率:如果高影响事件的发生概率较低,最适合的风险缓解方法是为这些事件投保。
-
高影响且高概率:如果这种风险的影响和概率都很高,那么最好关闭该操作。在这种情况下,无论是重组还是保险都不起作用。
这部分的风险管理更属于精算学范畴,而非金融分析。然而,R 提供的工具也能处理类似的问题。我们以 IT 系统故障可能带来的操作损失为例。故障次数遵循参数为λ = 20 的泊松分布,而每次损失的大小遵循对数正态分布,其中m为 5,s为 2。根据泊松分布,年均故障次数为 20,而损失大小的期望值为:
。
然而,我们需要确定联合分布、期望值以及 99.9%聚合年损失的分位数。后者将用于确定《巴塞尔协议Ⅱ》高级计量法(AMA)所要求的必要资本。我们使用了 10,000 元素的蒙特卡罗模拟。第一步是生成一个遵循泊松分布的离散随机变量。接着,我们根据之前生成的整数个数,生成符合对数正态分布的独立变量,并将其聚合。通过重复这个过程 10,000 次,我们可以创建聚合损失的分布。聚合损失的期望值为 21,694 美元,99.9%的分位数为 382,247 美元。
因此,在 1 年内,我们只会在 IT 系统故障的情况下,在 0.1%的概率下损失超过 382,000 美元。计算过程可以在 R 中看到:
op <- function(){
n <- rpois(1, 20)
z <- rlnorm(n,5,2)
sum(z)
}
Loss <- replicate(10000, op())
hist(Loss[Loss<50000], main = "", breaks = 20, xlab = "", ylab = "")
print(paste("Expected loss = ", mean(Loss)))
print(paste("99.9% quantile of loss = ", quantile(Loss, 0.999)))
以下是前述命令的截图:

我们可以从前述图表中看到聚合损失的分布,这与对数正态分布相似,但不一定是对数正态分布。
总结
在这一章节中,我们学习了巴塞尔协议的基本原则,银行监管中的资本充足性要求,风险度量和不同的风险类型,最重要的是,R 语言在风险管理中的强大工具。
我们了解到,巴塞尔协议是全球统一的银行监管框架,并且我们学习了金融监管的持续发展和更为复杂的方法。此外,我们提供了关于风险度量的见解,从最简单的回报标准差到更复杂的度量,最重要的是,风险价值(VaR)。然而,我们看到 VaR 不一定是一个一致的风险度量,但它仍然是监管和风险管理中最广泛使用的指标之一。
我们讨论了银行或金融机构面临的主要风险类型,即信用风险、市场风险和操作风险。你可以看到不同的风险管理方法如何用来计算各种风险类型的潜在损失以及相关的资本充足性。最后,我们展示了几个例子,说明如何使用 R 语言轻松解决风险管理中的复杂问题。
参考文献
[1] 巴塞尔委员会历史
[2] 巴塞尔银行监管委员会(章程)
[3] 银行监管与监督实践委员会(1987):资本测量和资本标准国际趋同建议;咨询文件;1987 年 12 月
[4] 巴塞尔银行监管委员会(1999):新的资本充足性框架;咨询文件;1999 年 6 月
[5] Artzner, P.; Delbaen, F.; Eber, J. M.; Heath, D. (1999). 一致的风险度量。数学金融学,9(第 3 版):p. 203
[6] Wilmott, P. (2006). 定量金融 1(第 2 版):p. 342
[7] Acerbi, C.; Tasche, D. (2002). 预期损失:一种自然一致的风险价值替代方法。经济学笔记 31: p. 379–388
[8] 巴塞尔 II 综合版本
[9] Hull, J. C. (2002). 期权、期货与其他衍生品(第 5 版)
[10] 信用风险管理原则 - 最终文件。巴塞尔银行监管委员会。国际清算银行(BIS)。(2000)
[11] Crosbie, P., Bohn, J. (2003): 违约风险建模。技术报告,穆迪 KMV
[12] Crouhy, M., Galai, D., Mark, R. (2000): 当前信用风险模型的比较分析。银行与金融学报, 24:59–117
[13] MSCI CreditMetrics 技术手册
第十三章:系统性风险
当前危机的主要教训之一是,一些机构因其规模或特殊角色而承担着对金融系统的突出风险。在危机期间,这些机构通常会获得国家援助,以防止整个系统崩溃,这也意味着国家和实际经济将承担更高的成本。最好的例子之一就是 AIG。由于其在 CDS 市场上的活动,联邦储备帮助这家保险公司避免了违约,因为没人知道该机构崩溃可能带来的后果。
这些教训促使中央银行和其他监管机构更加重视对系统重要金融机构(SIFI)的审查与监管。为此,SIFI 的精确识别在金融文献中变得愈发重要。扩展原有的简单技术,中央银行和监管机构倾向于使用基于网络理论方法的更复杂的技术,利用金融市场的交易数据。这些信息对投资者也非常重要,因为它有助于多元化他们对金融部门的风险敞口。
本章旨在介绍基于网络理论的两种技术,这些技术可用于识别系统重要金融机构(SIFI),超越常用的中心性度量。
系统性风险简述
全球金融危机凸显了某些金融机构的规模与实际经济相比过大,或者它们与重要对手方的联系过多。因此,任何影响这些机构的问题都可能对整个金融系统和实际经济产生致命影响。正因如此,政府在拯救这些机构时不遗余力。全球范围内有多个例子表明,政府或中央银行为其最重要的金融机构提供担保、注入资本、提供资金贷款或支持收购(例如,北岩银行、AIG 或贝尔斯登)。
如果没有这些步骤,金融体系崩溃的可能性似乎过高,这将伴随极高的成本,因为需要进行救助。总的来说,系统重要金融机构的识别再次成为一个热门话题。危机的主要教训之一是,即使在正常时期,规模最大、联系最广泛的机构也必须以不同的方式进行处理。根据新的巴塞尔框架,系统重要机构必须比其不太重要的合作伙伴受到更严格的监管。由于这些机构在金融体系中的核心作用和它们的相互联系,这些机构的失败可能会引发金融系统的震荡波,进而对实际经济造成损害。个别机构在追求最大化利润的过程中做出的理性选择,在全系统层面可能是次优的,因为它们没有考虑到在压力时期可能带来的负面影响。
危机之前,个别金融机构的系统性角色主要在决定最后贷款人支持时进行评估。中央银行在决定是否在严重问题时向银行提供贷款时,会考虑银行的系统性角色。关于不同国家使用的分析技术的调查发现,在许多情况下,监管机构在评估系统性重要性时采用了类似的方法。实践中存在多种不同的方法,从传统技术(例如,关注市场份额的基于指标的方法)和复杂的定量模型,到包括市场情报的定性标准(FSB(2009))。基于指标的方法可能包括几种不同类型的比率(BIS(2011))。通常,金融市场、金融基础设施和金融中介在检查中是重点,但实际的指标集可以根据所调查的银行系统的特殊特征而在不同国家之间有所不同。
基于指标的方法主要关注每家银行在银行业务不同领域的市场份额(从资产到负债,从场外衍生品的名义价值到支付清算,它可能涵盖多个领域,BIS(2011))。这些基于指标的方法有时不包含关于机构在金融市场上相互联系的信息。Daróczi 等人(2013)提供了一些关于如何将这些信息纳入系统性重要银行识别中的建议。应用于每家银行的简单网络度量可以扩展传统的基于指标的方法。在金融文献中,许多不同的度量方法被用来评估网络的稳定性或评估单个机构的角色。Iazetta 和 Manna(2009)使用了所谓的地理距离频率(也称为“中介性”)和度数来评估网络的韧性。
他们发现,这些比率的使用有助于识别系统中的大玩家。Berlinger 等人(2011)也使用了网络度量来检查单个机构在系统中的角色。
在本章中,我们不会包括这些方法,因为Daróczi 等人(2013)展示了理论及其在 R 中的应用。我们的重点将放在网络理论的两种不同方法上,这些方法在识别系统性重要性方面具有相关性,并且可以轻松应用。首先,我们将展示金融市场的核心-边缘分解。其次,我们将展示一种模拟方法,帮助我们在任何单个机构违约的情况下看到传染效应。
我们示例中使用的数据集
在本章中,我们将使用一个虚构的银行系统及其银行间存款市场。我们使用这个市场,因为它通常具有最大的潜在损失,因为这些交易没有抵押品。
为了进行这项分析,我们需要一个连通的网络,因此我们构建了一个。这个网络应该包含银行间相互之间的曝险信息。通常,我们有交易数据,比如在表格 13.1中所示。由于银行间市场的交易平均期限非常短,因此也可以使用这些数据。例如,我们可以通过使用每对银行之间的平均月交易量来构建这个网络。对于这种类型的分析,只有每笔交易的合作伙伴和合同规模是重要的。

表格 13.1:交易数据集
通过所有这些信息,我们可以构建一个金融市场的矩阵(可以将其可视化为一个网络)。

使用的矩阵
第一步将是对矩阵进行核心-外围分解。在这种情况下,我们只需要所谓的邻接矩阵A,其中
。
模拟方法会复杂一些,因为我们需要更多的银行和交易信息。我们将不再使用邻接矩阵,而是需要一个加权矩阵W,其中权重是交易规模:

图 13.2 显示了所考察市场在样本期间的加权网络:

图 13.2:银行间存款市场的网络
我们还需要一些关于银行的特定信息。向量C将包含关于银行资本位置的信息。C[i]表示银行i在给定货币下的资本缓冲超出监管最低要求的部分。当然,是否考虑资本缓冲或全部监管资本是一个决策问题。在我们看来,最好使用资本缓冲,因为如果银行失去了整个缓冲,监管机构将会采取措施。向量S将包含每家银行的规模。S[i]将是银行i的资产负债表总额。

图 13.3:资本位置和规模的向量
核心-外围分解
银行间市场是分层的,并以分级的方式运作。这些市场的一个众所周知的特点是,许多银行仅与少数大型机构进行交易,而这些大型机构则充当中介或资金中心银行。这些大型机构被认为是网络的核心,而其他的则是外围。
许多论文关注现实世界网络的这一特征。例如,Borgatti 和 Everett(1999)在一个由引文数据构成的网络上研究了这一现象,发现三本期刊是核心成员。Craig 和 von Peter(2010)将这一核心/外围结构应用于德国的银行间市场。他们的研究发现,银行的特定特征有助于解释银行如何在银行间市场中定位自己。网络中的大小和位置之间存在强烈的相关性。由于分层不是随机的而是行为性的,银行系统围绕一个货币中心银行的核心组织起来具有经济原因(例如,固定成本)。这一发现还意味着核心性可以是衡量系统性重要性的一个良好指标。
网络的完美核心-外围结构可以通过图 13.3中的矩阵轻松展示。核心银行位于矩阵的左上角。所有这些银行相互连接,可以被视为中介。它们负责市场的稳定,其他银行则通过这些核心机构相互连接。在右下角是外围银行。它们与其他外围机构没有任何连接,只与核心银行连接,如以下截图所示:

图 13.4:核心外围结构中的邻接矩阵
Craig 和 von Peter(2010)还提出,不仅矩阵的核心-核心部分或外围-外围部分很重要,核心-外围部分同样重要(右上角和左下角)。他们强调,所有核心银行都应该至少与一个外围机构有连接。这一特征意味着该外围银行除了通过核心银行外,没有其他可能性参与该市场。尽管这是一个重要问题,我们认为,由于可能的传染效应,成为核心银行本身就可能导致系统性重要性。
在许多情况下,现实世界网络中无法获得纯粹的核心/外围分解。尤其是在我们对矩阵的核心-外围部分有特定要求时,这一点尤为真实。因此,在第一步中,我们将尝试解决最大团问题(例如,通过使用 Bron-Kerbosch 算法,Bron 和 Kerbosch 1973),然后在第二步中,我们将选择外围-外围部分中平均度最低的结果。还有许多其他不同的方法可以进行核心-外围分解。由于其简单性,我们选择了这一方法。
R 中的实现
在本小节中,我们展示了如何编程核心-外围分解。我们将涵盖所有相关信息,从下载必要的 R 包到加载数据集,从分解本身到结果的可视化。我们将分小部分展示代码,并对每部分进行详细解释。
我们设置将在模拟过程中使用的库。代码将在此库中查找输入数据文件。我们下载了一个 R 包 igraph,它是可视化金融网络中的一个重要工具。当然,在第一次运行此代码后,这一行可能会被删除,因为安装过程不应再次重复。最后,在安装之后,该包应该首先加载到当前的 R 会话中。
install.packages("igraph")
library(igraph)
第二步,我们加载数据集,在这种情况下数据集仅为矩阵。导入的数据是一个数据框,必须转换为矩阵形式。如前所示(图 13.1),当两个银行之间没有交易时,矩阵不包含数据。第三行将这些单元格填充为 0。然后,由于我们只需要邻接矩阵,我们将所有非零单元格的值更改为 1。最后,我们从邻接矩阵创建一个图作为对象。
adj_mtx <- read.table("mtx.csv", header = T, sep = ";")
adj_mtx <- as.matrix(adj_mtx)
adj_mtx[is.na(adj_mtx)] <- 0
adj_mtx[adj_mtx != 0] <- 1
G <- graph.adjacency(adj_mtx, mode = "undirected")
igraph 包有一个名为 largest.clique 的函数,它返回最大团问题的解的列表。CORE 将包含所有最大团的集合。命令如下:
CORE <- largest.cliques(G)
最大团将成为图的核心,其补集将是外围。我们为每个结果的最大团创建外围。然后,我们为核心节点和外围节点设置不同的颜色,这有助于在图表中区分它们。
for (i in 1:length(CORE)){
core <- CORE[[i]]
periphery <- setdiff(1:33, core)
V(G)$color[periphery] <- rgb(0,1,0)
V(G)$color[core] <- rgb(1,0,0)
print(i)
print(core)
print(periphery)
然后,我们计算外围-外围矩阵的平均度数。对于系统重要性金融机构的识别,当该平均度数最低时是最好的解决方案。
H <- induced.subgraph(G, periphery)
d <- mean(degree(H))
最后,我们在一个新窗口中绘制图表。图表还将包含外围矩阵的平均度数。
windows()
plot(G, vertex.color = V(G)$color, main = paste("Avg periphery degree:", round(d,2) ) )}
结果
通过运行代码,我们获得了核心-外围分解的所有解决方案的图表。在每种情况下,图表上都会显示外围的平均度数。我们选择了外围度数最小的解决方案。这意味着在该解决方案中,外围银行之间的连接非常有限。核心中的问题可能导致它们无法访问市场。另一方面,由于核心是完全连接的,传染过程可能会很快展开,并且能影响到每个银行。总之,任何核心银行的违约都会危及外围银行的市场准入,并可能成为传染过程的源头。图 13.5 展示了通过这种简单方法获得的核心-外围分解的最佳解决方案。
根据结果,12 家银行可以被视为系统重要性金融机构,分别是 5、7、8、11、13、20、21、22、23、24、28 和 30。

图 13.5:核心-外围分解法,最小外围度
模拟方法
从系统性角度理解银行的角色的最佳方式是模拟其违约的影响。通过这种方式,我们可以得到银行系统重要性的最精确结果。通常,这些方法的主要问题在于数据需求。个别机构的主要特征(例如资本缓冲或规模)不足以进行此类分析。我们还必须准确了解其通过金融市场对其他银行的敞口,因为最重要的传播渠道是金融市场。
在本节中,我们将展示一种简单的方法来识别金融机构的系统重要性。为了尽可能简化,我们需要做一些假设:
-
我们将研究特定违约的影响。违约后,所有传播效应会突然通过网络传播。
-
由于所有影响都突然发生,银行不会有任何调整程序。
-
所有银行的 LGD(损失给付率)是固定的。有一些模型考虑到 LGD 可能因银行而异(例如,Eisenberg 和 Noe, 2001),但这会使我们的模型过于复杂。
-
我们不考虑违约后法律程序的时长。在实际中,这应当在 LGD 中加以考虑。
正如我们在数据部分所提到的,我们需要三种数据集。首先,我们需要包含银行之间在同业存款市场上敞口的矩阵。由于这些交易没有抵押,潜在损失在这个市场上是最大的。其次,我们需要每家银行的资本缓冲大小。较高的资本缓冲可以显著减少传播效应的可能性。因此,检查什么可以视为资本缓冲总是很重要。我们的观点是,在此类分析中,只有超过监管最低要求的资本应当被纳入考虑,以尽可能谨慎。第三,我们需要每家银行的规模。为了评估某家银行违约的影响,我们需要被感染银行的规模。在我们的示例中,我们使用资产负债表总额,但也可以使用其他衡量标准。所选择的衡量标准必须能够反映对实体经济的影响(例如,可以是企业贷款组合的规模或存款存量等)。
模拟
作为第一步,我们随机选择一个银行(任何银行,因为我们会对每个银行进行此操作),假设它在一次特殊冲击后违约。矩阵包含了所有向该银行借款的银行的信息。W[ij] 是银行 j 从银行 i 借款的金额。L 是 LGD,即与敞口相关的损失比例。当以下不等式成立时,即银行 i 从银行 j 违约中遭受的损失超过了银行 i 的资本缓冲区时,银行 i 必须被视为违约。

结果是,我们得到了所有在银行 j 倒闭后违约的伙伴银行。我们对所有新违约银行的伙伴银行进行第一步操作。我们继续进行模拟,直到达到一个平衡状态,即没有新的违约发生。
我们为每个银行做这个模拟,即我们试图找出在其因传染效应倒闭后,哪些银行会违约。最后,我们汇总每种情况下违约银行的资产负债表总额。我们的最终结果将是一个列表,包含了每家银行违约的潜在影响,基于受影响银行的市场份额。
R 语言中的实现
在本节中,我们将展示如何在 R 中实现这种模拟技术。我们将像之前一样展示完整的代码。代码中的一些部分也用于核心-边缘区分,因此我们不会对这些部分进行详细解释。
在前几行中,我们设置了一些基本信息。有两行需要解释。首先,我们设置了 LGD 的值。正如我们稍后看到的,使用不同的 LGD 进行检查是非常重要的,因为我们的模拟对 LGD 的水平敏感。该值可以是从 0 到 1 之间的任何数。其次,那些绘制网络的算法使用了一个随机数生成器。Set.seed 命令设置了随机数生成器的初始值,以确保我们获得具有相同外观的图表。
LGD = 0.65
set.seed(3052343)
library(igraph)
在代码的下一部分,我们加载将在模型中使用的数据,即网络的矩阵(mtx.csv)、资本缓冲区的向量(puf.csv)以及银行规模的向量(sizes.csv)。
adj_mtx <- read.table("mtx.csv", header = T, sep = ";")
node_w <- read.table("puf.csv", header = T, sep = ";")
node_s <- read.table("sizes.csv", header = T, sep = ";")
adj_mtx <- as.matrix(adj_mtx)
adj_mtx[is.na(adj_mtx)] <- 0
在模拟过程中,邻接矩阵是不够的,这与核心-边缘区分相反。我们需要加权矩阵 G。
G <- graph.adjacency((adj_mtx ), weighted = TRUE)
下一步是技术性的,而非本质性的,但它有助于避免后续的错误。V 是图中节点的集合。我们将每个节点的相关信息汇总在一起,即它在哪个步骤发生了违约(未违约的银行得 0),资本缓冲区和规模。
V(G)$default <- 0
V(G)$capital <- as.numeric(as.character(node_w[,2]))
V(G)$size <- as.numeric(as.character(node_s[,2]))
然后,我们可以轻松绘制网络。我们使用了这个命令来创建图 13.2。当然,这对模拟不是必须的。
plot(G, layout = layout.kamada.kawai(G), edge.arrow.size=0.3, vertex.size = 10, vertex.label.cex = .75)
正如我们所提到的,我们的目标是得到一个银行列表以及它们崩溃对银行系统的影响。然而,观察每个案例中传染过程的演变也是很有价值的。为此,我们使用一个可以生成相关图表的函数。sim函数有四个属性:G是加权图,第一个违约的银行为起始节点,LGD,以及一个控制是否绘制图表的变量。最后两个属性有默认值,但我们当然可以在每次运行时为它们设置不同的值。我们还根据每个节点的违约步骤设置不同的颜色。
sim <- function(G, starting_node, l = 0.85, drawimage = TRUE){
node_color <- function(n,m) c(rgb(0,0.7,0),rainbow(m))[n+1]
我们创建了一个变量,用于判断传染是否已经停止。我们还创建了一个包含违约银行的列表。列表中的第j个组件包含在第j步中崩溃的所有银行。
stop_ <- FALSE
j <- 1
default <- list(starting_node)
下一部分是整个代码的核心。我们启动一个while循环,检查传染是否继续传播。最开始,它肯定会继续传播。我们将那些在第j步崩溃的银行的违约属性设置为j。
然后,在一个for循环中,我们取出所有与银行i有连接的银行,并从它们的资本中扣除exposureLGD*。在此之后发生违约的银行将进入违约列表。然后,我们重新开始计算对新违约银行的风险暴露,并继续进行,直到没有新的违约发生。
while(!stop_){
V(G)$default[default[[j]]] <- j
j <- j + 1; stop_ <- TRUE
for( i in default[[j-1]]){V(G)$capital <- V(G)$capital - l*G[,i]}
default[[j]] = setdiff((1:33)[V(G)$capital < 0], unlist(default));
if( length( default[[j]] ) > 0) stop_ <- FALSE
}
当drawimage在sim函数中等于 T 时,代码将绘制网络图。每个节点的颜色取决于其违约的时间,如前所述。后违约的银行颜色较浅,尚未违约的银行则显示绿色。
if(drawimage) plot(G, layout = layout.kamada.kawai(G), edge.arrow.size=0.3, vertex.size = 12.5, vertex.color = node_color(V(G)$default, 4*length(default)), vertex.label.cex = .75)
然后,我们计算违约银行在违约列表中所占的比例。
sum(V(G)$size[unlist(default)])/sum(V(G)$size)}
使用 sapply函数,我们可以对向量的每个组件运行相同的函数,并将结果收集到一个列表中。
result <- sapply(1:33, function(j) sim(G,j,LGD, FALSE))
最后,我们绘制了一个包含系统中每个银行结果的条形图。这个图表使得我们能够判断银行的系统性重要性。
dev.new(width=15,height=10)
v <- barplot(result, names.arg = V(G)$name, cex.names = 0.5, ylim = c(0,1.1))
text(v, result, labels = paste(100*round(result, 2), "%", sep = ""), pos = 3, cex = 0.65)
结果
在这个练习中,我们的主要问题是:哪些银行是系统性重要的金融机构。运行我们在上一小节中展示的代码后,我们得到了问题的确切答案。运行后弹出的图表总结了模拟的主要结果。横轴显示的是银行的代码,纵轴则显示受特定冲击影响的银行系统比例。例如,在图 13.6 中,X3 的 76%意味着如果银行 3 因特定冲击而违约,整个银行系统中 76%的银行将因传染效应而违约。设定一个阈值,超过该阈值的银行必须被视为系统性重要银行,这就是决策的关键。在这个例子中,很容易区分出必须被视为 SIFI(系统重要性金融机构)的机构与那些对系统影响较小的机构。根据图 13.6,10 家银行(代码为 3、7、12、13、15、18、19、21、24 和 28)可以被视为系统性重要银行。

图 13.6:根据资产负债表总额,受特定冲击影响的银行系统比例 LGD = 0.65
需要强调的是,结果依赖于 LGD 参数,该参数必须在代码中设定。在第一次运行中,LGD 设定为 65%,但在不同的情况下,结果可能会有显著差异。例如,如果 LGD 为 90%,结果会更糟。五家银行(它们的代码是 2、8、11、16 和 20)也会在特定冲击的情况下对银行系统产生显著负面影响。然而,如果 LGD 值较低,结果也会更温和。例如,如果 LGD 设定为 30%,银行 13 将对银行系统产生最大影响。然而,与前面的例子相比,这个影响将非常有限。在这种情况下,36%的银行系统将违约。使用 30% LGD 值时,只有 4 家银行对系统的影响超过 10%(图 13.7)。

图 13.7:根据资产负债表总额,受特定冲击影响的银行系统比例 LGD = 0.3
这段 R 代码还能够展示我们传染过程。通过运行sim函数,我们可以找出哪些银行会直接受到被考察银行违约的影响,哪些银行会在模拟的第二步、第三步或后续步骤中受到影响。例如,如果我们想知道银行 15 号违约时会发生什么,我们可以在 R 控制台输入以下命令:sim(G, 13, 0.65),其中 G 是矩阵,13 是银行 15 号的序号,65% 是损失给付率(LGD)。最终,我们得到图 13.8。我们用红色标记发起传染的银行,橙色表示那些直接受到银行 15 号特有冲击影响的机构。然后,颜色越浅,银行受到的影响越晚。最后,绿色节点的银行是幸存者。本例中设置了 65% 的损失给付率。可以看到,银行 15 号的崩溃直接导致了另外五家银行(编号 8、18、20、21 和 36)的违约。随后,随着这些银行的违约,更多银行将失去资本。最终,超过 80% 的银行系统将进入违约状态。

图 13.8:银行 15 号违约后的传染过程
必须强调的是,使用这种模拟方法时,不仅考虑了同业间的暴露,还考虑了主要伙伴的规模以及它们的资本缓冲。在这种情况下,系统性重要性可能源于资本不足的伙伴。或者,恰恰相反,拥有许多伙伴和借入资金的银行可能不会对市场产生任何负面影响,因为其直接伙伴拥有足够高的资本缓冲。银行 20 号就是一个很好的例子。在核心-外围分解中,它绝对属于核心。然而,当我们以 65% 的损失给付率(LGD)运行sim函数时,结果会大不相同。图 13.9显示,在其特有冲击后,其他任何银行都不会违约。

图 13.9:银行 20 号违约后的传染过程
可能的解释和建议
系统性重要性考察的主要难点总是其庞大的数据需求。从这个角度看,核心-外围分解是一种更简便的方法,因为我们只需要银行在同业市场上的暴露情况。尽管在许多情况下这也可能带来一些困难,因为银行之间的直接关联通常是未知的。然而,在文献中,我们可以找到一些很好的解决方案来填补这些空白,例如Anand et al. (2014)提出的最小密度方法。或者,也有一些其他建议,说明如何从市场数据中创建网络(例如,Billio et al., 2013)。
由于两种方法之间的差异,结果可能会让人困惑。我们将给出一些关于如何解读这些结果的建议。核心-外围分解法只关注一个市场。这意味着处于核心的位置意味着该银行在此市场中非常重要。整个银行体系的重要性则取决于该市场的重要性。如果没有这些信息,我们可能只会说核心银行对市场的运作非常重要。
相反,模拟方法严格关注银行系统的稳定性。因此,我们得到的结果是那些可能引发严重危机的银行。然而,这并不意味着其他银行对银行间市场的运作没有重要影响。一个资本充足的银行,虽然可能会冻结市场,但不会危及整个银行系统的稳定。从更长远的角度来看,缺乏有效运作的市场将导致流动性管理效率低下。
摘要
金融机构的系统重要性是监管机构和中央银行的关键数据,因为维护金融体系稳定是它们的责任。然而,这些信息对于投资者也非常重要,因为它有助于分散其在金融行业的风险敞口。
在本章中,我们展示了两种可以帮助识别系统重要金融机构的不同方法。这两种方法都基于网络理论的工具。第一种方法只关注每个机构在金融网络中的位置。因此,它没有考虑到各个机构的资产负债表结构。第二种方法是一种模拟方法,也考虑了关于银行资本状况的某些重要数据。需要综合考虑这两种方法的结果,才能得到清晰的全貌。
参考文献
-
Anand, Kartik, Ben Craig 和 Goetz von Peter (2014):《填补空白:网络结构与银行间传染》,德国联邦银行讨论论文,第 02/2014 号
-
Berlinger, E., M. Michaletzky 和 M. Szenes (2011):《无担保银行间外汇市场的网络动态研究——流动性危机前后的分析》(Network dynamics of unsecured interbank HUF markets before and after the liquidity crisis)。《经济学评论》,第 58 卷,第 3 期
-
Billio, Monica, Mila Getmansky, Dale Gray, Andrew W. Lo, Robert C. Merton 和 Loriana Pelizzon:《主权、银行和保险信贷利差:关联性与系统网络》,Mimeo,2013 年
-
BIS (2011): 《全球系统重要性银行:评估方法及附加损失吸收要求》,规则文本,2011 年 11 月
-
Borgatti, Stephen 和 Martin Everett (1999):《核心/外围结构模型》,《社会网络》21 期
-
Bron, Coen 和 Kerbosch, Joep (1973):算法 457:寻找无向图的所有团体,《ACM 通信》16 卷(9 期):575–577
-
Craig, Ben and Goetz von Peter (2010):银行间分层与货币中心银行 – BIS 工作论文第 322 号,2010 年 10 月
-
Daróczi, Gergely, Michael Puhle, Edina Berlinger, Péter Csóka, Daniel Havran, Márton Michaletzky, Zsolt Tulassay, Kata Váradi, Agnes Vidovics-Dancs (2013):量化金融的 R 语言导论,Packt 出版公司(2013 年 11 月 22 日)
-
Eisenberg, L., Noe, T.H. (2001):金融系统中的系统性风险。《管理科学》47 (2),236–249
-
FSB, IMF, BIS (2009):评估金融机构、市场和工具的系统重要性的指导:初步考虑 – 背景文件,报告提交给 G-20 财长和央行行长,2009 年 10 月
-
Furfine, C.H. (2003):银行间暴露:量化传染风险。《货币、信用与银行学报》35 (1),111–128
-
Iazzetta, I. 和 M. Manna (2009):银行间市场的拓扑结构:自 1990 年以来意大利的变化,意大利银行工作论文第 711 号,2009 年 5 月


进行线性回归,回归对象是
(简单的 OLS 估计)。
,对矩阵 A 施加限制,使得
是一个对角协方差矩阵。为了使模型“恰好识别”,我们需要额外的
限制。这让人联想到施加一个三角矩阵(但并不要求该特定结构)。
。
确定。



浙公网安备 33010602011771号