Python 量化金融第二版(一)
原文:
zh.annas-archive.org/md5/25334ce178953792df9684c784953114
译者:飞龙
协议:CC BY-NC-SA 4.0
前言
我们坚信,任何一位雄心勃勃的金融专业学生都应该学习至少一门计算机语言。基本原因是我们已经进入了所谓的大数据时代。在金融领域,我们有大量的数据,并且大部分数据是公开的,可以免费获得。为了高效利用这些丰富的数据源,我们需要一个工具。在众多潜在的候选工具中,Python 是最佳选择之一。
关于第二版的几点话
对于第二版,我们重新组织了本书的结构,增加了更多与金融相关的章节。这是对众多读者反馈的认可和回应。第二版的前两章专门介绍 Python,之后的所有章节都与金融相关。再次强调,本书中的 Python 主要作为工具,帮助读者更好地学习和理解金融理论。为了满足各种量化程序、商业分析程序和金融工程程序对各类数据的使用需求,我们新增了第四章,数据来源。由于这种结构调整,本版更加适合如定量金融、使用 Python 进行金融分析和商业分析等为期一个学期的课程。宾州州立大学的 Premal P. Vora 教授和西敏大学的 Sheng Xiao 教授已经将第一版作为他们的教材。希望更多的金融、会计学教授会发现第二版更适合他们的学生,特别是金融工程、商业分析和其他定量领域的学生。
为什么选择 Python?
使用 Python 有多种理由。首先,Python 是免费的,无需授权费用。Python 可在所有主要操作系统上运行,如 Windows、Linux/Unix、OS/2、Mac 和 Amiga 等。免费这一点有很多好处。当学生毕业后,他们可以将学到的知识应用到任何地方,金融领域也是如此。相比之下,SAS 和 MATLAB 并非如此。其次,Python 功能强大、灵活且易学。它几乎能够解决我们所有的金融和经济估算问题。第三,Python 可应用于大数据。Dasgupta(2013)认为,R 和 Python 是两种最受欢迎的开源数据分析编程语言。第四,Python 中有许多有用的模块,每个模块都是为特定目的开发的。本书中,我们重点介绍了 NumPy、SciPy、Matplotlib、Statsmodels 和 Pandas 模块。
一本由金融教授编写的编程书籍
毫无疑问,大多数编程书籍都是由计算机科学的教授编写的。由一位金融学教授编写编程书籍似乎有些奇怪。可以理解的是,重点会有所不同。如果是计算机科学的讲师在编写这本书,自然重点会放在 Python 上,而真正的重点应该是金融。书名*《Python 金融应用》*应该可以让人一目了然。此书旨在改变目前许多服务于金融界的编程书籍过于注重编程语言本身,而忽视了金融内容的现状。书中的另一个独特之处是,它使用了大量与经济学、金融和会计相关的公共数据,详见第四章,数据来源部分。
本书内容简介
第一章,Python 基础,提供了简短的介绍,并解释了如何安装 Python、如何启动和退出 Python、变量赋值、向量、矩阵和元组、调用嵌入式函数、编写自己的函数、从输入文件读取数据、简单的数据处理、输出数据和结果,以及生成一个带有 pickle 扩展的 Python 数据集。
第二章,Python 模块介绍,讨论了模块的含义、如何导入模块、显示导入模块中包含的所有函数、为导入的模块取一个简短的别名、对比 import math 和 from math import、删除已导入的模块、仅导入模块中的某些函数、NumPy、SciPy、matplotlib、statsmodels、pandas 和 Pandas_reader 的介绍,如何查找所有内置模块及所有可用的(预安装的)模块,以及如何查找特定的未安装模块。
第三章,货币的时间价值,介绍并讨论了与金融相关的各种基本概念和公式,如单一未来现金流的现值、(增长型)永续年金的现值、年金的现值和未来值、永续年金与即付永续年金的区别、年金与即付年金的区别、SciPy 和 numpy.lib.financial 子模块中的相关函数、一个用 Python 编写的免费的金融计算器、净现值(NPV)的定义及其相关规则、内部收益率(IRR)的定义及其相关规则、时间价值的 Python 图形展示,以及 NPV 曲线。
第四章,数据来源,讨论了如何从各种公共来源获取数据,如 Yahoo!Finance、Google finance、FRED(美联储经济数据图书馆)、French 教授的数据图书馆、BLS(美国劳工统计局)和人口普查局。此外,还会讨论各种输入数据的方法,例如 csv、txt、pkl、Matlab、SAS 或 Excel 格式的文件。
第五章, 债券与股票估值,介绍了利率及其相关概念,如年利率(APR)、有效年利率(EAR)、复利频率,如何将一种有效利率转换为另一种,利率期限结构,如何估算普通债券的售价,如何使用所谓的折现股息模型来估算股票价格等。
第六章, 资本资产定价模型,展示了如何从 Yahoo!Finance 下载数据以运行 CAPM 的线性回归,滚动 beta,几个 Python 程序来估算多只股票的 beta、调整 beta 和投资组合 beta 估算,Scholes 和 Williams(1977)Dimson(1979)提出的两种 beta 调整方法。
第七章, 多因子模型与业绩衡量,展示了如何将第六章中描述的单因子模型,资本资产定价模型,扩展到多因子和复杂模型,如 Fama-French 三因子模型、Fama-French-Carhart 四因子模型、Fama-French 五因子模型,以及业绩衡量指标,如 Sharpe 比率、Treynor 比率、Sortino 比率和 Jensen’s alpha。
第八章, 时间序列分析,展示了如何设计良好的日期变量,通过该日期变量合并数据集,正态分布、正态性检验、利率期限结构、52 周高低交易策略、收益估算、将日收益转换为月度或年度收益、T 检验、F 检验、Durbin-Watson 自相关检验、Fama-MacBeth 回归、Roll(1984)价差、Amihud(2002)流动性测量、Pastor 和 Stambaugh(2003)流动性指标、1 月效应、星期效应、从 Google Finance 和 Hasbrouck 教授的 TORQ 数据库(交易、订单、报告和报价)中检索高频数据,以及介绍 CRSP(证券价格研究中心)数据库。
第九章, 投资组合理论,讨论了 2 只股票投资组合、N 只股票投资组合的均值和风险估算,相关性与多样化效应,如何生成收益矩阵,基于 Sharpe 比率、Treynor 比率和 Sortino 比率生成最佳投资组合;如何构建有效前沿;Modigliani 和 Modigliani 业绩衡量(M2 衡量);以及如何使用市值加权和等权重方法估算投资组合收益。
第十章,期权与期货,讨论了看涨期权和看跌期权的收益和盈亏函数及其图示表示,欧洲期权与美式期权;正态分布;标准正态分布;累积正态分布;著名的布莱克-斯科尔斯-莫顿期权模型(有无红利);各种交易策略及其可视化表示,如备兑看涨、跨式期权、蝶式期权和日历差价期权;希腊字母;看跌-看涨平价及其图形表示;单步和两步二项树模型的图形表示;如何使用二项树方法定价欧洲期权和美式期权;以及隐含波动率、波动率微笑和波动率偏斜。
第十一章,风险价值,首先回顾了正态分布的密度函数和累积分布函数,然后讨论了基于正态性假设估算 VaR 的第一种方法,如何从 1 天风险转换为 n 天风险,从 1 天 VaR 转换为 n 天 VaR,正态性检验,偏度和峰度的影响,如何通过包括偏度和峰度来修正 VaR 度量,基于历史收益的第二种 VaR 估算方法,如何通过蒙特卡洛模拟将两种方法链接起来,回测和压力测试。
第十二章,蒙特卡洛模拟,讨论了如何通过蒙特卡洛模拟估算π值;使用对数正态分布模拟股票价格波动;构建有效的投资组合和有效前沿;通过模拟复制布莱克-斯科尔斯-莫顿期权模型;定价多种奇异期权,如浮动行权价的回顾期权;有放回/无放回的自助法;长期预期收益预测及其相关效率,准蒙特卡洛模拟和索博尔序列。
第十三章,信用风险分析,讨论了穆迪、标准普尔和惠誉的信用评级、信用利差、1 年和 5 年迁移矩阵、利率期限结构、阿尔特曼 Z 分数用于预测企业破产、KMV 模型用于估算总资产及其波动性、违约概率和违约距离,以及信用违约掉期。
第十四章,奇异期权,首先比较了我们在第九章,投资组合理论 中学到的欧洲期权和美式期权与伯穆达期权,然后讨论了定价简单选择期权、喊叫期权、彩虹期权和二元期权的方法;平均价格期权;障碍期权,如向上进场期权和向上退场期权;以及障碍期权,如向下进场和向下退场期权。
第十五章,波动率、隐含波动率、ARCH 与 GARCH,重点讨论两个问题:波动率度量和 ARCH/GARCH 模型。
面向小程序
根据作者在七所学校的教学经验,包括加拿大的麦吉尔大学和威尔弗里德·劳里大学、新加坡的南洋理工大学,以及美国的洛约拉大学、马里兰大学大学城分校、霍夫斯特拉大学和卡尼修斯学院,再加上他在沃顿商学院的八年咨询经验,他知道许多金融学生喜欢解决单一特定任务的小程序。大多数编程书籍仅提供几个完整且复杂的程序,程序的数量远远不足。采取这种方法有两个副作用。首先,金融学生会被编程细节淹没,感到畏惧,最终失去学习计算机语言的兴趣。其次,他们没有学会如何应用所学的知识,例如运行资本资产定价模型(CAPM)来估算 1990 年到 2013 年间 IBM 的贝塔值。本书提供了约 300 个完整的 Python 程序,涵盖许多金融话题。
使用真实数据
大多数编程书籍的另一个缺点是它们使用假设数据。而在本书中,我们使用真实世界的数据来探讨各类金融话题。例如,我没有仅仅展示如何运行 CAPM 来估算贝塔(市场风险),而是向你展示如何估算 IBM、苹果或沃尔玛的贝塔。与仅呈现公式来估算投资组合的收益和风险不同,本书提供了 Python 程序来下载真实数据、构建各种投资组合,然后估算它们的收益和风险,包括风险价值(VaR)。当我还是一名博士生时,我学到了波动率微笑的基本概念。然而,直到写这本书时,我才有机会下载真实世界的数据来绘制 IBM 的波动率微笑。
本书所需内容
在这里,我们通过几个具体的例子来展示读者在认真阅读本书后能够取得的成就。
首先,在阅读完前两章后,读者/学生应该能够使用 Python 计算现值、未来值、年金现值、内部收益率(IRR)以及许多其他金融公式。换句话说,我们可以将 Python 作为一个免费的普通计算器来解决许多金融问题。其次,在完成前三章后,读者/学生或金融教师能够构建一个免费的金融计算器,即将数十个小的 Python 程序组合成一个大的 Python 程序。这个大程序的表现就像是任何其他人编写的模块。第三,读者将学习如何编写 Python 程序,下载并处理来自各种公共数据源的金融数据,例如 Yahoo! Finance、Google Finance、美国联邦储备数据库和法国教授的数据库。
第四,读者将理解与模块相关的基本概念,模块是专家、其他用户或我们为特定目的编写的包。第五,理解了 Matplotlib 模块后,读者可以生成各种图表。例如,读者可以通过结合基础股票和期权,使用图表展示基于不同交易策略的收益/利润结果。第六,读者将能够从 Yahoo! Finance 下载 IBM 的每日价格、标准普尔 500 指数价格,并通过应用资本资产定价模型(CAPM)来估算其市场风险(贝塔系数)。他们还将能够形成包含不同证券的投资组合,例如无风险资产、债券和股票。然后,他们可以通过应用 Markowitz 的均值-方差模型来优化他们的投资组合。此外,读者将知道如何估算其投资组合的风险价值(VaR)。
第七,读者应该能够通过应用 Black-Scholes-Merton 期权模型(仅适用于欧洲期权)和蒙特卡罗模拟(适用于欧洲期权和美国期权)来定价欧洲期权和美国期权。最后但同样重要的是,读者将学习几种衡量波动率的方法。特别是,他们将学习如何使用自回归条件异方差(ARCH)模型和广义自回归条件异方差(GARCH)模型。
本书适合谁阅读
如果你是金融专业的研究生,特别是学习计算金融、金融建模、金融工程或商业分析的学生,那么本书将对你大有裨益。这里有两个例子:宾夕法尼亚州立大学的 Premal P. Vora 教授在他的课程 数据科学与金融 中使用了本书,而西敏寺学院的 Sheng Xiao 教授则在他的课程 金融分析 中使用了本书。如果你是专业人士,你可以学习 Python 并将其应用于许多金融项目。如果你是个人投资者,阅读本书同样也会让你受益。
约定
本书中,你将发现几种文本样式,用于区分不同类型的信息。以下是这些样式的一些示例,并解释它们的含义。
文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入以及 Twitter 用户名通常显示如下:“sqrt()
函数,平方根,包含在 math
模块中。”
一段代码如下所示:
>>>sqrt(2)
NameError: name 'sqrt' is not defined
>>> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
math.sqrt(2)
1.4142135623730951
>>>
任何命令行输入或输出如下所示:
help(pv_f)
新术语和重要词汇以粗体显示。在屏幕上看到的单词,例如菜单或对话框中的单词,将以这种形式出现在文本中:“要编写 Python 程序,我们点击 文件,然后点击 新建文件。”
注意
警告或重要说明会显示在像这样的框中。
提示
提示和技巧如下所示。
读者反馈
我们总是欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢或可能不喜欢的部分。读者的反馈对我们开发出让你真正受益的书籍至关重要。
若要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>
,并在邮件主题中提到书籍标题。
如果你在某个领域有专长,并且有兴趣为书籍撰写或贡献内容,请查看我们的作者指南:www.packtpub.com/authors。
客户支持
现在,你已经是一本 Packt 书籍的骄傲拥有者,我们提供了许多资源帮助你从购买中获得最大价值。
下载示例代码
你可以从你的账户中下载本书的示例代码文件,访问:www.packtpub.com
。如果你从其他地方购买了这本书,可以访问:www.packtpub.com/support
,并注册后直接将文件通过电子邮件发送给你。
你可以按照以下步骤下载代码文件:
-
你可以按照以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的SUPPORT标签上。
-
点击Code Downloads & Errata。
-
在Search框中输入书名。
-
选择你想要下载代码文件的书籍。
-
从下拉菜单中选择你购买此书的地方。
-
点击Code Download。
下载文件后,请确保使用以下最新版本解压或提取文件夹:
WinRAR / 7-Zip for Windows
Zipeg / iZip / UnRarX for Mac
7-Zip / PeaZip for Linux
本书的代码包也托管在 GitHub 上,地址为:github.com/PacktPublishing/Python-for-Finance-Second-Edition
。我们还提供来自我们丰富书籍和视频目录的其他代码包,地址为:github.com/PacktPublishing/
。快去看看吧!
勘误表
尽管我们已经尽一切努力确保内容的准确性,但错误还是可能发生。如果您在我们的一本书中发现错误——无论是文本错误还是代码错误——我们将非常感激您能报告给我们。这样,您可以帮助其他读者避免困扰,并帮助我们改进书籍的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata
提交,选择您的书籍,点击 勘误 提交 表单 链接,并填写勘误的详细信息。一旦您的勘误被验证,您的提交将被接受,勘误将在我们的网站上上传,或加入到该书籍现有的勘误列表中,位于该书籍的勘误部分。您可以通过访问 www.packtpub.com/support
来查看任何现有的勘误。
盗版
网络上侵犯版权的盗版问题在所有媒体中持续存在。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上发现任何非法的我们作品的复制品,无论其形式如何,请立即向我们提供位置地址或网站名称,以便我们采取相应措施。
请通过 <copyright@packtpub.com>
联系我们,并提供涉嫌盗版材料的链接。
感谢您的帮助,帮助我们保护作者,并支持我们为您提供有价值的内容。
问题
如果您在书籍的任何方面遇到问题,可以通过 <questions@packtpub.com>
联系我们,我们会尽力解决。
第一章:Python 基础
本章将讨论基本概念和一些与 Python 相关的广泛使用的函数。本章以及下一章(第二章,Python 模块简介)是唯一完全基于 Python 技术的章节。这两章作为对有一定 Python 基础的读者的复习内容。没有任何 Python 基础的初学者,仅通过阅读这两章是不可能掌握 Python 的。对于想要更详细学习 Python 的新手,他们可以找到许多优秀的书籍。从第三章,货币时间价值开始,我们将使用 Python 来解释或演示各种金融概念、运行回归分析以及处理与经济学、金融和会计相关的数据。因此,我们将在接下来的每一章中提供更多与 Python 相关的技术和使用方法。
本章特别讨论以下主题:
Python 安装
变量赋值、空格和编写我们自己的程序
编写 Python 函数
数据输入
数据处理
数据输出
Python 安装
本节将讨论如何安装 Python。更具体地说,我们将讨论两种方法:通过 Anaconda 安装 Python 和直接安装 Python。
有几个原因说明为什么首选第一种方法:
首先,我们可以使用一个名为 Spyder 的 Python 编辑器,它对于编写和编辑我们的 Python 程序非常方便。例如,它有几个窗口(面板):一个用于控制台,我们可以在其中直接输入命令;一个用于程序编辑器,我们可以在其中编写和编辑我们的程序;一个用于 变量浏览器,我们可以查看我们的变量及其值;还有一个用于帮助,我们可以在其中寻求帮助。
第二,不同颜色的代码或注释行将帮助我们避免一些明显的拼写错误和失误。
第三,安装 Anaconda 时,许多模块会同时安装。模块是由专家、专业人士或任何围绕特定主题的人编写的一组程序。它可以看作是某个特定任务的工具箱。为了加速新工具的开发过程,一个新的模块通常依赖于其他已经开发的模块中的功能。这被称为模块依赖性。这样的模块依赖性有一个缺点,就是如何同时安装它们。有关更多信息,请参阅第二章,Python 模块简介。
通过 Anaconda 安装 Python
我们可以通过几种方式安装 Python。结果是我们将有不同的环境来编写和运行 Python 程序。
以下是一个简单的两步法。首先,我们访问continuum.io/downloads
并找到合适的安装包;请参见以下截图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_01.jpg
对于 Python,不同版本共存。从前面的截图中,我们看到有两个版本,分别是 3.5 和 2.7。
对于本书,版本并不是非常关键。旧版本问题较少,而新版本通常有新的改进。同样,模块依赖可能会成为一个大难题;详见第二章,Python 模块简介,以获取更多信息。Anaconda 的版本是 4.2.0。由于我们将通过 Spyder 启动 Python,因此它也可能有不同的版本。
通过 Spyder 启动 Python
通过 Anaconda 安装 Python 后,我们可以导航到Start(Windows 版本)|All Programs|Anaconda3(32-bit),如以下截图所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_02.jpg
在我们点击Spyder,即前面截图中的最后一个条目后,下面将显示四个面板:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_03.jpg
左上方的面板(窗口)是我们的程序编辑器,在这里我们编写程序。右下方的面板是 IPython 控制台,我们可以在其中输入简单的命令。IPython 是默认的控制台。要了解更多关于 IPython 的信息,只需输入一个问号;请参见以下截图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_04.jpg
另外,我们可以通过点击菜单栏上的Consoles,然后选择Open a Python console来启动 Python 控制台。之后,以下窗口将出现:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_05.jpg
在四个面板的图像中,右上方的面板是我们的帮助窗口,在这里我们可以寻求帮助。中间的面板称为变量浏览器,其中显示了变量的名称及其值。根据个人喜好,用户可以调整这些面板的大小或重新组织它们。
直接安装 Python
对于大多数用户来说,了解如何通过 Anaconda 安装 Python 已经足够了。为了完整性,下面展示了安装 Python 的第二种方式。
以下是涉及的步骤:
-
首先,访问www.python.org/download:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_06.jpg
-
根据你的电脑选择合适的安装包,例如 Python 版本 3.5.2。对于本书而言,Python 的版本并不重要。在这个阶段,新的用户可以直接安装最新版本的 Python。安装后,我们将看到以下 Windows 版本的条目:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_07.jpg
-
要启动 Python,我们可以点击
IDLE (Python 3.5\. 32 bit)
并看到以下界面:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_08.jpg -
从截图中显示的四个面板中的 IPython,或者从 Python 控制台面板,或从之前显示 Python Shell 的截图中,我们可以输入各种命令,如下所示:
>>>pv=100 >>>pv*(1+0.1)**20 672.7499949325611 >>> import math >>>math.sqrt(3) 1.7320508075688772 >>>
-
要编写一个 Python 程序,我们点击文件,然后点击新建文件:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_09.jpg
-
输入这个程序并保存:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_10.jpg
-
点击运行,然后点击运行模块。如果没有错误发生,我们就可以像使用其他内置函数一样使用该函数,如下所示:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_11.jpg
变量赋值、空格和编写我们自己的程序
首先,对于 Python 语言,空格或空格非常重要。例如,如果我们在输入pv=100
之前不小心多了一个空格,我们将看到如下错误信息:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_12.jpg
错误的名称是IndentationError
。原因是,对于 Python 来说,缩进非常重要。在本章稍后的内容中,我们将学习如何通过适当的缩进来规范/定义我们编写的函数,或者为何一组代码属于特定的主题、函数或循环。
假设我们今天在银行存入 100 美元。如果银行提供我们年存款利率 1.5%,那么 3 年后的价值是多少?相关代码如下所示:
>>>pv=100
>>>pv
100
>>>pv*(1+0.015)**3
104.56783749999997
>>>
在前面的代码中,**
表示幂运算。例如,2**3
的值是8
。要查看变量的值,我们只需输入变量名;请参见前面的例子。使用的公式如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_13.jpg
在这里,FV是未来值,PV是现值,R是周期存款利率,而n是周期数。在这个例子中,R是年利率0.015,而n是3。此时,读者应该专注于简单的 Python 概念和操作。
在第三章中,时间价值,该公式将详细解释。由于 Python 是区分大小写的,如果我们输入PV
而不是pv
,就会弹出错误信息;请参见以下代码:
>>>PV
NameError: name 'PV' is not defined
>>>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
与一些语言(如 C 和 FORTRAN)不同,Python 中变量不需要在赋值之前定义。要显示所有变量或函数,我们使用dir()
函数:
>>>dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'pv']
>>>
要查看所有内置函数,我们输入dir(__builtings__)
。输出如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_14.jpg
编写一个 Python 函数
假设我们有兴趣为方程式(1)编写一个 Python 函数。
启动 Spyder 后,点击文件,然后点击新建文件。我们编写以下两行代码,如左侧面板所示。关键字def
表示函数,fv_f
是函数名称,括号中的三个值pv
、r
和n
是输入变量。
冒号(:
)表示函数尚未结束。当我们按下 Enter 键时,下一行将自动缩进。
当我们输入 return pv*(1+r)**n
并按下 Enter 键两次时,这个简单的程序就完成了。显然,在第二行中,**
代表了一个幂运算。
假设我们将其保存为 c:/temp/temp.py
:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_15.jpg
要运行或调试程序,请点击菜单栏下方 Run 旁的箭头键;见前面的右上方图像。编译结果由右下方的图像显示(右上方第二张图)。现在,我们可以通过传入三个输入值轻松调用这个函数:
>>>fv_f(100,0.1,2)
121.00000000000001
>>>fv_f(100,0.02,20)
148.59473959783548
如果在程序中添加一些注释,解释输入变量的含义、使用的公式以及一些示例,将对其他用户或程序员非常有帮助。请查看以下带注释的程序:
def pv_f(fv,r,n):
"""Objective: estimate present value
fv
formula : pv=-------------
(1+r)^n
fv: fture value
r : discount periodic rate
n : number of periods
Example #1 >>>pv_f(100,0.1,1)
90.9090909090909
Example #2: >>>pv_f(r=0.1,fv=100,n=1)
90.9090909090909
"""
return fv/(1+r)**n
注释或解释内容包含在一对三重双引号中("""
和 """
)。注释中的缩进不重要。在编译时,底层软件会忽略所有注释。这些注释的美妙之处在于,我们可以通过 help(pv_f)
查看它们,如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_16.jpg
在 第二章,Python 模块介绍 中,我们将展示如何上传用 Python 编写的金融计算器;在 第三章,货币的时间价值 中,我们将解释如何生成这样的金融计算器。
Python 循环
在本节中,我们讨论一个非常重要的概念:循环。循环用于重复执行相同的任务,只是输入或其他因素略有不同。
Python 循环,if…else 条件
让我们来看一个简单的循环,遍历数组中的所有数据项:
>>>import numpy as np
>>>cashFlows=np.array([-100,50,40,30])
>>>for cash in cashFlows:
... print(cash)
...
-100
50
40
30
一种数据类型叫做元组,我们使用一对圆括号 ()
来包含所有输入值。元组变量的一个特点是我们不能修改它的值。如果有些变量永远不应该被修改,这个特殊的属性可能非常有用。元组与字典不同,字典通过键值对存储数据。字典是无序的,并且要求键是可哈希的。与元组不同,字典的值是可以修改的。
注意,对于 Python,向量或元组的下标是从 0
开始的。如果 x
的长度为 3
,那么下标将是 0
、1
和 2
:
>>> x=[1,2,3]
>>>x[0]=2
>>>x
>>>
[2, 2, 3]
>>> y=(7,8,9)
>>>y[0]=10
>>>
TypeError: 'tuple' object does not support item assignment
>>>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
>>>type(x)
>>>
<class'list'>
>>>type(y)
>>>
<class'tuple'>
>>>
假设我们今天投资 $100,并且明年再投资 $30,那么未来 5 年每年年末的现金流入将分别是 $10、$40、$50、$45 和 $20,从第二年年末开始;见下方的时间轴及其对应的现金流:
-100 -30 10 40 50 45 20
|--------|---------|--------|---------|----------|--------|
0 1 2 3 4 5 6
如果折现率为 3.5%,净现值(NPV)是多少?NPV 定义为所有收益的现值减去所有成本的现值。如果现金流入为正,现金流出为负,那么 NPV 可以方便地定义为所有现金流现值的总和。一个未来价值的现值是通过应用以下公式估算的:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_17.jpg
这里,PV 是现值,FV 是未来值,R 是期间折现率,n 是期数。在第三章,货币的时间价值中,我们将更详细地解释这个公式的意义。目前,我们只想编写一个npv_f()
函数,应用上述公式n 次,其中 n 是现金流的数量。完整的 NPV 程序如下所示:
def npv_f(rate, cashflows):
total = 0.0
for i in range(0,len(cashflows)):
total += cashflows[i] / (1 + rate)**i
return total
在程序中,我们使用了for
循环。同样,Python 中正确的缩进非常重要。第 2 到第 5 行都缩进了一个单位,因此它们属于同一个函数,名为npv_f
。类似地,第 4 行缩进了两个单位,也就是在第二个冒号(:
)后,它属于for
循环。total +=a
命令等价于 total=total +a
。
对于 NPV 函数,我们使用for
循环。注意,Python 中向量的索引从零开始,临时变量i
也从零开始。我们可以通过输入两组值来轻松调用此函数。输出如下所示:
>>>r=0.035
>>>cashflows=[-100,-30,10,40,50,45,20]
>>>npv_f(r,cashflows)
14.158224763725372
这是另一个带有enumerate()
函数的npv_f()
函数。这个函数将生成一对索引,从0
开始,并返回其对应的值:
def npv_f(rate, cashflows):
total = 0.0
for i, cashflow in enumerate(cashflows):
total += cashflow / (1 + rate)**i
return total
这里是一个使用enumerate()
的例子:
x=["a","b","z"]
for i, value in enumerate(x):
print(i, value)
与之前指定的npv_f
函数不同,Microsoft Excel 中的 NPV 函数实际上是一个 PV
函数,这意味着它只能应用于未来值。其等效的 Python 程序,称为 npv_Excel
,如下所示:
def npv_Excel(rate, cashflows):
total = 0.0
for i, cashflow in enumerate(cashflows):
total += cashflow / (1 + rate)**(i+1)
return total
比较结果如下表所示。左侧面板显示了 Python 程序的结果,右侧面板显示了调用 Excel NPV 函数的结果。请特别注意前面的程序以及如何调用这样的函数:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_18.jpg
通过使用循环,我们可以用不同的输入重复相同的任务。例如,我们计划打印一组值。以下是一个while
循环的例子:
i=1
while(i<10):
print(i)
i+=1
以下程序将报告一个折现率(或多个折现率),使其相应的 NPV 等于零。假设现金流分别为 550
、-500
、-500
、-500
和 1000
,时间点为 0
,并且接下来 4 年每年的年末。有关此练习的概念,我们将在第三章中进一步解释。
编写一个 Python 程序,找出哪个折扣率使得 NPV 等于零。由于现金流的方向变化两次,我们可能有两个不同的折扣率使得 NPV 为零:
cashFlows=(550,-500,-500,-500,1000)
r=0
while(r<1.0):
r+=0.000001
npv=npv_f(r,cashFlows)
if(abs(npv)<=0.0001):
print(r)
相应的输出如下:
0.07163900000005098
0.33673299999790873
在本章后面,将使用for
循环来估算一个项目的 NPV。
当我们需要使用一些数学函数时,可以先导入math
模块:
>>>import math
>>>dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
>>>math.pi
3.141592653589793
>>>
sqrt()
平方根函数包含在math
模块中。因此,要使用sqrt()
函数,我们需要使用math.sqrt()
;请参见以下代码:
>>>sqrt(2)
NameError: name 'sqrt' is not defined
>>>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
math.sqrt(2)
1.4142135623730951
>>>
如果我们希望直接调用这些函数,可以使用from math import *
;请参见以下代码:
>>>from math import *
>>>sqrt(3)
1.7320508075688772
>>>
要了解各个嵌入函数,我们可以使用help()
函数;请参见以下代码:
>>>help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
>>>
数据输入
首先,我们生成一个非常简单的输入数据集,如下所示。它的名称和位置为c:/temp/test.txt
。数据集的格式是文本:
a b
1 2
3 4
代码如下:
>>>f=open("c:/temp/test.txt","r")
>>>x=f.read()
>>>f.close()
可以使用print()
函数来显示x
的值:
>>>print(x)
a b
1 2
3 4
>>>
对于第二个示例,首先让我们从Yahoo!Finance下载 IBM 的每日历史价格数据。为此,我们访问finance.yahoo.com
:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_19.jpg
输入IBM
以找到其相关网页。然后点击历史数据,再点击下载:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_20.jpg
假设我们将每日数据保存为ibm.csv
,并放在c:/temp/
目录下。前五行如下所示:
Date,Open,High,Low,Close,Volume,Adj Close
2016-11-04,152.399994,153.639999,151.869995,152.429993,2440700,152.429993
2016-11-03,152.509995,153.740005,151.800003,152.369995,2878800,152.369995
2016-11-02,152.479996,153.350006,151.669998,151.949997,3074400,151.949997
2016-11-01,153.50,153.910004,151.740005,152.789993,3191900,152.789993
第一行显示了变量名:日期、开盘价、交易日内的最高价、交易日内的最低价、交易日内最后一次交易的收盘价、交易量和交易日的调整价格。分隔符是逗号。加载文本文件有几种方法,这里讨论了一些方法:
方法一:我们可以使用pandas
模块中的read_csv
:
>>> import pandas as pd
>>> x=pd.read_csv("c:/temp/ibm.csv")
>>>x[1:3]
Date Open High Low Close Volume \
1 2016-11-02 152.479996 153.350006 151.669998 151.949997 3074400
2 2016-11-01 153.500000 153.910004 151.740005 152.789993 3191900
Adj.Close
1 151.949997
2 152.789993>>>
方法二:我们可以使用pandas
模块中的read_table
;请参见以下代码:
>>> import pandas as pd
>>> x=pd.read_table("c:/temp/ibm.csv",sep=',')
另外,我们可以直接从 Yahoo!Finance 下载 IBM 的每日价格数据;请参见以下代码:
>>> import pandas as pd
>>>url=url='http://canisius.edu/~yany/data/ibm.csv'
>>> x=pd.read_csv(url)
>>>x[1:5]
Date Open High Low Close Volume \
1 2016-11-03 152.509995 153.740005 151.800003 152.369995 2843600
2 2016-11-02 152.479996 153.350006 151.669998 151.949997 3074400
3 2016-11-01 153.500000 153.910004 151.740005 152.789993 3191900
4 2016-10-31 152.759995 154.330002 152.759995 153.690002 3553200
Adj Close
1 152.369995
2 151.949997
3 152.789993
4 153.690002>>>
我们可以通过使用pandas
模块中的ExcelFile()
函数从 Excel 文件中检索数据。首先,我们生成一个包含少量观测值的 Excel 文件;请参见以下截图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_21.jpg
假设我们将此 Excel 文件命名为stockReturns.xlxs
,并保存到c:/temp/
。Python 代码如下:
>>>infile=pd.ExcelFile("c:/temp/stockReturns.xlsx")
>>> x=infile.parse("Sheet1")
>>>x
date returnAreturnB
0 2001 0.10 0.12
1 2002 0.03 0.05
2 2003 0.12 0.15
3 2004 0.20 0.22
>>>
要检索扩展名为.pkl
或.pickle
的 Python 数据集,我们可以使用以下代码。首先,我们从作者的网页www3.canisius.edu/~yany/python/ffMonthly.pkl
下载名为ffMonthly.pkl
的 Python 数据集。
假设数据集保存在c:/temp/
目录下。可以使用pandas
模块中名为read_pickle()
的函数加载扩展名为.pkl
或.pickle
的数据集:
>>> import pandas as pd
>>> x=pd.read_pickle("c:/temp/ffMonthly.pkl")
>>>x[1:3]
>>>
Mkt_RfSMBHMLRf
196308 0.0507 -0.0085 0.0163 0.0042
196309 -0.0157 -0.0050 0.0019 -0.0080
>>>
以下是最简单的 if
函数:当我们的利率为负时,打印一个警告信息:
if(r<0):
print("interest rate is less than zero")
与逻辑 AND
和 OR
相关的条件如下所示:
>>>if(a>0 and b>0):
print("both positive")
>>>if(a>0 or b>0):
print("at least one is positive")
对于多个 if...elif
条件,以下程序通过将数字等级转换为字母等级来说明其应用:
grade=74
if grade>=90:
print('A')
elif grade >=85:
print('A-')
elif grade >=80:
print('B+')
elif grade >=75:
print('B')
elif grade >=70:
print('B-')
elif grade>=65:
print('C+')
else:
print('D')
请注意,对于这种多重 if...elif
函数,最好以 else
条件结尾,因为如果没有满足这些条件,我们确切知道结果是什么。
数据操作
数据有很多不同的类型,比如整数、实数或字符串。以下表格列出了这些数据类型:
数据类型 | 描述 |
---|---|
Bool |
布尔值(TRUE 或 FALSE )以字节存储 |
Int |
平台整数(通常为 int32 或 int64 ) |
int8 |
字节(-128 到 127 ) |
int16 |
整数(-32768 到 32767 ) |
int32 |
整数(-2147483648 到 2147483647 ) |
int64 |
整数(9223372036854775808 到 9223372036854775807 ) |
unit8 |
无符号整数(0 到 255 ) |
unit16 |
无符号整数(0 到 65535 ) |
unit32 |
无符号整数(0 到 4294967295 ) |
unit64 |
无符号整数(0 到 18446744073709551615 ) |
float |
短浮点数,用于 float6 |
float32 |
单精度浮点数:符号位 bit23 位尾数;8 位指数 |
float64 |
52 位尾数 |
complex |
complex128 的简写 |
complex64 |
复数;由两个 32 位浮点数表示(实部和虚部) |
complex128 |
复数;由两个 64 位浮点数表示(实部和虚部) |
表 1.1 不同数据类型的列表
在以下示例中,我们将一个标量值赋给 r
,并将多个值赋给 pv
,它是一个数组(向量)。type()
函数用于显示它们的类型:
>>> import numpy as np
>>> r=0.023
>>>pv=np.array([100,300,500])
>>>type(r)
<class'float'>
>>>type(pv)
<class'numpy.ndarray'>
为了选择合适的决策,我们使用 round()
函数;见以下示例:
>>> 7/3
2.3333333333333335
>>>round(7/3,5)
2.33333
>>>
对于数据操作,我们来看一些简单的操作:
>>>import numpy as np
>>>a=np.zeros(10) # array with 10 zeros
>>>b=np.zeros((3,2),dtype=float) # 3 by 2 with zeros
>>>c=np.ones((4,3),float) # 4 by 3 with all ones
>>>d=np.array(range(10),float) # 0,1, 2,3 .. up to 9
>>>e1=np.identity(4) # identity 4 by 4 matrix
>>>e2=np.eye(4) # same as above
>>>e3=np.eye(4,k=1) # 1 start from k
>>>f=np.arange(1,20,3,float) # from 1 to 19 interval 3
>>>g=np.array([[2,2,2],[3,3,3]]) # 2 by 3
>>>h=np.zeros_like(g) # all zeros
>>>i=np.ones_like(g) # all ones
一些所谓的 dot
函数非常方便和有用:
>>> import numpy as np
>>> x=np.array([10,20,30])
>>>x.sum()
60
任何在 #
符号后面的内容都是注释。数组是另一种重要的数据类型:
>>>import numpy as np
>>>x=np.array([[1,2],[5,6],[7,9]]) # a 3 by 2 array
>>>y=x.flatten()
>>>x2=np.reshape(y,[2,3] ) # a 2 by 3 array
我们可以将字符串赋值给变量:
>>> t="This is great"
>>>t.upper()
'THIS IS GREAT'
>>>
为了找出所有与字符串相关的函数,我们使用 dir('')
;见以下代码:
>>>dir('')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>>
例如,从前面的列表中,我们看到一个名为 split
的函数。输入 help(''.split)
后,我们将得到相关的帮助信息:
>>>help(''.split)
Help on built-in function split:
split(...) method of builtins.str instance
S.split(sep=None, maxsplit=-1) -> list of strings
Return a list of the words in S, using sep as the
delimiter string. If maxsplit is given, at most maxsplit
splits are done. If sep is not specified or is None, any
whitespace string is a separator and empty strings are
removed from the result.
>>>
我们可以尝试以下示例:
>>> x="this is great"
>>>x.split()
['this', 'is', 'great']
>>>
矩阵操作在我们处理各种矩阵时非常重要:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_22.jpg
方程(3)的条件是矩阵 A 和 B 应该有相同的维度。对于两个矩阵的乘积,我们有以下方程:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_23.jpg
这里,A 是一个 n 行 k 列的矩阵(n 行和 k 列),而 B 是一个 k 行 m 列的矩阵。记住,第一个矩阵的第二维度应该与第二个矩阵的第一维度相同。在这种情况下,它是 k。如果我们假设 C、A 和 B 中的各个数据项分别为 Ci,j(第 i 行和第 j 列)、Ai,j 和 Bi,j,我们可以得到它们之间的关系:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_24.jpg
可以使用 NumPy 模块中的 dot()
函数进行上述矩阵乘法:
>>>a=np.array([[1,2,3],[4,5,6]],float) # 2 by 3
>>>b=np.array([[1,2],[3,3],[4,5]],float) # 3 by 2
>>>np.dot(a,b) # 2 by 2
>>>print(np.dot(a,b))
array([[ 19., 23.],
[ 43., 53.]])
>>>
我们可以手动计算 c(1,1): 11 + 23 + 34=19*。
在获取数据或从互联网下载数据后,我们需要处理这些数据。这种处理各种类型原始数据的技能对于金融学学生和从事金融行业的专业人士来说至关重要。这里我们将展示如何下载价格数据并估算收益率。
假设我们有 n 个 x1、x2、… 和 xn 的值。存在两种类型的均值:算术均值和几何均值;请参阅它们的基因定义:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_25.jpghttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_26.jpg
假设存在三个值 2
、3
和 4
。它们的算术均值和几何均值在此计算:
>>>(2+3+4)/3.
>>>3.0
>>>geo_mean=(2*3*4)**(1./3)
>>>round(geo_mean,4)
2.8845
对于收益率,算术平均数的定义保持不变,而几何平均数的定义则不同;请参阅以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_27.jpghttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_28.jpg
在第三章中,我们将再次讨论这两种均值。
我们可以说,NumPy 是一个基础模块,而 SciPy 是一个更高级的模块。NumPy 尝试保留其前辈所支持的所有特性,而大多数新特性属于 SciPy,而不是 NumPy。另一方面,NumPy 和 SciPy 在金融功能方面有许多重叠的特性。关于这两种定义,请参阅以下示例:
>>> import scipy as sp
>>> ret=sp.array([0.1,0.05,-0.02])
>>>sp.mean(ret)
0.043333333333333342
>>>pow(sp.prod(ret+1),1./len(ret))-1
0.042163887067679262
我们的第二个示例与处理 Fama-French 三因子时间序列相关。由于此示例比前一个更复杂,如果用户觉得难以理解,可以简单跳过此示例。首先,可以从 French 教授的数据库下载名为 F-F_Research_Data_Factor_TXT.zip
的 ZIP 文件。解压后,去除前几行和年度数据集,我们将得到一个月度的 Fama-French 因子时间序列。这里展示了前几行和后几行:
DATE MKT_RFSMBHMLRF
192607 2.96 -2.30 -2.87 0.22
192608 2.64 -1.40 4.19 0.25
192609 0.36 -1.32 0.01 0.23
201607 3.95 2.90 -0.98 0.02
201608 0.49 0.94 3.18 0.02
201609 0.25 2.00 -1.34 0.02
假设最终文件名为 ffMonthly.txt
,位于 c:/temp/
目录下。以下程序用于检索和处理数据:
import numpy as np
import pandas as pd
file=open("c:/temp/ffMonthly.txt","r")
data=file.readlines()
f=[]
index=[]
for i in range(1,np.size(data)):
t=data[i].split()
index.append(int(t[0]))
for j in range(1,5):
k=float(t[j])
f.append(k/100)
n=len(f)
f1=np.reshape(f,[n/4,4])
ff=pd.DataFrame(f1,index=index,columns=['Mkt_Rf','SMB','HML','Rf'])
要查看名为 ff
的数据集的前几行和后几行观察值,可以使用 .head()
和 .tail()
函数:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_29.jpg
数据输出
最简单的示例如下:
>>>f=open("c:/temp/out.txt","w")
>>>x="This is great"
>>>f.write(x)
>>>f.close()
对于下一个示例,我们首先下载历史股票价格数据,然后将数据写入输出文件:
import re
from matplotlib.finance import quotes_historical_yahoo_ochl
ticker='dell'
outfile=open("c:/temp/dell.txt","w")
begdate=(2013,1,1)
enddate=(2016,11,9)
p=quotes_historical_yahoo_ochl
(ticker,begdate,enddate,asobject=True,adjusted=True)
outfile.write(str(p))
outfile.close()
为了检索文件,我们有以下代码:
>>>infile=open("c:/temp/dell.txt","r")
>>>x=infile.read()
一个问题是,前面保存的文本文件包含了许多不必要的字符,比如 [
和 ]
。我们可以应用一个名为 sub()
的替换函数,它包含在 Python 模块中;请参见这里给出的最简单示例:
>>> import re
>>>re.sub("a","9","abc")
>>>
'9bc'
>>>
在前面的示例中,我们将字母 a
替换为 9
。感兴趣的读者可以尝试以下两行代码来运行前面的程序:
p2= re.sub('[\(\)\{\}\.<>a-zA-Z]','', p)
outfile.write(p2)
使用扩展名 .pickle
生成 Python 数据集是一个好主意,因为我们可以高效地检索此类数据。以下是生成 ffMonthly.pickle
的完整 Python 代码。在这里,我们展示了如何下载价格数据并估算收益:
import numpy as np
import pandas as pd
file=open("c:/temp/ffMonthly.txt","r")
data=file.readlines()
f=[]
index=[]
for i in range(1,np.size(data)):
t=data[i].split()
index.append(int(t[0]))
for j in range(1,5):
k=float(t[j])
f.append(k/100)
n=len(f)
f1=np.reshape(f,[n/4,4])
ff=pd.DataFrame(f1,index=index,columns=['Mkt_Rf','SMB','HML','Rf'])
ff.to_pickle("c:/temp/ffMonthly.pickle")
练习
-
你可以在哪里下载并安装 Python?
-
Python 是否区分大小写?
-
如何将一组值以元组的形式赋值给 pv?在赋值之后,我们可以更改其值吗?
-
如果直径为 9.7,使用 Python 估算圆的面积。
-
如何为一个新变量赋值?
-
如何找到与 Python 相关的一些示例代码?
-
如何启动 Python 的帮助函数?
-
如何获得更多关于某个特定函数的信息,例如
print()
? -
内置函数的定义是什么?
-
pow()
是一个内置函数吗?我们该如何使用它? -
如何找到所有内置函数?内置函数共有多少个?
-
当我们估算 3 的平方根时,应该使用哪个 Python 函数?
-
假设永久年金的现值为 124 美元,年现金流为 50 美元;那么相应的贴现率是多少?公式如下:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_30.jpg
-
根据上一个问题的解答,什么是相应的季度利率?
-
对于永久年金,现金流在相同的时间间隔内永远发生。增长的永久年金定义如下:未来的现金流将永远以固定的增长率增长。如果第一个现金流发生在第一个期间结束时,我们有以下公式:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_31.jpg
在这里,PV 是现值,C 是下一个期间的现金流,g 是增长率,R 是贴现率。如果第一个现金流为 12.50 美元,常数增长率为 2.5%,贴现率为 8.5%,那么这个持续增长的永久年金的现值是多少?
-
对于 n 天的方差,我们有以下公式:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_32.jpg
这里 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_36.jpg 是日波动率,! Exercises 是日标准差(波动率)。如果某只股票的波动率(每日标准差)为 0.2,那么它的 10 天波动率是多少?
-
我们预计在 5 年内将有 25,000 美元。如果年存款利率为 4.5%,我们今天需要存入多少金额?
-
这个名为
sub()
的替换函数来自一个 Python 模块。找出该模块包含了多少个函数。 -
编写一个 Python 程序,通过使用以下公式,将基于日数据或月数据估算的标准差转换为年标准差:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_33.jpghttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_34.jpg
-
夏普比率是衡量投资(如投资组合)在收益(超额收益)与成本(总风险)之间权衡的一种指标。编写一个 Python 程序,通过以下公式估算夏普比率:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_35.jpg
这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_38.jpg是投资组合的平均收益,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_01_39.jpg是无风险利率的平均值,而σ是投资组合的风险。同样,在此时,读者不理解该比率的经济含义是完全可以接受的,因为夏普比率将在第七章,多因子模型与绩效衡量中进行更详细的讨论。
总结
在本章中,我们讨论了许多与 Python 相关的基本概念和几个广泛使用的函数。在第二章,Python 模块介绍中,我们将讨论 Python 语言的一个关键组成部分:Python 模块及其相关问题。模块是由专家、专业人士或任何围绕特定主题的人编写的一组程序。模块可以视为完成特定任务的工具箱。本章将重点介绍五个最重要的模块:NumPy、SciPy、matplotlib
、statsmodels
和pandas
。
第二章:Python 模块简介
在本章中,我们将讨论与 Python 模块相关的最重要问题,这些模块是由专家或任何个人编写的,用于特定目的。在本书中,我们将使用大约十几个模块。因此,模块相关的知识对于我们理解 Python 及其在金融中的应用至关重要。特别地,在本章中,我们将涵盖以下主题:
Python 模块简介
NumPy 简介
SciPy 简介
matplotlib
简介
statsmodels
简介
pandas 简介
与金融相关的 Python 模块
pandas_reader 模块简介
用 Python 编写的两个金融计算器
如何安装 Python 模块
模块依赖关系
什么是 Python 模块?
模块是由专家、用户甚至是初学者编写的一组程序或软件包,通常这些人精通某个特定领域,且为了特定的目的而编写这些程序。
例如,一个名为 quant 的 Python 模块用于定量金融分析。quant 结合了 SciPy 和DomainModel
两个模块。该模块包含一个领域模型,其中有交易所、符号、市场和历史价格等内容。模块在 Python 中非常重要。在本书中,我们将或多或少地讨论十几个模块。特别地,我们将详细讲解五个模块:NumPy、SciPy、matplotlib
、statsmodels
和 Pandas。
注意
截至 2016 年 11 月 16 日,Python 包索引中有 92,872 个不同领域的 Python 模块(包)。
对于金融和保险行业,目前有 384 个模块可供使用。
假设我们想使用sqrt()
函数估算3
的平方根。然而,在执行以下代码后,我们将遇到错误消息:
>>>sqrt(3)
SyntaxError: invalid syntax
>>>
原因在于sqrt()
函数不是内置函数。内置函数可以视为在 Python 启动时就已存在的函数。要使用sqrt()
函数,我们需要先导入 math 模块,如下所示:
>>>import math
>>>x=math.sqrt(3)
>>>round(x,4)
1.7321
要使用sqrt()
函数,如果我们使用import math
命令导入 math 模块,则必须输入math.sqrt()
。在前面的代码中,round()
函数用于控制小数位数。此外,在执行dir()
命令后,我们将看到 math 模块的存在,它是此处输出中的最后一个:
>>>dir()
['__builtins__', '__doc__', '__name__', '__package__', 'math']
此外,当一个模块是预安装时,我们可以使用import x_module
来上传它。例如,math 模块是预安装的。在本章后面,我们将看到如何查找所有内置模块。在前面的输出中,发出dir()
命令后,我们还观察到__builtins__
。在builtin
前后都有两个下划线。这个__builtins__
模块不同于其他内置模块,例如math
模块。它包含所有内置函数和其他对象。再次发出dir(__builtins__)
命令可以列出所有内置函数,如以下代码所示:
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'debugfile', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'evalsc', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'open_in_spyder', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'runfile', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
从前面的输出中,我们发现一个名为pow()
的函数。可以使用help(pow)
命令来查找有关这个特定函数的更多信息;见下文:
>>> help(pow)
Help on built-in function pow in module builtins:
pow(x, y, z=None, /)
Equivalent to x**y (with two arguments) or x**y % z
(with three arguments)
Some types, such as ints, are able to use a more
efficient algorithm when invoked using the three argument form.
>> >
为了方便,建议为导入的模块采用简短名称。为了在编程时减少一些输入工作量,我们可以使用import x_module as short_name
命令,如以下代码所示:
>>>import sys as s
>>>import time as tt
>>>import numpy as np
>>>import matplotlib as mp
在调用导入模块中包含的特定函数时,我们使用模块的简短名称,如以下代码所示:
>>> import time as tt
>>> tt.localtime()
time.struct_time(tm_year=2016, tm_mon=11, tm_mday=21, tm_hour=10, tm_min=58, tm_sec=33, tm_wday=0, tm_yday=326, tm_isdst=0)
>>>
虽然用户可以自由选择任何简短的名称来导入模块,但遵循一些约定是个不错的主意,例如使用np
表示 NumPy,使用sp
表示 SciPy。使用这些常用的简短名称的一个额外好处是能使我们的程序对他人更具可读性。要显示导入模块中的所有函数,可以使用dir(module)
命令,如以下代码所示:
>>>import math
>>>dir(math)
['__doc__', '__loader__', '__name__', '__package__', 'acos', 'acosh',
'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos',
'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs',
'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot',
'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
>>>
回想一下在第一章,Python 基础中,比较了import math
和from math import *
。一般来说,为了简化程序,可以使用from math import *
。这对于刚开始学习 Python 编程的初学者尤其适用。让我们看一下以下代码:
>>>from math import *
>>>sqrt(3)
1.7320508075688772
现在,模块中包含的所有函数将可以直接使用。另一方面,如果我们使用import math
,我们必须将模块名作为前缀,例如math.sqrt()
而不是sqrt()
。熟悉 Python 后,建议使用导入模块的格式,而不是使用from module import *
。这种偏好背后有两个原因:
首先,用户可以准确知道函数来自哪个模块。
其次,我们可能已经编写了一个与另一个模块中函数同名的函数。模块名加在函数前面可以将其与我们自己的函数区分开,如以下代码所示:
>>>import math
>>>math.sqrt(3)
1.7320508075688772
del()
函数用于移除一个被认为不再需要的导入/上传模块,如以下代码所示:
>>>import math
>>>dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', 'math']
>>>del math
>>>dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__']
另一方面,如果我们使用from math import *
,我们不能通过del math
来移除所有函数。必须单独移除这些函数。以下两个命令演示了这种效果:
>>>from math import *
>>>del math
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
del math NameError: name 'math' is not defined
为了方便,我们可以只导入几个需要的函数。为了定价一个欧洲看涨期权,需要几个函数,比如log()
、exp()
、sqrt()
和cdf()
。cdf()
是累积标准正态分布函数。为了使这四个函数可用,我们指定它们的名称,如下代码所示:
From scipy import log,exp,sqrt,stats
这里给出了定价 Black-Scholes-Merton 看涨期权的完整代码:
def bsCall(S,X,T,r,sigma):
from scipy import log,exp,sqrt,stats
d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
d2 = d1-sigma*sqrt(T)
return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
这里给出了调用bsCall
函数的一个示例:
>>> bsCall(40,40,0.1,0.05,0.2)
1.1094616585675574
要查找所有可用模块,首先应激活帮助窗口。之后,输入modules
命令。结果如下所示:
>>> help()
>>>
Welcome to Python 3.5's help utility!
如果这是你第一次使用 Python,应该一定访问互联网上的教程:docs.python.org/3.5/tutorial/
。
输入任何模块、关键字或主题的名称,即可获得关于编写 Python 程序和使用 Python 模块的帮助。要退出该帮助工具并返回解释器,只需输入quit
。
要获取可用模块、关键字、符号或主题的列表,输入modules
、keywords
、symbols
或topics
。每个模块还附有一行总结其功能;要列出名称或总结包含给定字符串(如spam
)的模块,输入modules spam
:
help>
然后,我们在 Python 的help>
提示符下输入modules
,如下截图所示(为节省空间,这里仅展示了前部分):
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_01.jpg
要查找特定模块,我们只需输入modules
后跟模块名称。假设我们对名为cmd
的模块感兴趣,那么我们在帮助窗口中输入modules cmd
;请参见以下截图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_02.jpg
要获得更多有关模块的信息,请导航至所有程序 | Python 3.5
| Python 3.5 Module Docs
,如下截图所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_03.jpg
点击Python 3.5 Module Docs (32-bit)
后,我们将获得更多信息。
NumPy 简介
在以下示例中,NumPy 中的np.size()
函数显示了数组的数据项数量,np.std()
函数用于计算标准差:
>>>import numpy as np
>>>x= np.array([[1,2,3],[3,4,6]]) # 2 by 3 matrix
>>>np.size(x) # number of data items
6
>>>np.size(x,1) # show number of columns
3
>>>np.std(x)
1.5723301886761005
>>>np.std(x,1)
Array([ 0.81649658, 1.24721913]
>>>total=x.sum() # attention to the format
>>>z=np.random.rand(50) #50 random obs from [0.0, 1)
>>>y=np.random.normal(size=100) # from standard normal
>>>r=np.array(range(0,100),float)/100 # from 0, .01,to .99
与 Python 数组相比,NumPy 数组是一个连续的内存块,直接传递给 LAPACK,这是一个用于数值线性代数的底层软件库,因此在 Python 中矩阵操作非常快速。NumPy 中的数组就像 MATLAB 中的矩阵。与 Python 中的列表不同,数组应该包含相同的数据类型,如下代码所示:
>>>np.array([100,0.1,2],float)
实际数据类型是float64
,数值的默认类型也是float64
。
在前面的示例中,我们可以看到np.array()
函数将一个相同数据类型的列表(此例中为整数)转换为数组。要更改数据类型,应使用第二个输入值dtype
进行指定,如下代码所示:
>>>x=[1,2,3,20]
>>>y=np.array(x1,dtype=float)
>>>y
array([ 1., 2., 3., 20.])
在前一个示例中,dtype
是指定数据类型的关键字。对于列表,不同的数据类型可以共存而不会引发问题。然而,当将包含不同数据类型的列表转换为数组时,会出现错误信息,如以下代码所示:
>>>x2=[1,2,3,"good"]
>>>x2
[1, 2, 3, 'good']
>>>y3=np.array(x2,float)
Traceback (most recent call last):
File "<pyshell#25>", line 1, in <module>
y3=np.array(x2,float)
ValueError: could not convert string to float: 'good'
. ]])
为了显示 Numpy 中包含的所有函数,在导入 Numpy 模块后使用dir(np)
。
以下显示的是前几行代码:
>>> import numpy as np
>>> dir(np)
['ALLOW_THREADS', 'BUFSIZE', 'CLIP', 'ComplexWarning', 'DataSource', 'ERR_CALL', 'ERR_DEFAULT', 'ERR_IGNORE', 'ERR_LOG', 'ERR_PRINT', 'ERR_RAISE', 'ERR_WARN', 'FLOATING_POINT_SUPPORT', 'FPE_DIVIDEBYZERO', 'FPE_INVALID', 'FPE_OVERFLOW', 'FPE_UNDERFLOW', 'False_', 'Inf', 'Infinity', 'MAXDIMS', 'MAY_SHARE_BOUNDS', 'MAY_SHARE_EXACT', 'MachAr', 'ModuleDeprecationWarning', 'NAN', 'NINF', 'NZERO', 'NaN', 'PINF', 'PZERO', 'PackageLoader', 'RAISE', 'RankWarning', 'SHIFT_DIVIDEBYZERO', 'SHIFT_INVALID', 'SHIFT_OVERFLOW', 'SHIFT_UNDERFLOW', 'ScalarType', 'Tester', 'TooHardError', 'True_', 'UFUNC_BUFSIZE_DEFAULT', 'UFUNC_PYVALS_NAME', 'VisibleDeprecationWarning', 'WRAP', '_NoValue', '__NUMPY_SETUP__', '__all__', '__builtins__', '__cached__', '__config__', '__doc__', '__file__', '__git_revision__', '__loader__', '__mkl_version__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_import_tools', '_mat', 'abs', 'absolute', 'absolute_import', 'add', 'add_docstring', 'add_newdoc', 'add_newdoc_ufunc', 'add_newdocs', 'alen', 'all', 'allclose', 'alltrue', 'alterdot', 'amax', 'amin', 'angle', 'any', 'append', 'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin', 'argpartition', 'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal', 'array_equiv', 'array_repr', 'array_split', 'array_str', 'asanyarray',
实际上,更好的方法是生成一个包含所有函数的数组,如下所示:
>>> x=np.array(dir(np))
>>> len(x)
598
要显示200
到250
的函数,可以输入x[200:250]
;请参见以下代码:
>>> x[200:250]
array(['disp', 'divide', 'division', 'dot', 'double', 'dsplit', 'dstack',
'dtype', 'e', 'ediff1d', 'einsum', 'emath', 'empty', 'empty_like',
'equal', 'errstate', 'euler_gamma', 'exp', 'exp2', 'expand_dims',
'expm1', 'extract', 'eye', 'fabs', 'fastCopyAndTranspose', 'fft',
'fill_diagonal', 'find_common_type', 'finfo', 'fix', 'flatiter',
'flatnonzero', 'flexible', 'fliplr', 'flipud', 'float', 'float16',
'float32', 'float64', 'float_', 'floating', 'floor', 'floor_divide',
'fmax', 'fmin', 'fmod', 'format_parser', 'frexp', 'frombuffer',
'fromfile'],
dtype='<U25')
>> >
查找特定函数的更多信息非常简单。执行dir(np)
后,std()
函数等将会出现。要获取该函数的更多信息,可以使用help(np.std)
。为了简洁起见,以下仅显示部分代码:
>>>import numpy as np
>>>help(np.std)
Help on function std in module numpy.core.fromnumeric:
std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False)
Compute the standard deviation along the specified axis.
该函数返回标准差,它是衡量分布离散程度的指标,表示数组元素的标准差。默认情况下,标准差是针对展平后的数组计算的,或者根据指定的轴进行计算:
Parameters
----------
a : array_like
Calculate the standard deviation of these values.
axis : None or int or tuple of ints, optional
Axis or axes along which the standard deviation is computed. The
default is to compute the standard deviation of the flattened array.
.. versionadded: 1.7.0
SciPy 简介
以下是几个基于 SciPy 模块中函数的示例。sp.npv()
函数估算给定现金流的现值,第一个现金流发生在时间零。第一个输入值是贴现率,第二个输入值是所有现金流的数组。
以下是一个示例。请注意,sp.npv()
函数与 Excel 中的npv()
函数不同。我们将在第三章中详细解释原因,货币的时间价值:
>>>import scipy as sp
>>>cashflows=[-100,50,40,20,10,50]
>>>x=sp.npv(0.1,cashflows)
>>>round(x,2)
>>>31.41
sp.pmt()
函数用于解答以下问题。
每月现金流是多少,用于偿还一笔$250,000 的抵押贷款,贷款期为 30 年,年利率(APR)为 4.5%,按月复利?以下代码显示了答案:
>>>payment=sp.pmt(0.045/12,30*12,250000)
>>>round(payment,2)
-1266.71
基于前面的结果,每月支付金额为$1,266.71。出现负值可能让人感到奇怪。实际上,sp.pmt()
函数模拟了 Excel 中的等效函数,正如我们在以下截图中看到的那样:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_04.jpg
输入值包括:有效期利率、期数和现值。顺便提一下,括号中的数字表示负数。
此时,暂时忽略负号。在第三章,货币的时间价值中,将更详细讨论这一所谓的 Excel 约定。
类似地,sp.pv()
函数复制了 Excel 的 PV()
函数。对于 sp.pv()
函数,其输入格式为 sp.pv(rate, nper, pmt, fv=0.0, when='end')
,其中 rate
是折现率,nper
是期数,pmt
是期支付额,fv
是未来值,默认为零。最后一个输入变量指定现金流是在每个时间段的末尾还是开始时进行。默认为每期末尾。以下命令演示了如何调用此函数:
>>>pv1=sp.pv(0.1,5,0,100) # pv of one future cash flow
>>>round(pv1,2)
-92.09
>>>pv2=sp.pv(0.1,5,100) # pv of annuity
>>>round(pv2,2)
-379.08
sp.fv()
函数的设置类似于 sp.pv()
。在金融领域,我们估算算术平均值和几何平均值,定义在以下公式中。
对于 n 个 x 数字,即 x1、x2、x3 和 xn,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_05.jpghttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_06.jpg
这里,https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_26.jpg 和 https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_27.jpg。假设我们有三个数字 a、b 和 c,那么它们的算术平均值是 (a+b+c)/3,而它们的几何平均值是 (abc)^(1/3)。对于 2
、3
和 4
三个值,我们得到以下两种均值:
>>>(2+3+4)/3.
>>>3.0
>>>geo_mean=(2*3*4)**(1./3)
>>>round(geo_mean,4)
2.8845
如果给定 n 个回报,估算其算术平均值的公式保持不变。然而,回报的几何平均值公式不同,如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_07.jpghttps://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_08.jpg
要估算几何平均值,可以使用 sp.prod()
函数。该函数为我们提供所有数据项的乘积;请参见以下代码:
>>>import scipy as sp
>>>ret=sp.array([0.1,0.05,-0.02])
>>>sp.mean(ret) # arithmetic mean
0.04333
>>>pow(sp.prod(ret+1),1./len(ret))-1 # geometric mean
0.04216
实际上,可以通过仅写两行简单的 Python 函数来计算一组给定回报的几何平均值;请参见以下代码:
def geoMeanReturn(ret):
return pow(sp.prod(ret+1),1./len(ret))-1
调用前面的函数非常简单;请参见以下代码:
>>> import scipy as sp
>>> ret=sp.array([0.1,0.05,-0.02])
>>> geoMeanReturn(ret)
0.042163887067679262
另外两个有用的函数是 sp.unique()
和 sp.median()
,如以下代码所示:
>>>sp.unique([2,3,4,6,6,4,4])
Array([2,3,4,6])
>>>sp.median([1,2,3,4,5])
3.0
Python 的 sp.pv()
、sp.fv()
和 sp.pmt()
函数分别与 Excel 的 pv()
、fv()
和 pmt()
函数行为相似。它们具有相同的符号约定:现值的符号与未来值相反。
在以下示例中,假设我们输入一个正的未来值来估算现值,最终会得到一个负的现值:
>>>import scipy as sp
>>>round(sp.pv(0.1,5,0,100),2)
>>>-62.09
>>>round(sp.pv(0.1,5,0,-100),2)
>>>62.09
有多种方法可以查找 SciPy 模块中包含的所有函数。
首先,我们可以阅读相关手册。其次,我们可以发出以下代码行:
>>>import numpy as np
>>>dir(np)
为了节省空间,以下代码仅显示部分输出:
>>> import scipy as sp
>>> dir(sp)
'ALLOW_THREADS', 'BUFSIZE', 'CLIP', 'ComplexWarning', 'DataSource', 'ERR_CALL', 'ERR_DEFAULT', 'ERR_IGNORE', 'ERR_LOG', 'ERR_PRINT', 'ERR_RAISE', 'ERR_WARN', 'FLOATING_POINT_SUPPORT', 'FPE_DIVIDEBYZERO', 'FPE_INVALID', 'FPE_OVERFLOW', 'FPE_UNDERFLOW', 'False_', 'Inf', 'Infinity', 'MAXDIMS', 'MAY_SHARE_BOUNDS', 'MAY_SHARE_EXACT', 'MachAr', 'ModuleDeprecationWarning', 'NAN', 'NINF', 'NZERO', 'NaN', 'PINF', 'PZERO', 'PackageLoader', 'RAISE', 'RankWarning', 'SHIFT_DIVIDEBYZERO', 'SHIFT_INVALID', 'SHIFT_OVERFLOW', 'SHIFT_UNDERFLOW', 'ScalarType', 'Tester', 'TooHardError', 'True_', 'UFUNC_BUFSIZE_DEFAULT', 'UFUNC_PYVALS_NAME', 'VisibleDeprecationWarning', 'WRAP', '__SCIPY_SETUP__', '__all__', '__builtins__', '__cached__', '__config__', '__doc__', '__file__', '__loader__', '__name__', '__numpy_version__', '__package__', '__path__', '__spec__', '__version__', '_lib', 'absolute', 'absolute_import', 'add', 'add_docstring', 'add_newdoc', 'add_newdoc_ufunc', 'add_newdocs', 'alen', 'all', 'allclose', 'alltrue', 'alterdot', 'amax', 'amin', 'angle', 'any', 'append', 'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin', 'argpartition', 'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal', 'array_equiv', 'array_repr', 'array_split', 'array_str', 'asanyarray', 'asarray', 'asarray_chkfinite', 'ascontiguousarray', 'asfarray', 'asfortranarray', 'asmatrix', 'asscalar', 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett',
类似地,我们可以将所有函数保存到一个向量(数组)中;请参见以下代码:
>>>import scipy as sp
>>> x=dir(sp)
>>> len(x)
588
>>>
matplotlib 介绍
图表和其他可视化表示在解释许多复杂的金融概念、交易策略和公式中变得更加重要。
在这一部分,我们讨论了 matplotlib
模块,它用于创建各种类型的图形。此外,模块将在 [第十章,期权与期货 中得到广泛应用,当时我们将讨论著名的 Black-Scholes-Merton 期权模型以及各种交易策略。matplotlib
模块旨在生成出版质量的图形和图表。matplotlib
模块依赖于 NumPy 和 SciPy,这些在前面的章节中已经讨论过。为了保存生成的图形,有多种输出格式可供选择,如 PDF、Postscript、SVG 和 PNG。
如何安装 matplotlib
如果 Python 是通过 Anaconda 超级包安装的,那么 matplotlib
已经预先安装好了。启动 Spyder 后,输入以下命令进行测试。如果没有错误,说明我们已经成功导入/上传了该模块。这就是使用像 Anaconda 这样超级包的好处:
>>> import matplotlib
若要单独安装 matplotlib
模块或其他模块,请参见 模块依赖 – 如何安装模块 部分。
使用 matplotlib 的几种图形展示
理解 matplotlib
模块的最佳方式是通过示例。以下示例可能是最简单的,因为它仅包含三行 Python 代码。目标是连接几个点。默认情况下,matplotlib
模块假设 x 轴从零开始,并且数组的每个元素增加 1。
以下命令行截图说明了这一情况:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_09.jpg
在输入最后一个命令 show()
并按下 Enter 键后,上图右侧的图形将出现。在图形顶部,有一组图标(功能)可供选择。点击它们,我们可以调整图像或保存图像。关闭上述图形后,我们可以返回到 Python 提示符。另一方面,如果我们第二次输入 show()
,则什么也不会发生。要重新显示上面的图形,我们必须同时输入 plot([1,2,3,9])
和 show()
。可以为 x 轴和 y 轴添加两个标签,如下所示。
相应的图形显示在右侧的以下截图中:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_10.jpg
下一个例子展示了两个余弦函数:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_11.jpg
在上述代码中,linspace()
函数有四个输入值:start
、stop
、num
和 endpoint
。在前面的示例中,我们将从 -3.1415916 开始,到 3.1415926 结束,中间有 256 个值。此外,端点将被包括在内。顺便提一下,num
的默认值是 50。以下示例显示了散点图。首先,使用 np.random.normal()
函数生成两组随机数。由于 n
为 1024
,所以 X
和 Y
变量都有 1,024 个观测值。关键函数是 scatter(X,Y)
,如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_12.jpg
这是一个更复杂的图,展示了股票的波动。我们先看看代码:
import datetime
import matplotlib.pyplot as plt
from matplotlib.finance import quotes_historical_yahoo_ochl
from matplotlib.dates import MonthLocator,DateFormatter
ticker='AAPL'
begdate= datetime.date( 2012, 1, 2 )
enddate = datetime.date( 2013, 12,5)
months = MonthLocator(range(1,13), bymonthday=1, interval=3) # every 3rd month
monthsFmt = DateFormatter("%b '%Y")
x = quotes_historical_yahoo_ochl(ticker, begdate, enddate)
if len(x) == 0:
print ('Found no quotes')
raise SystemExit
dates = [q[0] for q in x]
closes = [q[4] for q in x]
fig, ax = plt.subplots()
ax.plot_date(dates, closes, '-')
ax.xaxis.set_major_locator(months)
ax.xaxis.set_major_formatter(monthsFmt)
ax.xaxis.set_minor_locator(mondays)
ax.autoscale_view()
ax.grid(True)
fig.autofmt_xdate()
相应的图表如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_13.jpg
介绍 statsmodels
statsmodels
是一个强大的 Python 包,适用于多种类型的统计分析。同样,如果通过 Anaconda 安装了 Python,那么该模块也会随之安装。在统计学中,普通最小二乘法 (OLS) 回归是一种估计线性回归模型中未知参数的方法。它通过最小化观测值与线性近似预测值之间的垂直距离的平方和来进行优化。OLS 方法在金融领域被广泛使用。假设我们有如下方程,其中 y 是一个 n 行 1 列的向量(数组),x 是一个 n 行 (m+1) 列的矩阵,表示回报矩阵(n 行 m 列),加上一个仅包含 1 的向量。n 是观测值的数量,m 是独立变量的数量:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_14.jpg
在以下程序中,生成 x
和 y
向量后,我们运行一个 OLS 回归(线性回归)。x
和 y
是人工数据。最后一行只打印参数(截距为 1.28571420
,斜率为 0.35714286
):
>>> import numpy as np
>>> import statsmodels.api as sm
>>> y=[1,2,3,4,2,3,4]
>>> x=range(1,8)
>>> x=sm.add_constant(x)
>>> results=sm.OLS(y,x).fit()
>>> print(results.params)
[ 1.28571429 0.35714286]
若要了解有关此模块的更多信息,可以使用 dir()
函数:
>>> import statsmodels as sm
>>> dir(sm)
['CacheWriteWarning', 'ConvergenceWarning', 'InvalidTestWarning', 'IterationLimitWarning', 'NoseWrapper', 'Tester', '__builtins__', '__cached__', '__doc__', '__docformat__', '__file__', '__init__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'api', 'base', 'compat', 'datasets', 'discrete', 'distributions', 'duration', 'emplike', 'errstate', 'formula', 'genmod', 'graphics', 'info', 'iolib', 'nonparametric', 'print_function', 'regression', 'robust', 'sandbox', 'simplefilter', 'stats', 'test', 'tools', 'tsa', 'version']
对于各种子模块,也可以使用 dir()
;请参见这里的示例:
>>> import statsmodels.api as api
>>> dir(api)
['Categorical', 'CategoricalIndex', 'DataFrame', 'DateOffset', 'DatetimeIndex', 'ExcelFile', 'ExcelWriter', 'Expr', 'Float64Index', 'Grouper', 'HDFStore', 'Index', 'IndexSlice', 'Int64Index', 'MultiIndex', 'NaT', 'Panel', 'Panel4D', 'Period', 'PeriodIndex', 'RangeIndex', 'Series', 'SparseArray', 'SparseDataFrame', 'SparseList', 'SparsePanel', 'SparseSeries', 'SparseTimeSeries', 'Term', 'TimeGrouper', 'TimeSeries', 'Timedelta', 'TimedeltaIndex', 'Timestamp', 'WidePanel', '__builtins__', '__cached__', '__doc__', '__docformat__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_np_version_under1p10', '_np_version_under1p11', '_np_version_under1p12', '_np_version_under1p8', '_np_version_under1p9', '_period', '_sparse', '_testing', '_version', 'algos', 'bdate_range', 'compat', 'computation', 'concat', 'core', 'crosstab', 'cut', 'date_range', 'datetime', 'datetools', 'dependency', 'describe_option', 'eval', 'ewma', 'ewmcorr', 'ewmcov', 'ewmstd', 'ewmvar', 'ewmvol', 'expanding_apply', 'expanding_corr', 'expanding_count', 'expanding_cov', 'expanding_kurt', 'expanding_max', 'expanding_mean', 'expanding_median', 'expanding_min', 'expanding_quantile', 'expanding_skew', 'expanding_std', 'expanding_sum', 'expanding_var', 'factorize', 'fama_macbeth', 'formats', 'get_dummies', 'get_option', 'get_store', 'groupby', 'hard_dependencies', 'hashtable', 'index', 'indexes', 'infer_freq', 'info', 'io', 'isnull', 'json', 'lib', 'lreshape', 'match', 'melt', 'merge', 'missing_dependencies', 'msgpack', 'notnull', 'np', 'offsets', 'ols', 'option_context', 'options', 'ordered_merge', 'pandas', 'parser', 'period_range', 'pivot', 'pivot_table', 'plot_params', 'pnow', 'qcut', 'read_clipboard', 'read_csv', 'read_excel', 'read_fwf', 'read_gbq', 'read_hdf', 'read_html', 'read_json', 'read_msgpack', 'read_pickle', 'read_sas', 'read_sql', 'read_sql_query', 'read_sql_table', 'read_stata', 'read_table', 'reset_option', 'rolling_apply', 'rolling_corr', 'rolling_count', 'rolling_cov', 'rolling_kurt', 'rolling_max', 'rolling_mean', 'rolling_median', 'rolling_min', 'rolling_quantile', 'rolling_skew', 'rolling_std', 'rolling_sum', 'rolling_var', 'rolling_window', 'scatter_matrix', 'set_eng_float_format', 'set_option', 'show_versions', 'sparse', 'stats', 'test', 'timedelta_range', 'to_datetime', 'to_msgpack', 'to_numeric', 'to_pickle', 'to_timedelta', 'tools', 'tseries', 'tslib', 'types', 'unique', 'util', 'value_counts', 'wide_to_long']
从前面的输出可以看出,有 16 个函数以 read
开头;请参见下表:
名称 | 描述 |
---|---|
read_clipboard |
从剪贴板输入数据 |
read_csv |
从 CSV(逗号分隔值)输入数据 |
read_excel |
从 Excel 文件输入数据 |
read_fwf |
输入定宽数据 |
read_gbq |
从 Google BigQuery 加载数据 |
read_hdf |
读取 HDF5 格式的数据 |
read_html |
从网页输入数据 |
read_json |
读取 JSON(JavaScript 对象表示法)数据 |
read_msgpack |
MessagePack 是一种快速、紧凑的二进制序列化格式,适用于类似 JSON 的数据 |
read_pickle |
输入一个 Python 数据集,称为 pickle |
read_sas |
从 SAS 数据集输入数据 |
read_sql |
从 SQL 数据库输入数据 |
read_sql_query |
从查询中输入数据 |
read_sql_table |
将 SQL 数据库表读入 DataFrame |
read_stata |
从 Stata 数据集输入数据 |
read_table |
从文本文件输入数据 |
表 2.1 输入数据所用的函数列表
pandas 简介
pandas
模块是一个强大的工具,用于处理各种类型的数据,包括经济、金融和会计数据。如果你通过 Anaconda 在你的机器上安装了 Python,那么pandas
模块已经安装好了。如果你执行以下命令且没有错误提示,则说明pandas
模块已经安装:
>>>import pandas as pd
在以下示例中,我们生成了两个从 2013 年 1 月 1 日开始的时间序列。这两列时间序列的名称分别是A
和B
:
import numpy as np
import pandas as pd
dates=pd.date_range('20160101',periods=5)
np.random.seed(12345)
x=pd.DataFrame(np.random.rand(5,2),index=dates,columns=('A','B'))
首先,我们导入了 NumPy 和pandas
模块。pd.date_range()
函数用于生成索引数组。x
变量是一个以日期为索引的 pandas DataFrame。稍后我们将在本章中讨论pd.DataFrame()
函数。columns()
函数定义了列的名称。由于程序中使用了seed()
函数,任何人都可以生成相同的随机值。describe()
函数提供了这两列的属性,例如均值和标准差。再次调用这样的函数,如下所示:
>>> x
A B
2016-01-01 0.929616 0.316376
2016-01-02 0.183919 0.204560
2016-01-03 0.567725 0.595545
2016-01-04 0.964515 0.653177
2016-01-05 0.748907 0.653570
>>>
>>> x.describe()
A B
count 5.000000 5.000000
mean 0.678936 0.484646
std 0.318866 0.209761
min 0.183919 0.204560
25% 0.567725 0.316376
50% 0.748907 0.595545
75% 0.929616 0.653177
max 0.964515 0.653570
>>>
为了显示pandas
模块中包含的所有函数,在导入该模块后,使用dir(pd)
命令;请参见以下代码及相应的输出:
>>> import pandas as pd
>>> dir(pd)
['Categorical', 'CategoricalIndex', 'DataFrame', 'DateOffset', 'DatetimeIndex', 'ExcelFile', 'ExcelWriter', 'Expr', 'Float64Index', 'Grouper', 'HDFStore', 'Index', 'IndexSlice', 'Int64Index', 'MultiIndex', 'NaT', 'Panel', 'Panel4D', 'Period', 'PeriodIndex', 'RangeIndex', 'Series', 'SparseArray', 'SparseDataFrame', 'SparseList', 'SparsePanel', 'SparseSeries', 'SparseTimeSeries', 'Term', 'TimeGrouper', 'TimeSeries', 'Timedelta', 'TimedeltaIndex', 'Timestamp', 'WidePanel', '__builtins__', '__cached__', '__doc__', '__docformat__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_np_version_under1p10', '_np_version_under1p11', '_np_version_under1p12', '_np_version_under1p8', '_np_version_under1p9', '_period', '_sparse', '_testing', '_version', 'algos', 'bdate_range', 'compat', 'computation', 'concat', 'core', 'crosstab', 'cut', 'date_range', 'datetime', 'datetools', 'dependency', 'describe_option', 'eval', 'ewma', 'ewmcorr', 'ewmcov', 'ewmstd', 'ewmvar', 'ewmvol', 'expanding_apply', 'expanding_corr', 'expanding_count', 'expanding_cov', 'expanding_kurt', 'expanding_max', 'expanding_mean', 'expanding_median', 'expanding_min', 'expanding_quantile', 'expanding_skew', 'expanding_std', 'expanding_sum', 'expanding_var', 'factorize', 'fama_macbeth', 'formats', 'get_dummies', 'get_option', 'get_store', 'groupby', 'hard_dependencies', 'hashtable', 'index', 'indexes', 'infer_freq', 'info', 'io', 'isnull', 'json', 'lib', 'lreshape', 'match', 'melt', 'merge', 'missing_dependencies', 'msgpack', 'notnull', 'np', 'offsets', 'ols', 'option_context', 'options', 'ordered_merge', 'pandas', 'parser', 'period_range', 'pivot', 'pivot_table', 'plot_params', 'pnow', 'qcut', 'read_clipboard', 'read_csv', 'read_excel', 'read_fwf', 'read_gbq', 'read_hdf', 'read_html', 'read_json', 'read_msgpack', 'read_pickle', 'read_sas', 'read_sql', 'read_sql_query', 'read_sql_table', 'read_stata', 'read_table', 'reset_option', 'rolling_apply', 'rolling_corr', 'rolling_count', 'rolling_cov', 'rolling_kurt', 'rolling_max', 'rolling_mean', 'rolling_median', 'rolling_min', 'rolling_quantile', 'rolling_skew', 'rolling_std', 'rolling_sum', 'rolling_var', 'rolling_window', 'scatter_matrix', 'set_eng_float_format', 'set_option', 'show_versions', 'sparse', 'stats', 'test', 'timedelta_range', 'to_datetime', 'to_msgpack', 'to_numeric', 'to_pickle', 'to_timedelta', 'tools', 'tseries', 'tslib', 'types', 'unique', 'util', 'value_counts', 'wide_to_long']
仔细查看前面的列表,我们会看到与statsmodels
模块中包含的相同函数,它们以read_
开头,如表 2.1 所示。这种重复使我们的程序工作变得稍微简单一点。假设我们计划用时间序列的均值替换缺失值(NaN
)。这时使用的两个函数是mean()
和fillna()
:
>>> import pandas as pd
>>> import numpy as np
>>> x=pd.Series([1,4,-3,np.nan,5])
>>> x
0 1.0
1 4.0
2 -3.0
3 NaN
4 5.0
dtype: float64
>>> m=np.mean(x)
>>> m
1.75
>>> x.fillna(m)
0 1.00
1 4.00
2 -3.00
3 1.75
4 5.00
dtype: float64>> >
从右侧的输出中可以看到,第四个观察值NaN
被替换为均值 1.75。在以下代码中,我们通过使用pandas
模块中包含的dataFrame()
函数生成了一个 DataFrame:
import pandas as pd
import numpy as np
np.random.seed(123)
df = pd.DataFrame(np.random.randn(10, 4))
由于程序中使用了numpy.random.seed()
函数,不同的用户将得到相同的随机数:
>>> df
>>>
0 1 2 3
0 -1.085631 0.997345 0.282978 -1.506295
1 -0.578600 1.651437 -2.426679 -0.428913
2 1.265936 -0.866740 -0.678886 -0.094709
3 1.491390 -0.638902 -0.443982 -0.434351
4 2.205930 2.186786 1.004054 0.386186
5 0.737369 1.490732 -0.935834 1.175829
6 -1.253881 -0.637752 0.907105 -1.428681
7 -0.140069 -0.861755 -0.255619 -2.798589
8 -1.771533 -0.699877 0.927462 -0.173636
9 0.002846 0.688223 -0.879536 0.283627
>>>
目前,读者可能会感到困惑,为什么在尝试获取一组随机数时,我们会得到相同的随机值。这个问题将在第十二章,蒙特卡洛模拟中进行更详细的讨论和解释。在以下代码中,展示了如何使用不同的方法进行插值:
import pandas as pd
import numpy as np
np.random.seed(123) # fix the random numbers
x=np.arange(1, 10.1, .25)**2
n=np.size(x)
y = pd.Series(x + np.random.randn(n))
bad=np.array([4,13,14,15,16,20,30]) # generate a few missing values
x[bad] = np.nan # missing code is np.nan
methods = ['linear', 'quadratic', 'cubic']
df = pd.DataFrame({m: x.interpolate(method=m) for m in methods})
df.plot()
相应的图表如下截图所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_15.jpg
通常,不同的编程语言有各自的类型数据集。
例如,SAS 有自己的数据集,其扩展名为.sas7bdat
。
对于 R,其扩展名可能是.RData
、.rda
或.rds
。Python 也有自己的数据集格式。一种数据集类型的扩展名是.pickle
或.pkl
。让我们生成一个 pickle 数据集;请查看以下代码:
import numpy as np
import pandas as pd
np.random.seed(123)
df=pd.Series(np.random.randn(100))
df.to_pickle('test.pkl')
最后一条命令将变量保存为一个名为test.pkl
的 pickle 数据集,保存在当前工作目录下。要将 pickle 数据集保存到特定地址的文件中,即绝对路径,我们有以下代码:
df.to_pickle('test.pkl')
要读取 pickle 数据集,使用pd.read_pickle()
函数:
>>>import pandas as pd
>>>x=pd.read_pickle("c:/temp/test.pkl")
>>>x[:5]
>>>
>>>
0 -1.085631
1 0.997345
2 0.282978
3 -1.506295
4 -0.578600
dtype: float64
>>>
合并两个不同的数据集是研究人员常做的常见操作。以下程序的目的是根据它们的公共变量key
合并两个数据集:
import numpy as np
import pandas as pd
x = pd.DataFrame({'key':['A','B','C','D'],'value': [0.1,0.2,-0.5,0.9]})
y = pd.DataFrame({'key':['B','D','D','E'],'value': [2, 3, 4, 6]})
z=pd.merge(x, y, on='key')
以下代码展示了x
和y
的初始值,以及合并后的数据集z
:
>>> x
key value
0 A 0.1
1 B 0.2
2 C -0.5
3 D 0.9
>>> y
key value
0 B 2
1 D 3
2 D 4
3 E 6numpy as np
>>>z
key value_x value_y
0 B 0.2 2
1 D 0.9 3
2 D 0.9 4
>>>
对于金融领域,时间序列占据了独特的地位,因为许多数据集是以时间序列的形式存在的,例如股价和回报。因此,了解如何定义date
变量并研究相关函数,对于处理经济、金融和会计数据至关重要。我们来看一些例子:
>>> date1=pd.datetime(2010,2,3)
>>> date1
datetime.datetime(2010, 2, 3, 0, 0)
两个日期之间的差异可以轻松估算;请查看以下代码:
>>>date1=pd.datetime(2010,2,3)
>>>date2=pd.datetime(2010,3,31)
>>> date2-date1
datetime.timedelta(56)
来自pandas
模块的一个子模块datetools
非常有用;请查看其中包含的函数列表:
>>> dir(pd.datetools)
>>>
['ABCDataFrame', 'ABCIndexClass', 'ABCSeries', 'AmbiguousTimeError', 'BDay', 'BMonthBegin', 'BMonthEnd', 'BQuarterBegin', 'BQuarterEnd', 'BYearBegin', 'BYearEnd', 'BusinessDay', 'BusinessHour', 'CBMonthBegin', 'CBMonthEnd', 'CDay', 'CustomBusinessDay', 'CustomBusinessHour', 'DAYS', 'D_RESO', 'DateOffset', 'DateParseError', 'Day', 'Easter', 'FY5253', 'FY5253Quarter', 'FreqGroup', 'H_RESO', 'Hour', 'LastWeekOfMonth', 'MONTHS', 'MS_RESO', 'Micro', 'Milli', 'Minute', 'MonthBegin', 'MonthEnd', 'MutableMapping', 'Nano', 'OLE_TIME_ZERO', 'QuarterBegin', 'QuarterEnd', 'Resolution', 'S_RESO', 'Second', 'T_RESO', 'Timedelta', 'US_RESO', 'Week', 'WeekOfMonth', 'YearBegin', 'YearEnd', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'algos', 'bday', 'bmonthBegin', 'bmonthEnd', 'bquarterEnd', 'businessDay', 'byearEnd', 'cache_readonly', 'cbmonthBegin', 'cbmonthEnd', 'cday', 'com', 'compat', 'customBusinessDay', 'customBusinessMonthBegin', 'customBusinessMonthEnd', 'datetime', 'day', 'deprecate_kwarg', 'format', 'getOffset', 'get_base_alias', 'get_freq', 'get_freq_code', 'get_freq_group', 'get_legacy_offset_name', 'get_offset', 'get_offset_name', 'get_period_alias', 'get_standard_freq', 'get_to_timestamp_base', 'infer_freq', 'isBMonthEnd', 'isBusinessDay', 'isMonthEnd', 'is_subperiod', 'is_superperiod', 'lib', 'long', 'monthEnd', 'need_suffix', 'normalize_date', 'np', 'offsets', 'ole2datetime', 'opattern', 'parse_time_string', 'prefix_mapping', 'quarterEnd', 'range', 're', 'thisBMonthEnd', 'thisBQuarterEnd', 'thisMonthEnd', 'thisQuarterEnd', 'thisYearBegin', 'thisYearEnd', 'time', 'timedelta', 'to_datetime', 'to_offset', 'to_time', 'tslib', 'unique', 'warnings', 'week', 'yearBegin', 'yearEnd', 'zip']
>>>
这是一个使用pandas
模块中weekday()
函数的例子。该函数在进行所谓的周效应测试时非常重要。该测试将在第四章 数据来源中详细解释。让我们来看一下以下代码:
>>import pandas as pd
>>>date1=pd.datetime(2010,10,10)
>>>date1.weekday()
6
在某些情况下,用户可能希望将数据堆叠在一起或反过来;请查看以下代码:
import pandas as pd
import numpy as np
np.random.seed(1256)
df=pd.DataFrame(np.random.randn(4,2),columns=['Stock A','Stock B'])
df2=df.stack()
原始数据集与堆叠数据集的比较如下。左侧是原始数据集:
>>> df
Stock A Stock B
0 0.452820 -0.892822
1 -0.476880 0.393239
2 0.961438 -1.797336
3 -1.168289 0.187016
>>>
>>> df2
>>>
0 Stock A 0.452820
Stock B -0.892822
1 Stock A -0.476880
Stock B 0.393239
2 Stock A 0.961438
Stock B -1.797336
3 Stock A -1.168289
Stock B 0.187016
dtype: float64>> >
股票的反操作是应用unstack()
函数;请查看以下代码:
>>> k=df2.unstack()
>>> k
Stock A Stock B
0 0.452820 -0.892822
1 -0.476880 0.393239
2 0.961438 -1.797336
3 -1.168289 0.187016
如果输入数据集按股票 ID 和日期排序,即视为按顺序堆叠每只股票,那么此操作可以用于生成回报矩阵。
与金融相关的 Python 模块
由于本书是将 Python 应用于金融领域,因此与金融相关的模块(包)将是我们的首要任务。
下表列出了约十个与金融相关的 Python 模块或子模块:
名称 | 描述 |
---|---|
Numpy.lib.financial |
提供许多公司财务和财务管理相关的函数。 |
pandas_datareader |
从 Google、Yahoo! Finance、FRED、Fama-French 因子获取数据。 |
googlefinance |
Python 模块,用于通过 Google Finance API 获取实时(无延迟)股票数据。 |
yahoo-finance |
Python 模块,用于从 Yahoo! Finance 获取股票数据。 |
Python_finance |
下载并分析 Yahoo! Finance 数据,并开发交易策略。 |
tstockquote |
从 Yahoo! Finance 获取股票报价数据。 |
finance |
财务风险计算。通过类构造和运算符重载优化,便于使用。 |
quant |
用于财务定量分析的企业架构。 |
tradingmachine |
一个用于金融算法回测的工具。 |
economics |
经济数据的函数和数据处理。有关更好理解,请访问以下链接:github.com/tryggvib/economics 。 |
FinDates |
处理财务中的日期。 |
表 2.2 与财务相关的模块或子模块列表
若要了解更多关于经济学、财务或会计的信息,请访问以下网页:
名称 | 位置 |
---|---|
Python 模块索引(v3.5) | docs.python.org/3/py-modindex.html |
PyPI – Python 包索引 | pypi.python.org/pypi |
Python 模块索引(v2.7) | docs.python.org/2/py-modindex.html |
表 2.3 与 Python 模块(包)相关的网站
pandas_reader 模块介绍
通过该模块,用户可以从 Yahoo! Finance、Google Finance、联邦储备经济数据(FRED)和 Fama-French 因子下载各种经济和财务数据。
假设已安装pandas_reader
模块。有关如何安装此模块的详细信息,请参阅如何安装 Python 模块部分。首先,让我们看一个最简单的例子,只需两行代码即可获取 IBM 的交易数据;请见下文:
import pandas_datareader.data as web
df=web.get_data_google("ibm")
我们可以使用点头和点尾显示部分结果;请见以下代码:
>>> df.head()
>>>
Open High Low Close Volume
Date
2010-01-04 131.179993 132.970001 130.850006 132.449997 6155300
2010-01-05 131.679993 131.850006 130.100006 130.850006 6841400
2010-01-06 130.679993 131.490005 129.809998 130.000000 5605300
2010-01-07 129.869995 130.250000 128.910004 129.550003 5840600
2010-01-08 129.070007 130.919998 129.050003 130.850006 4197200
Adj Close
Date
2010-01-04 112.285875
2010-01-05 110.929466
2010-01-06 110.208865
2010-01-07 109.827375
2010-01-08 110.929466
>> >df.tail()
>>>
Open High Low Close Volume
Date
2016-11-16 158.460007 159.550003 158.029999 159.289993 2244100
2016-11-17 159.220001 159.929993 158.850006 159.800003 2256400
2016-11-18 159.800003 160.720001 159.210007 160.389999 2958700
2016-11-21 160.690002 163.000000 160.369995 162.770004 4601900
2016-11-22 163.000000 163.000000 161.949997 162.669998 2707900
Adj Close
Date
2016-11-16 159.289993
2016-11-17 159.800003
2016-11-18 160.389999
2016-11-21 162.770004
2016-11-22 162.669998
>>>
本模块将在第四章,数据来源中再次进行更详细的解释。
两个财务计算器
在下一章中,将介绍并讨论许多基本的财务概念和公式。通常,在学习企业财务或财务管理时,学生依赖 Excel 或财务计算器来进行估算。由于 Python 是计算工具,因此用 Python 编写的财务计算器无疑将加深我们对财务和 Python 的理解。
这是第一个用 Python 编写的财务计算器,来自Numpy.lib.financial
;请见以下代码:
>>> import numpy.lib.financial as fin
>>> dir(fin)
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_convert_when', '_g_div_gp', '_rbl', '_when_to_num', 'absolute_import', 'division', 'fv', 'ipmt', 'irr', 'mirr', 'np', 'nper', 'npv', 'pmt', 'ppmt', 'print_function', 'pv', 'rate']
>>>
在第三章,时间价值中将使用并讨论的函数包括fv()
、irr()
、nper()
、npv()
、pmt()
、pv()
和rate()
。以下代码展示了使用pv()
的一个示例:
>>> import numpy.lib.financial as fin
>>> fin.pv(0.1,1,0,100)
-90.909090909090907
>>>
第二个财务计算器由作者提供。使用这个第二个财务计算器有许多优点。首先,所有功能都采用与教科书中公式相同的格式。
换句话说,这里没有 Excel 的符号约定。
例如,pv_f()
函数将依赖于以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_16.jpg
名为pvAnnuity()
的函数基于以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_17.jpg
第二,估算一个未来现金流的现值的公式与估算年金现值的公式是分开的。这将帮助学生,特别是初学者,避免不必要的困惑。
为了进行对比,numpy.lib.financial.pv()
函数实际上结合了公式(6)和(7)。我们将在第三章,货币的时间价值中详细讨论这一点。第三,对于每个函数,提供了许多示例。这意味着用户花费更少的时间去理解各个函数的含义。第四,这个第二个金融计算器提供的功能比numpy.lib.financial
子模块能提供的更多。最后但同样重要的是,用户最终会学会如何用 Python 编写自己的金融计算器。更多细节,请参见第三章,货币的时间价值中的最后一节。
要使用这样的金融计算器,用户应从作者的网站下载名为fincal.cpython-35.syc
的文件(canisius.edu/~yany/fincal.cpython-35.pyc
)。假设可执行文件已保存在c:/temp/
目录下。要将c:/temp/
添加到 Python 路径中,请点击菜单栏最右侧的 Python 徽标;请参见下图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_18.jpg
点击前面截图中所示的徽标后,用户将看到以下截图中左侧的屏幕:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_19.jpg
点击添加路径后,输入c:/temp/
;请参见前面截图中右侧的屏幕。现在,我们可以使用import fincal
来使用模块中的所有函数。在第三章,货币的时间价值中,我们展示了如何生成这样的fincal
模块:
>>>import fincal
>>>dir(fincal)
['CND', 'EBITDA_value', 'IRR_f', 'IRRs_f', 'NPER', 'PMT', 'Rc_f', 'Rm_f', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__request', '__spec__', 'bondPrice', 'bsCall', 'convert_B_M', 'duration', 'exp', 'fincalHelp', 'fvAnnuity', 'fv_f', 'get_200day_moving_avg', 'get_50day_moving_avg', 'get_52week_high', 'get_52week_low', 'get_EBITDA', 'get_all', 'get_avg_daily_volume', 'get_book_value', 'get_change', 'get_dividend_per_share', 'get_dividend_yield', 'get_earnings_per_share', 'get_historical_prices', 'get_market_cap', 'get_price', 'get_price_book_ratio', 'get_price_earnings_growth_ratio', 'get_price_earnings_ratio', 'get_price_sales_ratio', 'get_short_ratio', 'get_stock_exchange', 'get_volume', 'log', 'market_cap', 'mean', 'modified_duration', 'n_annuity', 'npv_f', 'payback_', 'payback_period', 'pi', 'pvAnnuity', 'pvAnnuity_k_period_from_today', 'pvGrowPerpetuity', 'pvGrowingAnnuity', 'pvPerpetuity', 'pvPerpetuityDue', 'pv_excel', 'pv_f', 'r_continuous', 're', 'sign', 'sqrt', 'urllib']
要查找每个函数的用法,请使用help()
函数;请参见以下示例:
>>> import fincal
>>> help(fincal.pv_f)
Help on function pv_f in module fincal:
pv_f(fv, r, n)
Objective: estimate present value
fv: fture value
r : discount period rate
n : number of periods
formula : fv/(1+r)**n
e.g.,
>>>pv_f(100,0.1,1)
90.9090909090909
>>>pv_f(r=0.1,fv=100,n=1)
90.9090909090909
>>>pv_f(n=1,fv=100,r=0.1)
90.9090909090909
>>>
从前面的信息中,用户可以知道该函数的目标、三个输入值的定义、使用的公式以及一些示例。
如何安装 Python 模块
如果 Python 是通过 Anaconda 安装的,那么很可能本书中讨论的许多模块已经随着 Python 一起安装。如果 Python 是独立安装的,用户可以使用 PyPi 来安装或更新。
例如,我们对安装 NumPy 感兴趣。在 Windows 上,我们使用以下代码:
python -m pip install -U pip numpy
如果Python.exe
在路径中,我们可以先打开一个 DOS 窗口,然后输入前述命令。如果Python.exe
不在路径中,我们需要打开一个 DOS 窗口,然后移动到Python.exe
文件的位置;例如,见下图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_20.jpg
对于 Mac,我们有以下代码。有时,在运行前述命令后,你可能会收到以下信息,提示更新 PiP:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_21.jpg
这里给出了更新 pip
的命令行:
python –m pip install –upgrade pip
请参见以下截图显示的结果:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_22.jpg
要独立安装 NumPy,在 Linux 或 OS X 上,我们执行以下命令:
pip install -U pip numpy
要为 Anaconda 安装一个新的 Python 模块,我们有以下列表。另见链接:conda.pydata.org/docs/using/pkgs.html
:
命令 | 描述 |
---|---|
conda list |
列出活动环境中的所有软件包 |
conda list -n snowflakes |
列出安装到名为 snowflakes 的非活动环境中的所有软件包 |
conda search beautiful-soup |
使用 conda install 将如Beautiful Soup 等包安装到当前环境 |
conda install --name bunnies quant |
安装名为quant 的 Python 模块(包) |
conda info |
获取更多信息 |
表 2.4 使用 conda 安装新软件包的命令列表
以下截图展示了执行 conda info
命令后你将看到的内容:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_23.jpg
以下示例与安装名为pandas_datareader
的 Python 模块有关:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_24.jpg
在回答y
后,模块完成安装后,以下结果将显示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_02_25.jpg
要获取各种模块的版本,我们有以下代码:
>>>import numpy as np
>>> np.__version__
'1.11.1'
>>> import scipy as sp
>>> sp.__version__
'0.18.1'
>>>import pandas as pd
>>> pd.__version__
'0.18.1'
模块依赖性
在本书的最开始,我们提到使用 Python 的一个优势是它提供了数百个被称为模块的特殊软件包。
为了避免重复劳动并节省开发新模块的时间,后续模块选择使用早期模块开发的函数;也就是说,它们依赖于早期模块。
这个优势显而易见,因为开发者在构建和测试新模块时可以节省大量时间和精力。然而,一个缺点是安装变得更加困难。
有两种竞争方法:
第一种方法是将所有内容打包在一起,确保各部分能够协调运行,从而避免独立安装n个软件包的麻烦。假设它能正常工作,这非常棒。但潜在的问题是,单个模块的更新可能不会反映在超级包中。
第二种方法是使用最小依赖项。这对包的维护者来说会减少麻烦,但对于需要安装多个组件的用户来说,可能会更加麻烦。Linux 有更好的方法:使用包管理器。包的发布者可以声明依赖项,系统会追踪它们,前提是它们在 Linux 仓库中。SciPy、NumPy 和 quant 都是这样设置的,效果很好。
练习
-
如果我们的 Python 是通过 Anaconda 安装的,是否需要单独安装 NumPy?
-
使用超级包同时安装多个模块有哪些优点?
-
如何查找 NumPy 或 SciPy 中包含的所有函数?
-
有多少种方法可以导入 SciPy 中包含的特定函数?
-
以下操作有什么问题?
>>>x=[1,2,3] >>>x.sum()
-
如何打印给定数组的所有数据项?
-
以下代码行有什么问题?
>>>import np >>>x=np.array([True,false,true,false],bool)
-
查找
stats
子模块(SciPy)中包含的skewtest
函数的含义,并给出一个使用该函数的示例。 -
算术平均数和几何平均数有什么区别?
-
调试以下代码行,用于估算给定收益集的几何平均数:
>>>import scipy as sp >>>ret=np.array([0.05,0.11,-0.03]) >>>pow(np.prod(ret+1),1/len(ret))-1
-
编写一个 Python 程序,估算给定收益集的算术平均数和几何平均数。
-
查找
stats
子模块(SciPy)中包含的zscore()
函数的含义,并提供一个使用该函数的简单示例。 -
以下代码行有什么问题?
>>>c=20 >>>npv=np.npv(0.1,c)
-
什么是模块依赖性,如何处理它?
-
编写依赖其他模块的模块有哪些优缺点?
-
如何使用 NumPy 中包含的财务函数;例如,
pv()
或fv()
函数? -
对于
numpy.lib.financial
中包含的函数,SciPy 中是否有类似的函数? -
如何使用作者生成的
fincal
模块中包含的函数? -
在哪里可以找到所有 Python 模块的列表?
-
如何查找与财务相关的 Python 模块的更多信息?
总结
在本章中,我们讨论了 Python 最重要的特性之一:模块。模块是由专家或任何个人编写的包,用于服务于特定的目的。与模块相关的知识对于我们理解 Python 及其在金融中的应用至关重要。特别地,我们介绍并讨论了最重要的模块,如 NumPy、SciPy、matplotlib
、statsmodels
、pandas
和 pandas_reader
。此外,我们简要提及了模块依赖关系和其他问题。还介绍了两个用 Python 编写的金融计算器。在第三章中,货币的时间价值,我们将讨论许多与金融相关的基本概念,如单个未来现金流的现值、永续年金的现值、成长永续年金的现值、年金现值以及与未来价值相关的公式。此外,我们将讨论净现值(NPV)、内部收益率(IRR)和回收期的定义。之后,我们将解释几个投资决策规则。
第三章:时间价值
就金融本身而言,本章并不依赖于前两章。由于本书中使用 Python 作为计算工具来解决各种金融问题,因此最低要求是读者应安装 Python 以及 NumPy 和 SciPy。如果读者通过 Anaconda 安装了 Python,实际上可以不读前两章。另外,读者可以阅读附录 A 了解如何安装 Python。
本章将介绍并详细讨论与金融相关的各种概念和公式。由于这些概念和公式非常基础,曾学习过一门金融课程的读者,或在金融行业工作了几年的专业人士,可以快速浏览本章内容。再次强调,本书的一个特点与典型金融教材有所不同,即使用 Python 作为计算工具。特别是,以下主题将会涉及:
单一未来现金流的现值与永续年金的现值
增长永续年金的现值
年金的现值与未来值
永续年金与永续年金到期,年金与年金到期的区别
SciPy 中包含的相关函数以及 numpy.lib.financial
子模块
一个用 Python 编写的免费财务计算器,名为 fincal
NPV 和 NPV 法则的定义
IRR 和 IRR 法则的定义
时间价值与 NPV 图形展示
回收期和回收期法则的定义
如何使用 Python 编写自己的财务计算器
时间价值引论
让我们用一个非常简单的例子来说明。假设今天将 $100 存入银行,年利率为 10%。一年后存款的价值是多少?以下是包含日期和现金流的时间线:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_13.jpg
显然,我们的年利息支付将是 $10,即 1000.1=10*。因此,总价值将是 110,即 100 + 10。原始的 $100 是本金。或者,我们有以下结果:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_14.jpg
假设 $100 将以相同的 10% 年利率在银行存两年。两年末的未来价值是多少?
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_15.jpg
由于在第一年末,我们有 $110,并且应用相同的逻辑,第二年末的未来价值应该是:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_16.jpg
由于 110 = 100(1+0.1)*,所以我们有以下表达式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_17.jpg
如果 $100 存入银行五年,年利率为 10%,那么五年末的未来价值是多少?根据前述逻辑,我们可以得到以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_18.jpg
一般化得出我们用来估算给定现值的未来值的第一个公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_19.jpg
这里,FV是未来值,PV是现值,R是期利率,n是期数。在前面的例子中,R是年利率,n是年数。R和n的频率应当一致。这意味着,如果R是年利率(月利率/季利率/日利率),那么n必须是年数(月数/季数/天数)。对应的函数是 SciPy 模块中的fv()
,可用来估算未来值;请参阅以下代码。若要估算在年末以 10%年利率计算的未来值,代码如下:
>>>import scipy as sp
>>> sp.fv(0.1,2,0,100)
-121.00000000000001
对于该函数,输入格式为sp.fv(rate,nper,pmt,pv=0,when='end')
。目前,暂时忽略最后一个名为 when 的变量。对于方程式 (1),没有 pmt,因此第三个输入应为零。请注意先前结果中的负号。原因在于scipy.fv()
函数遵循 Excel 符号约定:正的未来值对应负的现值,反之亦然。要了解更多关于此函数的信息,我们可以输入help(sp.fv)
,查看以下几行内容:
>>> help(sp.fv)
numpy.lib.financial
模块中fv
函数的帮助文档:
fv(rate, nper, pmt, pv, when='end')
计算未来值。
如果我们不小心输入sp.fv(0.1,2,100,0)
,结果和相应的现金流如下所示:
>>>import scipy as sp
>>> sp.fv(0.1,2,100,0)
-210.0000000000002
>>>
本章稍后将展示,sp.fv(0.1,2,100,0)
对应的是两个相等的 100 美元在第一年和第二年年末发生的现值。从方程式 (1),我们可以轻松推导出第二个公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_20.jpg
PV、FV、R和n的符号与方程式 (1)中的符号保持一致。如果我们计划在第五年末得到 234 美元,并且年利率为 1.45%,那么我们今天需要存入多少?应用方程式 (2) 手动计算后的结果如下图所示:
>>> 234/(1+0.0145)**5
217.74871488824184
>>> sp.pv(0.0145,5,0,234)
-217.74871488824184
另外,也可以使用sp.pv()
函数,参见右侧的结果。要了解更多关于sp.pv()
函数的信息,我们可以使用help(sp.pv)
,查看以下输出的一部分:
>>>import scipy as sp
>>> help(sp.pv)
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_01.jpg
请注意,对于输入变量集的第四个输入变量,scipy.fv()
和scipy.pv()
函数的行为不同:scipy.fv(0.1,1,100)
会给出错误消息,而scipy.pv(0.1,1,100)
则可以正常工作。原因是scipy.pv()
函数的第四个输入变量默认值为零,而scipy.fv()
函数没有第四个输入变量的默认值。这是 Python 编程中的一种不一致之处。
在金融学中,广为人知的是,今天收到的 100 美元比一年后收到的 100 美元更有价值,而一年后收到的 100 美元又比两年后收到的 100 美元更有价值。如果使用不同的大小来表示相对价值,我们将得到以下图形。第一个蓝色圆圈是今天 100 美元的现值,第二个是第一年末 100 美元的现值,依此类推。生成该图像的 Python 程序见附录 B:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_02.jpg
下一个概念是永续年金,它被定义为相同的恒定现金流,按相同的间隔永远支付。这里是时间线以及这些恒定现金流:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_21.jpg
请注意,在前面的例子中,第一个现金流发生在第一个周期结束时。我们可以有其他永续年金,其第一个现金流发生在其他周期的结束。我们先研究这个例子,稍后在本章中,我们会进行一个简单的扩展。当周期贴现率为R时,如何计算这种永续年金的现值?
首先,方程式(2)可以应用于每一个未来现金流。因此,所有这些现值的总和将是解:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_22.jpg
为了简化我们的推导,永续年金现值(PV)被替换为PV。我们称之为方程式(I):
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_23.jpg
为了推导公式,方程式(I)的两边都乘以1/(1+R);见下式。我们称之为方程式(II):
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_24.jpg
*方程式(I)减去方程式(II)*得到下式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_25.jpg
将两边乘以*(1+R)*,我们得到:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_26.jpg
重组前面的结果,最终我们得到了估算永续年金现值的公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_27.jpg
这里有一个例子。约翰计划每年捐赠 3,000 美元给他的母校,用于为即将到来的 MBA 学生举办迎新派对,且每年都会举行。如果年贴现率为 2.5%,且第一次派对将在第一年末举行,那么他今天应该捐赠多少?通过应用前面的公式,答案是 120,000 美元:
>>> 3000/0.025
120000.0
假设第一个现金流为C,且随后的现金流享有恒定的增长率 g;见下方时间线和现金流:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_28.jpg
如果贴现率为R,那么估算成长型永续年金现值的公式如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_29.jpg
同样,C、R 和 g 的频率应该保持一致,也就是说,它们的频率应该相同。章节末尾有一个问题要求读者证明方程 (4)。以约翰的 MBA 欢迎派对捐款为例,每年需要 $3,000 的费用是基于零通货膨胀的。假设年通货膨胀率为 1%,他今天需要捐赠多少钱?每年所需的金额如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_30.jpg
以下结果表明,他今天需要 $200,000:
>>> 3000/(0.025-0.01)
199999.99999999997
对于永续年金,如果第一笔现金流发生在第 k 期末,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_31.jpg
显然,当第一笔现金流发生在第一个时期末时,方程 (5) 会简化为方程 (3)。年金被定义为在 n 期内相同时间间隔内的相同现金流。如果第一笔现金流发生在第一个时期末,则年金的现值通过以下公式估算:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_32.jpg
在这里,C 是在每个时期末发生的递归现金流,R 是期间折现率,n 是期数。方程 (5) 比其他方程要复杂。然而,稍加想象,方程 (6) 可以通过结合方程 (2) 和 (3) 推导出来;有关更多细节,请参见附录 C。
要估算年金的未来价值,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_33.jpg
从概念上讲,我们可以将方程 (7) 看作是方程 (6) 和 (1) 的组合。在之前与永续年金或年金相关的公式中,假设所有现金流都发生在期末。对于年金或永续年金,当现金流发生在每个时间段的开始时,它们被称为期初年金或期初永续年金。计算其现值有三种方法。
对于第一种方法,scipy.pv()
或 numpy.lib.financial.pv()
中的最后一个输入值将取值为 1。
假设折现率为每年 1%。接下来的 10 年每年现金流为 $20。第一笔现金流今天支付。这些现金流的现值是多少?结果如下所示:
>>>import numpy.lib.financial as fin
>>> fin.pv(0.01,10,20,0,1)
-191.32035152017377
请注意,numpy.lib.financial.pv()
函数的输入格式为 rate
、nper
、pmt
、fv
和 when
。最后一个变量 when
的默认值为零,即发生在期末。当 when
取值为 1 时,表示为期初年金。
对于第二种方法,以下公式可以应用:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_34.jpg
这里是方法:将期初年金视为普通年金,然后将结果乘以 (1+R)。应用如下所示:
>>>import numpy.lib.financial as fin
>>> fin.pv(0.01,10,20,0)*(1+0.01)
-191.3203515201738
对于第三种方法,我们使用名为 fincal.pvAnnuityDue()
的函数,该函数包含在用 Python 编写的财务计算器 fincal
包中;请参见以下结果:
>>> import fincal
>>> fincal.pvAnnuityDue(0.01,10,20)
191.32035152017383
有关如何下载 fincal
模块,请参见 附录 D – 如何下载一个用 Python 编写的免费财务计算器。要获取有关此函数的更多信息,可以使用 help()
函数;请参见以下代码:
>>>import fincal
>>>help(fincal.pvAnnuityDue)
Help on function pvAnnuityDue in module __main__:
pvAnnuityDue(r, n, c)
Objective : estimate present value of annuity due
r : period rate
n : number of periods
c : constant cash flow
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_35.jpg
Example 1: >>>pvAnnuityDue(0.1,10,20)
135.1804763255031
Example #2:>>> pvAnnuityDue(c=20,n=10,r=0.1)
135.1804763255031
>>>
有关名为 fincal
的财务计算器的更多详细信息,请参见下一节。如果现金流将以 g 的固定速度增长,我们有以下增长年金的公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_36.jpg
这些函数在 SciPy 或 numpy.lib.financial
中没有对应的函数。幸运的是,我们有一个名为 fincal
的财务计算器,它包含了 pvGrowingAnnuity()
和 fvGrowingAnnuity()
函数;有关更多详细信息,请参见以下代码:
>>> import fincal
>>> fincal.pvGrowingAnnuity(0.1,10,20,0.03)
137.67487382555464
>>>
要获取有关此函数的更多信息,请输入 help(fincal.pvGrowingAnnuity)
;请参见以下代码:
>>> import fincal
>>> help(fincal.pvGrowingAnnuity)
Help on function pvGrowingAnnuity in module fincal:
pvGrowingAnnuity(r, n, c, g)
Objective: estimate present value of a growting annuity
r : period discount rate
n : number of periods
c : period payment
g : period growth rate (g<r)
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_37.jpg
Example #1 >>>pvGrowingAnnuity(0.1,30,10000,0.05)
150463.14700582038
Example #2: >>> pvGrowingAnnuity(g=0.05,r=0.1,c=10000,n=30)
150463.14700582038
>> >
用 Python 编写财务计算器
在讨论货币时间价值的各种概念时,学习者需要一个财务计算器或 Excel 来解决各种相关问题。
从前面的示例可以看出,像 scipy.pv()
这样的多个函数可以用来估算一个未来现金流的现值或年金现值。实际上,SciPy 模块中与财务相关的函数来自 numpy.lib.financial
子模块:
>>> import numpy.lib.financial as fin
>>> dir(fin)
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_convert_when', '_g_div_gp', '_rbl', '_when_to_num', 'absolute_import', 'division', 'fv', 'ipmt', 'irr', 'mirr', 'np', 'nper', 'npv', 'pmt', 'ppmt', 'print_function', 'pv', 'rate']
>>>
Below are a few examples, below.
>>>import numpy.lib.financial as fin
>>> fin.pv(0.1,3,0,100) # pv of one future cash flow
-75.131480090157751
>>> fin.pv(0.1,5,100) # pv of annuity
-379.07867694084507
>>> fin.pv(0.1,3,100,100) # pv of annuity plus pv of one fv
-323.81667918858022
>>>
首先,我们导入与各种财务函数相关的两个模块。
>>>import scipy as sp
>>>import numpy.lib.financial as fin
下表总结了这些函数:
函数 | 输入格式 |
---|---|
sp.fv() |
fin.fv() |
sp.pv() |
fin.pv() |
sp.pmt() |
fin.pmt() |
sp.npv() |
fin.npv() |
sp.rate() |
fin.rate() |
sp.nper() |
fin.nper() |
sp.irr() |
fin.irr() |
sp.mirr() |
fin.mirr() |
sp.ipmt() |
fin.ipmt() |
sp.ppmt() |
fin.ppmt() |
表 3.1 Scipy 和 numpy.lib.financial 中包含的函数列表
另一个财务计算器是由本书作者编写的。附录 B 显示了如何下载它。以下是函数列表:
>>> import fincal
>>> dir(fincal)
['CND', 'EBITDA_value', 'IRR_f', 'IRRs_f', 'NPER', 'PMT', 'Rc_f', 'Rm_f', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__request', '__spec__', 'bondPrice', 'bsCall', 'convert_B_M', 'duration', 'exp', 'fincalHelp', 'fvAnnuity', 'fv_f', 'get_200day_moving_avg', 'get_50day_moving_avg', 'get_52week_high', 'get_52week_low', 'get_EBITDA', 'get_all', 'get_avg_daily_volume', 'get_book_value', 'get_change', 'get_dividend_per_share', 'get_dividend_yield', 'get_earnings_per_share', 'get_historical_prices', 'get_market_cap', 'get_price', 'get_price_book_ratio', 'get_price_earnings_growth_ratio', 'get_price_earnings_ratio', 'get_price_sales_ratio', 'get_short_ratio', 'get_stock_exchange', 'get_volume', 'log', 'market_cap', 'mean', 'modified_duration', 'n_annuity', 'npv_f', 'payback_', 'payback_period', 'pi', 'pvAnnuity', 'pvAnnuityDue', 'pvAnnuity_k_period_from_today', 'pvGrowingAnnuity', 'pvGrowingPerpetuity', 'pvPerpetuity', 'pvPerpetuityDue', 'pv_excel', 'pv_f', 'r_continuous', 're', 'sign', 'sqrt', 'urllib']
使用这个财务计算器有几个优点,相比于 SciPy 模块和numpy.lib.financial
子模块中包含的函数。首先,对于三种现值,pv(单笔现金流)
、pv(年金)
和pv(年金到期)
,分别有三个对应的函数,分别是pv_f()
、pvAnnuity()
和pvAnnuityDue()
。因此,对于一个对金融知识了解较少的新学习者来说,他/她更不容易产生困惑。其次,对于每个函数,如单笔未来现金流的现值,输出与典型教材中显示的公式完全一致;请参见以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_38.jpg
换句话说,没有 Excel 的符号约定。对于fv=100、r=0.1和n=1,根据之前的公式,我们应该得到一个 90.91 的值。通过以下代码,我们展示了没有符号约定和有符号约定的结果:
>>>import fincal
>>> fincal.pv_f(0.1,1100)
90.9090909090909
>>> import scipy as sp
>>> sp.pv(0.1,1,0,100)
-90.909090909090907
第三,对于fincal
中的每个函数,我们可以找出使用的公式,并附上一些示例:
>>>import fincal
>>> help(fincal.pv_f)
Help on function pv_f in module __main__:
pv_f(r, n, fv)
Objective: estimate present value
r : period rate
n : number of periods
fv : future value
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_39.jpg
Example 1: >>>pv_f(0.1,1,100) # meanings of input variables
90.9090909090909 # based on their input order
Example #2 >>>pv_f(r=0.1,fv=100,n=1) # meanings based on keywords
90.9090909090909
>>>
最后但同样重要的是,新的学习者可以自己编写财务计算器!更多细节,请参见《用 Python 编写你自己的财务计算器》章节和附录 H。
从前面的讨论中,我们知道,对于年金的现值,可以使用以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_40.jpg
在上述公式中,我们有四个变量:pv、c、R和n。为了估算现值,我们给定了c、R和n。实际上,对于任何一组三个值,我们都可以估算出第四个值。让我们使用 SciPy 和 NumPy 中相同的符号:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_41.jpg
四个对应的函数是:sp.pv()
、sp.pmt()
、sp.rate()
和sp.nper()
。这是一个例子。约翰计划购买一辆二手车,价格为 $5,000。假设他将支付 $1,000 作为首付,其余部分借款。车贷的年利率为 1.9%,按月复利计算。如果他打算在三年内偿还贷款,那么他的月供是多少?我们可以手动计算月供;请参见以下代码:
>>> r=0.019/12
>>> pv=4000
>>> n=3*12
>>> pv*r/(1-1/(1+r)**n)
114.39577546409993
由于年利率是按月复利计算的,实际月利率为 0.019/12。在第五章《债券与股票估值》中,将更详细地讨论如何转换不同的有效利率。根据之前的结果,约翰的月供为 $114.40。或者,我们可以使用scipy.pmt()
函数;请参见以下代码:
>>import scipy as sp
>>> sp.pmt(0.019/12,3*12,4000)
-114.39577546409993
类似地,对于前面公式中的利率,可以使用scipy.rate()
和numpy.lib.rate()
函数。这里是一个例子。某公司计划为其 CEO 租赁一辆豪华轿车。如果接下来的三年每月付款$2,000,且汽车的现值为$50,000,隐含的年利率是多少?
>>>import scipy as sp
>>>r=sp.rate(3*12,2000,-50000,0) # monthly effective rate
>>>r
0.021211141641636025
>>> r*12
0.2545336996996323 # annual percentage rate
月有效利率为 2.12%,年利率为 25.45%。
按照相同的逻辑,对于前面公式中的nper
,可以使用scipy.nper()
和numpy.lib.financial.nper()
函数。
这里是一个例子。Peter 借了$5,000 来支付获得 Python 证书的费用。如果月利率为 0.25%,他计划每月偿还$200,他需要多少个月才能偿还贷款?
>>>import scipy as sp
>>> sp.nper(0.012,200,-5000,0)
29.900894915842475
基于前面的结果,他大约需要 30 个月才能偿还全部贷款。在前面的两个例子中,未来价值为零。按照相同的逻辑,对于未来价值年金,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_42.jpg
如果使用与 SciPy 和numpy.lib.financial
相同的符号,我们得到以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_43.jpg
scipy.pmt()
、scipy.rate()
、scipy.nper()
、numy.lib.financial.pmt()
、numpy.lib.financial.rate()
和numpy.lib.financial.nper()
函数可以用来估算这些值。我们将在Scipy 和numpy.lib.financial
中的常用公式部分进一步讨论这些公式。
净现值和 NPV 规则的定义
净现值(NPV)由以下公式定义:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_44.jpg
这里是一个例子。初始投资为$100。接下来五年的现金流入分别是$50、$60、$70、$100 和$20,从第一年开始。如果折现率为 11.2%,该项目的 NPV 值是多少?由于只有六个现金流,我们可以手动计算:
>>> r=0.112
>>> -100+50/(1+r)+60/(1+r)**2+70/(1+r)**3+100/(1+r)**4+20/(1+r)**5
121.55722687966407
Using the scipy.npv() function, the estimation process could be simplified dramatically:
>>> import scipy as sp
>>> cashflows=[-100,50,60,70,100,20]
>>> sp.npv(0.112,cashflows)
121.55722687966407
根据前面的结果,该项目的 NPV 为$121.56。正常项目定义如下:首先是现金流出,其次是现金流入。任何其他情况都是不正常项目。对于正常项目,其 NPV 与折现率呈负相关;见下图。原因是当折现率上升时,未来现金流(大多数情况下是收益)的现值会比当前或最早的现金流(大多数情况下是成本)减少得更多。NPV 曲线描述了 NPV 与折现率之间的关系,见下图。有关生成图表的 Python 程序,请参阅附录 E。y轴为 NPV,x轴为折现率:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_03.jpg
为了估算一个项目的净现值(NPV),我们可以调用npv()
函数,该函数包含在 SciPy 或numpy.lib.financial
库中;请参阅以下代码:
>>>import scipy as sp
>>>cashflows=[-100,50,60,70]
>>>rate=0.1
>>>npv=sp.npv(rate,cashflows)
>>>round(npv,2)
47.62
scipy.npv()
函数估算给定现金流集的现值。第一个输入变量是折现率,第二个输入是现金流数组。注意,这个现金流数组中的第一个现金流发生在时间零。这个 scipy.npv()
函数不同于 Excel 的 NPV 函数,后者并不是一个真正的 NPV 函数。实际上,Excel 的 NPV 是一个 PV 函数。它通过假设第一个现金流发生在第一个期间结束时,来估算未来现金流的现值。使用 Excel npv()
函数的示例如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_04.jpg
在仅使用一个未来现金流时,scipy.npv()
函数的意义通过以下代码行变得更加清晰:
>>>c=[100]
>>>x=np.npv(0.1,c)
>>>round(x,2)
>>>100.0
相关的 Excel 函数及其输出如图所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_05.jpg
对于只有一个未来现金流,基于 Excel 的 npv()
函数的结果如前图所示。对于 numpy.lib.financial.npv()
函数,唯一的现金流 $100 会发生在今天,而对于 Excel 的 npv()
函数,唯一的现金流 $100 会发生在一个期间后。因此,100/(1+0.1) 得出 90.91。
NPV 规则如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_45.jpg
IRR 和 IRR 规则的定义
内部收益率(IRR)被定义为使 NPV 等于零的折现率。假设我们今天投资 $100,未来四年的现金流分别为 $30、$40、$40 和 $50。假设所有现金流都发生在年底,那么该投资的 IRR 是多少?在以下程序中,应用了 scipy.irr()
函数:
>>>import scipy as sp
>>> cashflows=[-100,30,40,40,50]
>>> sp.irr(cashflows)
0.2001879105140867
我们可以验证这样的利率是否使 NPV 等于零。由于 NPV 为零,20.02% 确实是一个 IRR:
>>> r=sp.irr(cashflows)
>>> sp.npv(r,cashflows)
1.7763568394002505e-14
>>>
对于一个正常项目,IRR 规则如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_46.jpg
这里,Rc 是资本成本。这个 IRR 规则仅适用于正常项目。我们来看下面的投资机会。今天的初始投资是 $100,明年的投资是 $50。未来五年的现金流入分别为 $50、$70、$100、$90 和 $20。如果资本成本是 10%,我们应该接受这个项目吗?时间线及相应的现金流如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_47.jpg
这里给出了 Python 代码:
>>>import scipy as sp
>>> cashflows=[-100,-50,50,70,100,90,20]
>>> sp.irr(cashflows)
0.25949919326073245
由于 IRR 为 25.9%,高于 10% 的资本成本,我们应该根据 IRR 规则接受该项目。在前面的例子中,这是一个正常项目。对于不正常项目或具有多个 IRR 的项目,我们不能应用 IRR 规则。当现金流方向发生超过一次变化时,我们可能会有多个 IRR。假设我们的现金流为 504
,-432
,-432
,-432
和 843
,从今天开始:
>>>import scipy as sp
>>> cashflows=[504, -432,-432, -432,843]
>>> sp.irr(cashflows)
0.14277225152187745
相关图表如图所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_06.jpg
由于我们的现金流方向发生了两次变化,该项目可能会有两个不同的 IRR。前述右侧的图像显示了这是这种情况。对于 Python 程序绘制前述的 NPV 曲线,参见附录 F。使用spicy.npv()
函数,我们只得到了一个 IRR。从fincal.IRRs_f()
函数中,我们可以得到两个 IRR;请参见以下代码:
>>>import fincal
>>> cashflows=[504, -432,-432, -432,843]
>>> fincal.IRRs_f(cashflows)
[0.143, 0.192]
回收期和回收期规则的定义
回收期定义为收回初始投资所需的年数。假设初始投资为$100。如果每年公司能够回收$30,那么回收期为 3.3 年:
>>import fincal
>>>cashflows=[-100,30,30,30,30,30]
>>> fincal.payback_period(cashflows)
3.3333333333333335
回收期规则的决策规则如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_48.jpg
在这里,T是项目的回收期,而Tc是收回初始投资所需的最大年数。因此,如果Tc是四年,那么回收期为 3.3 年的前述项目应该被接受。
回收期规则的主要优点是其简单性。然而,这种规则也存在许多缺点。首先,它没有考虑货币的时间价值。在前面的案例中,第一年末收到的$30 与今天收到的$30 是一样的。其次,回收期之后的任何现金流都被忽略了。这种偏差不利于具有较长未来现金流的项目。最后但同样重要的是,没有理论基础来定义一个好的Tc截止点。换句话说,没有合理的理由来解释为什么四年的截止点比五年更好。
用 Python 编写你自己的财务计算器
当一个新的 Python 学习者能够编写自己的财务计算器时,这可以视为一个巨大的成就。做到这一点的基本知识包括以下内容:
如何编写一个函数的知识
相关的财务公式是什么?
对于后者,我们已经从前面的章节中学到了一些内容,比如计算单一未来现金流现值的公式。现在我们来写一个最简单的 Python 函数来对输入值进行加倍:
def dd(x):
return 2*x
这里,def
是编写函数的关键字,dd
是函数名,括号中的x
是输入变量。对于 Python,缩进是至关重要的。前面的缩进表示第二行是dd
函数的一部分。调用这个函数与调用其他内建的 Python 函数相同:
>>>dd(5)
10
>>>dd(3.42)
6.84
现在,让我们编写最简单的财务计算器。首先,启动 Python 并使用其编辑器输入以下代码:
def pvFunction(fv,r,n):
return fv/(1+r)**n
def pvPerpetuity(c,r):
return c/r
def pvPerpetuityDue(c,r):
return c/r*(1+r)
为了简单起见,前述三个函数中的每个函数只有两行代码。在通过运行整个程序激活这些函数后,可以使用dir()
函数来显示它们的存在:
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'pvFunction', 'pvPerpetuity','pvPerpetuityDue']
>>>
调用这个自生成的财务计算器是微不足道的;请参见以下代码:
>>> pvFunction(100,0.1,1)
90.9090909090909
>>> pvFunction(n=1,r=0.1,fv=100)
90.9090909090909
>>> pvFunction(n=1,fv=100,r=0.1)
90.9090909090909
>>>
同样,在输入值时,可以使用两种方法:输入变量的含义取决于它们的顺序,见第一次调用;或者使用关键字,见最后两个示例。
编写个人财务计算器的一种更优雅的方法见于附录 G。
多个函数的两个通用公式
这一节是可选的,因为在数学表达式上比较复杂。跳过这一节不会影响对其他章节的理解。因此,这一节适合进阶学习者。目前为止,在本章中,我们已经学习了 SciPy 模块或 numpy.lib.financial
子模块中包含的几个函数的使用方法,如 pv()
、fv()
、nper()
、pmt()
和 rate()
。第一个通用公式与现值有关:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_49.jpg
在前述公式的右侧,第一个部分是单个未来现金流的现值,而第二部分是年金的现值。变量type的值为零(默认值);它表示普通年金的现值,而如果type的值为 1,则表示到期年金。负号用于符号约定。如果使用与 SciPy 和 numpy.lib.financial
中函数相同的符号,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_50.jpg
这里有几个使用公式(14)和 pv()
函数(来自 SciPy)示例。James 打算今天投资 x 美元,投资期限为 10 年。他的年回报率是 5%。在接下来的 10 年里,他将在每年年初提取 5,000 美元。此外,他希望在投资期满时能有 7,000 美元。今天他必须投资多少,即 x 的值是多少?通过手动应用前述公式,我们得到了以下结果。请注意负号:
>>> -(7000/(1+0.05)**10 + 5000/0.05*(1-1/(1+0.05)**10)*(1+0.05))
-44836.501153005614
结果与调用 scipy.pv()
函数时相同;见以下代码:
>>> import scipy as sp
>>> sp.pv(0.05,10,5000,7000,1)
-44836.5011530056
为了将普通年金与到期年金分开,我们有以下两个公式。对于普通年金,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_51.jpg
对于到期年金,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_52.jpg
类似地,对于未来值,我们有以下通用公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_53.jpg
如果使用与 SciPy 和 numpy.lib.financial
中相同的符号,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_54.jpg
类似地,我们可以将年金与到期年金分开。对于普通年金,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_55.jpg
对于到期年金,我们有以下公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_56.jpg
在以下方程中,现值(pv)出现了两次。然而,它们的含义大不相同。同样,未来值也出现了两次,且含义不同:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_57.jpg
让我们用一个简单的例子来解释这两个方程之间的联系。首先,通过去掉符号约定来简化我们的函数,并假设是普通年金,即不考虑期初年金:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_58.jpg
实际上,我们将有三个pv(现值)和三个fv(未来值)。我们投资$100,投资期为三年。此外,在接下来的三年里,每年年底我们再投资$20。如果年回报率为 4%,那么我们的投资的未来值是多少?
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_59.jpg
显然,我们可以应用最后一个方程来得出我们的答案:
>>> 100*(1+0.04)**3+20/0.04*((1+0.04)**3-1)
174.91840000000005
>>> import scipy as sp
>>> sp.fv(0.04,3,20,100)
-174.91840000000005
实际上,我们有三个未来值。我们将它们称为FV(total)、FV( annuity) 和 FV(one PV)。它们之间的关系如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_60.jpg
以下代码展示了如何计算年金的未来值和一个现值的未来值:
>>> fv_annuity=20/0.04*((1+0.04)**3-1)
>>> fv_annuity
62.432000000000045
>>>fv_one_PV=100*(1+0.04)**3
>>> fv_one_PV
112.4864
总未来值是这两个未来值的总和:62.4320+ 112.4864=174.92。现在,让我们看看如何得到三个对应的现值。我们将它们称为PV(total)、PV( annuity) 和 PV(one PV)。它们之间的关系如下:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_61.jpg
我们使用之前展示的相同现金流。显然,第一个$100 本身就是现值。三个$20 的现值可以手动计算;请参见以下代码:
>>>20/0.04*(1-1/(1+0.04)**3)
55.501820664542564
因此,总现值将是100 + 55.51=155.51。另外,我们可以应用 scipy.pv()
来估算年金的现值;请参见以下代码:
>>>import scipy as sp
>>> sp.pv(0.04,3,20)
-55.501820664542592
>>>import fincal
>>> fincal.pvAnnuity(0.04,3,20)
55.501820664542564
总未来值(174.92
)和总现值(155.51
)之间的关系如下:
>>>174.92/(1+0.04)**3
155.5032430587164
总结一下,当调用 scipy.pv()
和 scipy.fv()
函数时,scipy.pv()
函数中的 fv
含义与 scipy.fv()
函数中的最终值是不同的。读者需要理解总未来值、一个现值的未来值和年金的未来值之间的区别。这对于 scipy.fv()
函数中的 pv
变量和调用 scipy.pv()
函数后的最终结果也适用。
附录 A – Python、NumPy 和 SciPy 的安装
通过 Anaconda 安装 Python,我们需要以下步骤:
-
访问
continuum.io/downloads
。 -
找到合适的软件包;请参见以下截图:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_07.jpg
对于 Python,不同版本并存。从前面的截图中,我们可以看到存在 3.5 和 2.7 两个版本。对于本书来说,版本并不是那么关键。旧版本问题较少,而新版本通常有新的改进。在通过 Anaconda 安装 Python 后,NumPy 和 SciPy 将同时安装。启动 Python 并通过 Spyder 发出以下两行命令。如果没有错误,说明这两个模块已预安装:
>>> import numpy as np
>>> import scipy as sp
另一种方法是直接安装 Python。
访问 www.python.org/download
。根据你的计算机,选择合适的安装包,例如 Python 3.5.2 版本。关于安装模块,请查阅 Python 文档。以下命令将从Python 包索引(PIP)安装模块及其依赖项的最新版本:
python -m pip install SomePackage
对于 POSIX 用户(包括 Mac OS X 和 Linux 用户),本指南中的示例假定使用虚拟环境。要安装特定版本,请参见以下代码:
python -m pip install SomePackage==1.0.4 # specific version
python -m pip install "SomePackage>=1.0.4" # minimum version
通常,如果已经安装了适当的模块,再次尝试安装它不会有任何效果。升级现有模块必须明确请求:
python -m pip install --upgrade SomePackage
附录 B – 时间价值的直观展示
如果读者在理解以下代码时有困难,可以忽略这一部分。在金融学中,我们知道今天收到的 $100 比一年后收到的 $100 更有价值。如果我们用大小来表示差异,我们可以通过以下 Python 程序来表示相同的概念:
from matplotlib.pyplot import *
fig1 = figure(facecolor='white')
ax1 = axes(frameon=False)
ax1.set_frame_on(False)
ax1.get_xaxis().tick_bottom()
ax1.axes.get_yaxis().set_visible(False)
x=range(0,11,2)
x1=range(len(x),0,-1)
y = [0]*len(x);
name="Today's value of $100 received today"
annotate(name,xy=(0,0),xytext=(2,0.001),arrowprops=dict(facecolor='black',shrink=0.02))
s = [50*2.5**n for n in x1];
title("Time value of money ")
xlabel("Time (number of years)")
scatter(x,y,s=s);
show()
这里显示了图表。第一个蓝色圆圈是现值,而第二个是同样 $100 在第二年末的现值:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_08.jpg
附录 C – 从未来现金流的现值和永久年金的现值推导年金现值
首先,我们有以下两个公式:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_62.jpg
这里,FV 是未来值,R 是折现期利率,n 是期数,C 是在每个期末发生的相同现金流,第一个现金流发生在第一期末。
年金被定义为一组未来发生的等值现金流。如果第一个现金流发生在第一期末,则年金的现值由以下公式给出:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_63.jpg
这里,C是每期末发生的递归现金流,R是期的折现率,n是期数。*方程(3)*相当复杂。然而,稍加想象,我们可以通过将方程(1)和(2)结合,推导出方程(3)。这可以通过将年金分解为两个永续年金来完成:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_64.jpg
这相当于以下两个永续年金:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_65.jpg
从概念上讲,我们可以这样考虑:玛丽将在未来 10 年内每年收到 20 美元。这相当于两个永续年金:她将永远每年收到 20 美元,并且从第 11 年开始,每年支付 20 美元。因此,她的年金现值将是第一个永续年金的现值减去第二个永续年金的现值:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_66.jpg
如果相同的现金流在相同的时间间隔内永远发生,则称为永续年金。如果折现率是恒定的,并且第一笔现金流发生在第一个周期结束时,则其现值如下所示。
附录 D – 如何下载免费财务计算器
可执行文件位于canisius.edu/~yany/fincal.pyc
。假设它保存在c:/temp/
中。更改你的路径;请参见以下截图:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_09.jpg
这是一个例子:
>>>import fincal
>>> fincal.pv_f(0.1,1,100)
90.9090909090909
要找出所有包含的函数,可以使用dir()
函数;请参见以下代码:
>>> import fincal
>>> dir(fincal)
['CND', 'EBITDA_value', 'IRR_f', 'IRRs_f', 'NPER', 'PMT', 'Rc_f', 'Rm_f', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__request', '__spec__', 'bondPrice', 'bsCall', 'convert_B_M', 'duration', 'exp', 'fincalHelp', 'fvAnnuity', 'fvAnnuityDue', 'fv_f', 'get_200day_moving_avg', 'get_50day_moving_avg', 'get_52week_high', 'get_52week_low', 'get_EBITDA', 'get_all', 'get_avg_daily_volume', 'get_book_value', 'get_change', 'get_dividend_per_share', 'get_dividend_yield', 'get_earnings_per_share', 'get_historical_prices', 'get_market_cap', 'get_price', 'get_price_book_ratio', 'get_price_earnings_growth_ratio', 'get_price_earnings_ratio', 'get_price_sales_ratio', 'get_short_ratio', 'get_stock_exchange', 'get_volume', 'log', 'market_cap', 'mean', 'modified_duration', 'n_annuity', 'npv_f', 'payback_', 'payback_period', 'pi', 'pvAnnuity', 'pvAnnuityDue', 'pvAnnuity_k_period_from_today', 'pvGrowingAnnuity', 'pvGrowingPerpetuity', 'pvPerpetuity', 'pvPerpetuityDue', 'pv_excel', 'pv_f', 'r_continuous', 're', 'sign', 'sqrt', 'urllib']
要了解每个函数的用途,可以使用help()
函数:
>>> help(fincal.pv_f)
Help on function pv_f in module fincal:
pv_f(r, n, fv)
Objective: estimate present value
r : period rate
n : number of periods
fv : future value
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_67.jpg
Example 1: >>>pv_f(0.1,1,100) # meanings of input variables
90.9090909090909 # based on their input order
Example #2 >>>pv_f(r=0.1,fv=100,n=1) # meanings based on keywords
90.9090909090909
>> >
附录 E – NPV 与 R 关系的图形表示
NPV 曲线是项目的净现值与其折现率(资本成本)之间的关系。对于正常项目(即现金流先为支出后为收入),其净现值将是折现率的递减函数;请参见以下代码:
import scipy as sp
from matplotlib.pyplot import *
cashflows=[-120,50,60,70]
rate=[]
npv =[]
for i in range(1,70):
rate.append(0.01*i)
npv.append(sp.npv(0.01*i,cashflows))
plot(rate,npv)
show()
相关图表如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_10.jpg
为了使我们的图表更好,我们可以添加标题、标签以及一条水平线;请参见以下代码:
import scipy as sp
from matplotlib.pyplot import *
cashflows=[-120,50,60,70]
rate=[]
npv=[]
x=(0,0.7)
y=(0,0)
for i in range(1,70):
rate.append(0.01*i)
npv.append(sp.npv(0.01*i,cashflows))
title("NPV profile")
xlabel("Discount Rate")
ylabel("NPV (Net Present Value)")
plot(rate,npv)
plot(x,y)
show()
输出如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_11.jpg
附录 F – 带有两个 IRR 的净现值(NPV)曲线图
由于现金流的方向发生了两次变化,我们可能会有两个内部收益率(IRR):
import scipy as sp
import matplotlib.pyplot as plt
cashflows=[504,-432,-432,-432,832]
rate=[]
npv=[]
x=[0,0.3]
y=[0,0]
for i in range(1,30):
rate.append(0.01*i)
npv.append(sp.npv(0.01*i,cashflows))
plt.plot(x,y),plt.plot(rate,npv)
plt.show()
相应的图表如下所示:
https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_12.jpg
附录 G – 用 Python 编写你自己的财务计算器
现在,让我们编写我们最简单的财务计算器。首先,启动 Python 并使用编辑器输入以下代码。为简单起见,每个前面 10 个函数的函数体只有两行。再次强调,正确的缩进非常重要。因此,每个函数的第二行应该有缩进:
def pvFunction(fv,r,n):
return fv/(1+r)**n
def pvPerpetuity(c,r):
return c/r
def pvPerpetuityDue(c,r):
return c/r*(1+r)
def pvAnnuity(c,r,n):
return c/r*(1-1/(1+r)**n)
def pvAnnuityDue(c,r,n):
return c/r*(1-1/(1+r)**n)*(1+r)
def pvGrowingAnnuity(c,r,n,g):
return c/(r-g)*(1-(1+g)**n/(1+r)**n)
def fvFunction(pv,r,n):
return pv*(1+r)**n
def fvAnnuity(cv,r,n):
return c/r*((1+r)**n-1)
def fvAnnuityDue(cv,r,n):
return c/r*((1+r)**n-1)*(1+r)
def fvGrowingAnnuity(cv,r,n):
return c/(r-g)*((1+r)**n-(1+g)*n)
假设前面的程序名为myCalculator
。
以下程序将生成一个可执行文件,名为myCalculator.cpython-35.pyc
:
>>> import py_compile
>>> py_compile.compile('myCalculator.py')
'__pycache__\\myCalculator.cpython-35.pyc'
>>> __pycache__
py_compile.compile('c:/temp/myCalculator.py')
习题
-
如果年折现率为 2.5%,那么 10 年后收到 206 美元的现值是多少?
-
永续年金的未来价值是多少,如果年付款为 1 美元,年折现率为 2.4%?
-
对于一个普通项目,其净现值(NPV)与折现率负相关,为什么?
-
约翰在银行存入 5,000 美元,存期 25 年。如果年利率为每年 0.25%,未来值是多少?
-
如果年付款为 55 美元,剩余期限为 20 年,年折现率为 5.41%,按半年复利计算,现值是多少?
-
如果玛丽计划在第 5 年末拥有 2,400 美元,她每年需要存多少钱,如果对应的年利率为 3.12%?
-
为什么在以下代码中我们得到了负的期数?
>>>import scipy as sp >>> sp.nper(0.012,200,5000,0) -21.99461003591637
-
如果一家公司每股收益从 2 美元增长到 4 美元,增长周期为 9 年(总增长为 100%),其年增长率是多少?
-
在本章中,在编写现值函数时,我们使用了
pv_f()
。为什么不使用pv()
,它和以下公式一样呢?!习题这里,PV 是现值,FV 是未来值,R 是周期折现率,n 是期数。
-
一个项目在第一年和第二年分别产生 5,000 美元和 8,000 美元的现金流入。初期投资为 3,000 美元。第一年和第二年的折现率分别为 10%和 12%。该项目的净现值(NPV)是多少?
-
A 公司将发行年票息为 80 美元,面值为 1,000 美元的新债券。利息支付为半年付,债券将在 2 年后到期。第一年的现货利率为 10%。第一年末,1 年期现货利率预计为 12%:
-
债券的现值是多少?
-
如果你愿意在第二年末接受一次性支付的金额是多少?
-
彼得的富有叔叔承诺如果他在四年内完成大学学业,将支付他 4,000 美元。理查德刚刚完成了非常艰难的二年级(大二),包括修读几门金融课程。理查德非常希望能够享受一个长假。适当的折现率为 10%,半年复利。彼得如果现在去度假,将放弃什么价值?
-
今天,您有 5000 美元可供投资,投资期限为 25 年。您被提供一个投资计划,未来 10 年每年回报 6%,接下来的 15 年每年回报 9%。在 25 年结束时,您将拥有多少资金?您的年平均回报率是多少?
-
使用默认输入值的优点和缺点是什么?
-
我们知道,增长永续年金的现值公式为:https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/py-fin-2e/img/B06175_03_69.jpg
证明这一点。
-
今天,简已经 32 岁了。她计划在 65 岁时退休,届时储蓄达到 250 万美元。如果她每年能获得 3.41%的复利回报(按月复利),她每月需要存多少钱?
-
假设我们有一组小程序组成的文件,名为
fin101.py
。import fin101
和from fin101 import *
这两个 Python 命令有什么区别? -
如何防止输入错误,如负利率?
-
编写一个 Python 程序来估算回收期。例如,初始投资为 256 美元,预计未来 7 年内的现金流分别为 34 美元、44 美元、55 美元、67 美元、92 美元、70 美元和 50 美元。该项目的回收期是多少年?
-
在前面的练习中,如果贴现率是每年 7.7%,则折现回收期是多少?注意:折现回收期是通过检查未来现金流的现值之和来计算如何收回初始投资。
摘要
本章介绍了许多与金融相关的基本概念,如单一未来现金流的现值、永续年金的现值、年金的现值、单一现金流/年金的未来值,以及应付年金现值的概念。详细讨论了几种决策规则,如净现值(NPV)规则、内部收益率(IRR)规则和回收期规则。在下一章中,我们将讨论如何从几个开放资源(如 Yahoo!Finance、Google Finance、Prof. French 的数据图书馆和联邦研究的经济数据图书馆)中检索与经济学、金融学和会计学相关的数据。
作者:绝不原创的飞龙