Python 金融编程第二版(一)

2024-06-10 08:19:03 浏览数 (2)

原文:annas-archive.org/md5/d2f94efd019a2e2cb5c4fa9f260d63c 译者:飞龙 协议:CC BY-NC-SA 4.0

第一部分:Python 与金融

本部分介绍了 Python 在金融领域的应用。它包括三章:

  • 第一章简要讨论了 Python 的一般情况,并论述了为什么 Python 确实非常适合应对金融行业和金融(数据)分析中的技术挑战。
  • 第二章关于 Python 基础设施,旨在简要概述管理 Python 环境的重要方面,以便开始使用 Python 进行交互式金融分析和金融应用程序开发。

第一章:为什么要用 Python 进行金融?

银行本质上是科技公司。 雨果·班齐格

什么是 Python?

Python 是一种高级、通用的编程语言,广泛应用于各个领域和技术领域。在 Python 网站上,您可以找到以下执行摘要(参见 https://www.python.org/doc/essays/blurb):

Python 是一种解释性的、面向对象的、高级的编程语言,具有动态语义。它的高级内置数据结构,结合动态类型和动态绑定,使其非常适合快速应用程序开发,以及用作脚本语言或粘合语言将现有组件连接在一起。Python 简单、易学的语法强调可读性,从而降低了程序维护的成本。Python 支持模块和包,这鼓励了程序的模块化和代码的重用。Python 解释器和广泛的标准库可以在所有主要平台上免费获得源代码或二进制形式,并可以自由分发。

这很好地描述了为什么 Python 已经发展成为今天的主要编程语言之一。现在,Python 不仅被初学者程序员使用,还被高技能专家开发者使用,在学校,在大学,在网络公司,在大型企业和金融机构以及在任何科学领域都有应用。

Python 具有以下特点之一:

开源

Python 及其大多数支持库和工具都是开源的,并且通常具有相当灵活和开放的许可证。

解释性的

参考 CPython 实现是一种语言的解释器,它将 Python 代码在运行时转换为可执行字节码。

多范式

Python 支持不同的编程和实现范式,如面向对象和命令式、函数式或过程式编程。

多用途

Python 可用于快速、交互式的代码开发,也可用于构建大型应用程序;它可用于低级系统操作,也可用于高级分析任务。

跨平台

Python 可用于最重要的操作系统,如 Windows、Linux 和 Mac OS;它用于构建桌面应用程序和 Web 应用程序;它可以用于最大的集群和最强大的服务器,也可以用于树莓派等小型设备(参见 http://www.raspberrypi.org)。

动态类型

Python 中的类型通常在运行时推断,而不是像大多数编译语言中静态声明的那样。

缩进感知

与大多数其他编程语言不同,Python 使用缩进来标记代码块,而不是使用括号、方括号或分号。

垃圾收集

Python 具有自动化垃圾收集,避免了程序员管理内存的需要。

当涉及到 Python 语法和 Python 的本质时,Python Enhancement Proposal 20——即所谓的“Python 之禅”提供了主要的指导原则。可以通过每个交互式 shell 的命令import this访问它:

代码语言:javascript复制
In [1]: import this

        The Zen of Python, by Tim Peters

        Beautiful is better than ugly.
        Explicit is better than implicit.
        Simple is better than complex.
        Complex is better than complicated.
        Flat is better than nested.
        Sparse is better than dense.
        Readability counts.
        Special cases aren't special enough to break the rules.
        Although practicality beats purity.
        Errors should never pass silently.
        Unless explicitly silenced.
        In the face of ambiguity, refuse the temptation to guess.
        There should be one-- and preferably only one --obvious way to do it.
        Although that way may not be obvious at first unless you're Dutch.
        Now is better than never.
        Although never is often better than *right* now.
        If the implementation is hard to explain, it's a bad idea.
        If the implementation is easy to explain, it may be a good idea.
        Namespaces are one honking great idea -- let's do more of those!

Python 的简要历史

尽管对于一些人来说,Python 可能仍然具有一些新鲜感,但它已经存在了相当长的时间。事实上,Python 的开发工作始于 1980 年代,由来自荷兰的 Guido van Rossum 负责。他仍然活跃于 Python 的开发,并且被 Python 社区授予了终身仁慈独裁者的称号(参见http://en.wikipedia.org/wiki/History_of_Python)。以下可以被视为 Python 发展的里程碑:

  • Python 0.9.0 发布于 1991 年(第一个版本)
  • Python 1.0 发布于 1994 年
  • Python 2.0 发布于 2000 年
  • Python 2.6 发布于 2008 年
  • Python 3.0 发布于 2008 年
  • Python 3.1 发布于 2009 年
  • Python 2.7 发布于 2010 年
  • Python 3.2 发布于 2011 年
  • Python 3.3 发布于 2012 年
  • Python 3.4 发布于 2014 年
  • Python 3.5 发布于 2015 年
  • Python 3.6 发布于 2016 年

令人瞩目的是,对于 Python 新手来说,Python 有两个主要版本可用,自 2008 年以来仍在开发中,并且更重要的是,同时使用。截至撰写本文时,这种情况可能会持续一段时间,因为大量的代码仍然是基于 Python 2.6/2.7 并且在生产中使用。尽管本书的第一版是基于 Python 2.7 编写的,但本书的第二版全程使用的是 Python 3.6。

Python 生态系统

Python 作为一个生态系统的一个主要特点,与仅仅是一种编程语言相比,就是有大量的包和工具可用。这些包和工具通常在需要时必须被导入(例如,绘图库),或者必须作为一个单独的系统进程启动(例如,Python 开发环境)。导入意味着使一个包对当前的命名空间和当前的 Python 解释器进程可用。

Python 本身已经带有大量的包和模块,可以增强基本的解释器。人们谈论的是Python 标准库(参见https://docs.python.org/3/library/index.html)。例如,可以进行基本的数学计算而无需任何导入,而更专业的数学函数需要通过math模块导入:

代码语言:javascript复制
In [2]: 100 * 2.5   50
Out[2]: 300.0

In [3]: log(1)

        ----------------------------------------
        NameErrorTraceback (most recent call last)
        <ipython-input-3-cfa4946d0225> in <module>()
----> 1 log(1)

        NameError: name 'log' is not defined

In [4]: import math

In [5]: math.log(1)
Out[5]: 0.0

虽然 math 是一个标准的 Python 库,可以在任何安装中使用,但还有许多其他可选安装的库,可以像标准库一样使用。这些库可从不同的(网络)来源获取。然而,通常建议使用一个 Python 软件包管理器,以确保所有库都与彼此一致(有关此主题的更多信息,请参见第二章)。

到目前为止所提供的代码示例都使用了IPython(请参见http://www.ipython.org),这是 Python 最受欢迎的交互式开发环境(IDE)之一。尽管它最初只是一个增强的 shell,但今天它具有许多通常在 IDE 中找到的特性(例如,支持分析和调试)。那些缺失的功能通常由高级文本/代码编辑器提供,如 Sublime Text(请参见http://www.sublimetext.com)。因此,将IPython与个人选择的文本/代码编辑器结合起来形成 Python 开发过程的基本工具集并不罕见。

IPython 在许多方面增强了标准的交互式 shell。例如,它提供了改进的命令行历史功能,并允许轻松地检查对象。例如,只需在函数名称前后添加?(添加??将提供更多信息),就可以打印函数的帮助文本(docstring)。

IPython 最初有两个流行版本:一个是 shell 版本,另一个是 基于浏览器的版本Notebook)。Notebook 变体已被证明非常有用和受欢迎,因此它现在已成为一个独立的、与语言无关的项目和工具,现在称为 Jupyter(见http://jupyter.org)。

Python 用户光谱

Python 不仅吸引专业软件开发人员;它也对业余开发人员以及领域专家和科学开发人员有用。

专业软件开发人员可以找到他们构建大型应用程序所需的一切。几乎支持所有编程范式;有强大的开发工具可用;并且原则上任何任务都可以通过 Python 来解决。这些类型的用户通常构建自己的框架和类,也在基本的 Python 和科学堆栈上工作,并努力充分利用生态系统。

科学开发人员领域专家通常是某些库和框架的重度用户,已经构建了自己的应用程序,并随着时间的推移对其进行了增强和优化,并根据自己的特定需求定制了生态系统。这些用户群通常参与更长时间的交互式会话,快速原型化新代码,并探索和可视化他们的研究和/或领域数据集。

业余程序员 喜欢使用 Python 通常是因为他们知道 Python 在特定问题上有优势。例如,访问 matplotlib 的画廊页面,复制那里提供的某个可视化代码片段,并根据他们的特定需求调整代码,可能是这个群体的成员的有益用例。

还有另一重要的 Python 用户群体:初学者程序员,即那些刚开始学习编程的人。如今,Python 已经成为大学、学院甚至学校介绍编程给学生的非常流行的语言之一。¹ 这主要是因为它的基本语法易于学习和理解,即使对于非开发者也是如此。此外,有帮助的是 Python 几乎支持所有的编程风格。²

科学堆栈

有一组被统称为 科学堆栈 的库。这个堆栈包括,但不限于,以下几个包:

NumPy

NumPy 提供了一个多维数组对象来存储同构或异构数据;它还提供了优化的函数/方法来操作这个数组对象。

SciPy

SciPy 是一个子包和函数的集合,实现了科学或金融中经常需要的重要标准功能;例如,可以找到用于三次样条插值以及数值积分的函数。

matplotlib

这是 Python 中最受欢迎的绘图和可视化库,提供了 2D 和 3D 可视化功能。

PyTables

PyTablesHDF5 数据存储库的流行封装器(参见http://www.hdfgroup.org/HDF5/);它是一个用于实现基于磁盘的优化 I/O 操作的库,基于分层数据库/文件格式。

pandas

pandas 建立在 NumPy 之上,提供了更丰富的类来管理和分析时间序列和表格数据;它与 matplotlib 紧密集成用于绘图和 PyTables 用于数据存储和检索。

Scikit-Learn

Scikit-Learn 是一个流行的机器学习(ML)包,为许多不同的 ML 算法提供了统一的 API,例如,用于估计、分类或聚类的算法。

根据特定的领域或问题,这个堆栈会通过额外的库进行扩展,这些库往往有一个共同点,即它们建立在一个或多个基本库的基础之上。然而,在一般情况下,最常见的最小公分母或基本构建块是 NumPyndarray 类(参见 第四章),或者现在是 pandasDataFrame 类(参见 第五章)。

单纯将 Python 视为一种编程语言,还有许多其他语言可供选择,可能与其语法和优雅程度相媲美。例如,Ruby 是一种相当流行的语言,经常与 Python 进行比较。在该语言的网站 http://www.ruby-lang.org 上,您会找到以下描述:

一种以简洁和高效为重点的动态、开源编程语言。它具有优雅的语法,自然易读且易于编写。

大多数使用 Python 的人可能也同意对 Python 本身作出完全相同的声明。然而,与 Ruby 等同样吸引人的语言相比,许多用户认为 Python 的特点在于科学堆栈的可用性。这使得 Python 不仅是一种好的、优雅的语言,还能够替代类似 Matlab 或 R 这样的领域特定语言和工具集。它默认提供了你所期望的任何东西,比如一位经验丰富的 Web 开发人员或系统管理员。此外,Python 也擅长与领域特定语言(如 R)进行接口交互,因此通常决策并不是关于“要么选择 Python 要么选择其他语言”,而是关于选择哪种语言作为主要语言。

金融科技

现在我们对 Python 的大致了解有了一些想法,稍微退后一步,简要思考一下技术在金融中的作用是有意义的。这将使我们能够更好地评判 Python 已经扮演的角色,甚至更重要的是,可能会在未来的金融行业中发挥的作用。

在某种意义上,技术本身对金融机构(例如,与工业公司相比)或金融职能(例如,与物流等其他企业职能相比)并不“特别”。然而,近年来,在创新和监管的推动下,银行和其他金融机构(如对冲基金)已经越来越多地演变成了技术公司,而不仅仅是“仅仅”是金融中介。技术已经成为全球几乎所有金融机构的重要资产,具有带来竞争优势以及劣势的潜力。一些背景信息可以阐明这一发展的原因。

技术支出

银行和金融机构共同构成了年度技术支出最多的行业。因此,以下声明不仅显示了技术对金融业的重要性,而且金融业对技术行业也非常重要(www.idc.com):

…,截至 2016 年,全球金融服务 IT 支出将达到近 4800 亿美元,年复合增长率(CAGR)为 4.2%。 IDC

特别是,银行和其他金融机构正在竞相将业务和运营模式转移到数字化基础上 (www.statista.com):

预计 2017 年北美银行对新技术的支出将达到 199 亿美元。 银行开发当前系统并致力于新技术解决方案,以增强其在全球市场上的竞争力并吸引对新在线和移动技术感兴趣的客户。这对于为银行业提供新想法和软件解决方案的全球金融科技公司来说是一个巨大的机遇。 Statista

当今,大型跨国银行通常雇佣数千名开发人员来维护现有系统并构建新系统。

技术作为推动因素

技术发展也促进了金融领域的创新和效率提升:

金融服务行业在过去几年里经历了技术引领的剧变。许多高管指望他们的 IT 部门提高效率并促进游戏改变式的创新,同时以某种方式降低成本并继续支持遗留系统。与此同时,金融科技初创企业正在侵入已建立的市场,以客户友好的解决方案为首,这些解决方案从零开始开发,并且不受遗留系统的束缚。 《普华永道第 19 届全球 CEO 调查报告》(2016 年)

随着效率日益提高,寻找竞争优势往往要求在越来越复杂的产品或交易中寻找。这反过来会增加风险,并使风险管理以及监督和监管变得越来越困难。2007 年和 2008 年的金融危机讲述了由此类发展带来的潜在危险。同样,“算法和计算机失控”也代表了对金融市场的潜在风险;这在 2010 年 5 月的所谓闪电崩盘中得到了戏剧性的体现,那时自动卖出导致某些股票和股票指数在一天内大幅下跌(参见http://en.wikipedia.org/wiki/2010_Flash_Crash)。

技术和人才作为进入壁垒

一方面,技术的进步会随着时间的推移降低成本,其他条件不变。另一方面,金融机构继续大力投资于技术,以获得市场份额并捍卫其当前地位。要在今天的某些金融领域活跃起来,通常需要进行大规模的技术和人才投资。例如,考虑一下衍生品分析领域:

在总体软件生命周期中,采用内部策略进行场外[衍生品]定价的公司,仅建立、维护和增强完整的衍生品库就需要投资额在25 百万到36 百万之间。 Ding 2010

建立完整的衍生品分析库不仅成本高昂且耗时,你还需要足够的专家来完成这项工作。而且这些专家必须有适当的工具和技术来完成他们的任务。

另一则关于长期资本管理(LTCM)早期的报价支持了关于技术和人才的这一见解:LTCM 曾是最受尊敬的量化对冲基金之一,然而在 1990 年代末破产。

Meriwether 花费了$20 百万在格林威治,康涅狄格州建立了一套最先进的计算机系统,并雇佣了一支精英金融工程团队来运作 LTCM,这是工业级风险管理。 Patterson 2010

Meriwether 为数百万美元购买的同样计算能力,如今可能只需数千美元就能获得。另一方面,对于更大的金融机构来说,交易、定价和风险管理已变得如此复杂,以至于今天它们需要部署拥有数万计算核心的 IT 基础设施。

日益增加的速度、频率和数据量

金融行业的一个维度受到技术进步影响最大:金融交易的决策和执行速度频率。Lewis(2014 年)的新书详细描述了所谓的闪电交易—即以最高速度进行交易。

一方面,不断增加的数据可用性在越来越小的尺度上使得实时反应变得必要。另一方面,交易速度和频率的增加使得数据量进一步增加。这导致了一些过程相互强化,并将金融交易的平均时间尺度系统性地推向下降:

Renaissance 的 Medallion 基金在 2008 年惊人地增长了 80%,利用其闪电般快速的计算机资本化市场极端波动。吉姆·西蒙斯成为当年对冲基金界的最高收入者,赚得了 25 亿美元。 Patterson 2010

单支股票的三十年日度股价数据大约包括 7,500 个报价。今天大部分金融理论基于这种数据。例如,现代投资组合理论(MPT)、资本资产定价模型(CAPM)和风险价值(VaR)理论都基于日度股价数据。

相比之下,在典型的交易日,苹果公司(AAPL)的股价约被报价 15,000 次—比过去 30 年末日报价看到的报价多两倍。这带来了一系列挑战:

数据处理

仅仅考虑和处理股票或其他金融工具的日终行情是不够的;对于某些工具来说,每天 24 小时,每周 7 天都会发生“太多”的事情。

分析速度

决策通常需要在毫秒甚至更快的时间内做出,因此需要建立相应的分析能力,并实时分析大量数据。

理论基础

尽管传统的金融理论和概念远非完美,但它们随着时间的推移已经经过了充分的测试(有时也被充分地拒绝);就当今至关重要的毫秒级时间尺度而言,仍然缺乏一致的、经过时间检验的概念和理论,这些理论已被证明在一定程度上是相对稳健的。

所有这些挑战原则上只能通过现代技术来解决。可能有点令人惊讶的是,缺乏一致性理论往往是通过技术手段来解决的,高速算法利用市场微观结构元素(例如,订单流量、买卖价差),而不是依赖某种金融推理。

实时分析的崛起

有一个学科在金融行业的重要性大大增加:金融和数据分析。这一现象与速度、频率和数据量在行业中迅速增长的认识密切相关。事实上,实时分析可以被认为是行业对这一趋势的回应。

大致而言,“金融和数据分析”是指将软件和技术与(可能是先进的)算法和方法相结合,以收集、处理和分析数据,以获取见解、做出决策或满足监管要求的学科。例如,估算银行零售业务中金融产品定价结构变化引起的销售影响。另一个例子可能是对投资银行衍生品交易的复杂投资组合进行大规模的隔夜信用价值调整(CVA)计算。

在这种情况下,金融机构面临着两个主要挑战:

大数据

即使在“大数据”这个术语被创造之前,银行和其他金融机构也不得不处理大量数据;然而,随着时间的推移,单个分析任务中需要处理的数据量已经大大增加,要求提高计算能力以及越来越大的内存和存储容量。

实时经济

在过去,决策者可以依靠结构化的、定期的规划、决策和(风险)管理流程,而今天他们面临的是需要实时处理这些功能的需求;过去通过夜间批量运行在后台处理的几项任务现在已被转移到前台,并实时执行。

再次,人们可以观察到技术进步与金融/商业实践之间的相互作用。一方面,需要不断应用现代技术来提高分析方法的速度和能力。另一方面,技术的进步使得几年甚至几个月前被认为不可能(或由于预算限制而不可行)的新分析方法成为可能。

分析领域的一个主要趋势是在 CPU(中央处理单元)端利用并行架构和在 GPGPU(通用图形处理单元)端利用大规模并行架构。当前的 GPGPU 通常拥有 1,000 多个计算核心,这使得有时需要彻底重新思考并行对不同算法可能意味着什么。在这方面仍然存在的障碍是用户通常必须学习新的范例和技术来利用这种硬件的性能。^(3)

金融中的 Python

上一节描述了金融中技术角色的一些选定方面:

  • 金融业技术成本
  • 技术作为新业务和创新的推动者
  • 技术和人才作为金融行业进入壁垒
  • 速度、频率和数据量的增加
  • 实时分析的兴起

在本节中,我们想要分析 Python 如何帮助解决这些方面所暗示的几个挑战。但首先,从更基本的角度来看,让我们从语言和语法的角度来审视 Python 在金融领域的作用。

金融和 Python 语法

在金融环境中首次尝试 Python 的大多数人可能会面临算法问题。这类似于科学家想要解决微分方程、评估积分或简单地可视化一些数据的情况。一般来说,在这个阶段,很少有人会花费时间思考形式化的开发过程、测试、文档编写或部署等问题。然而,这似乎是人们开始喜爱 Python 的阶段。其中一个主要原因可能是 Python 语法通常与用于描述科学问题或金融算法的数学语法非常接近。

我们可以通过一个简单的金融算法来说明这一现象,即通过蒙特卡洛模拟对欧式看涨期权进行估值。我们将考虑一个 Black-Scholes-Merton(BSM)设置(也见[待续链接]),其中期权的基础风险因素遵循几何布朗运动。

假设我们对估值有以下数值参数值

  • 初始股指水平 S 0 = 100
  • 欧式看涨期权的行权价格 K = 105
  • 到期时间 T = 1 年。
  • 常量,无风险短期利率 r = 0 . 05。
  • 常量波动率 σ = 0 . 2。

在 BSM 模型中,到期时的指数水平是一个随机变量,由 方程 1-1 给出,其中 z 是一个标准正态分布的随机变量。

方程 1-1. 到期时的 Black-Scholes-Merton (1973) 指数水平。

S T = S 0 exp r - 1 2 σ 2 T σ T z

以下是蒙特卡洛估值过程的 算法描述

  1. 从标准正态分布中绘制(伪)随机数 I(i) , 其中 z ( i ) , i i n { 1 , 2 , . . . , I }。
  2. 计算给定 z ( i ) 和 方程 1-1 的所有到期时指数水平 S T ( i )。
  3. 计算到期时期权的所有内部值为 h T ( i ) = max ( S T ( i ) - K , 0 )。
  4. 根据 方程 1-2 中给出的蒙特卡罗估计量估算期权现值。
方程 1-2. 欧式期权的蒙特卡罗估计量。

C 0 ≈ e -rT 1 I ∑ I h T ( i )

现在我们将把这个问题和算法翻译成 Python 代码。读者可以通过使用 IPython 等工具来跟踪单个步骤,但在这个阶段并不是非常必要。

代码语言:javascript复制
In [6]: S0 = 100.  # ①
        K = 105.  # ①
        T = 1.0  # ①
        r = 0.05  # ①
        sigma = 0.2  # ①

In [7]: import math
        import numpy as np  # ②

        I = 100000

        np.random.seed(1000)  # ③
        z = np.random.standard_normal(I)  # ④
        ST = S0 * np.exp((r - sigma ** 2 / 2) * T   sigma * math.sqrt(T) * z)  # ⑤
        hT = np.maximum(ST - K, 0)  # ⑥
        C0 = math.exp(-r * T) * np.mean(hT)  # ⑦

In [8]: print('Value of the European Call Option %5.3f:' % C0)  # ⑧

        Value of the European Call Option 8.019:

模型参数值已定义。

NumPy 在这里作为主要包被使用。

随机数生成器的种子值是固定的。

这绘制了标准正态分布的随机数。

这模拟了期末值。

到期时的期权回报是通过计算得出的。

蒙特卡洛估算器被评估。

这将打印出结果值的估计。

有三个方面值得强调:

语法

Python 语法确实与数学语法非常接近,例如,在参数值赋值时。

翻译

每个数学和/或算法声明通常可以被翻译成一行单独的Python 代码。

向量化

NumPy的一个优点是其紧凑、向量化的语法,例如,允许在一行代码中进行 10 万次计算。

这段代码可以在像IPython这样的交互环境中使用。然而,通常被定期重复使用的代码会被组织成所谓的模块(或脚本),它们是具有后缀.py的单个 Python(技术上的“文本”)文件。在这种情况下,这样的一个模块可能看起来像 Example 1-1,并且可以保存为名为bsm_mcs_euro.py的文件。

示例 1-1. 欧式看涨期权的蒙特卡洛估值
代码语言:javascript复制
#
# Monte Carlo valuation of European call option
# in Black-Scholes-Merton model
# bsm_mcs_euro.py
#
# Python for Finance
# (c) Dr. Yves J. Hilpisch
#
import math
import numpy as np

# Parameter Values
S0 = 100.  # initial index level
K = 105.  # strike price
T = 1.0  # time-to-maturity
r = 0.05  # riskless short rate
sigma = 0.2  # volatility

I = 100000  # number of simulations

# Valuation Algorithm
z = np.random.standard_normal(I)  # pseudorandom numbers
# index values at maturity
ST = S0 * np.exp((r - 0.5 * sigma ** 2) * T   sigma * math.sqrt(T) * z)
hT = np.maximum(ST - K, 0)  # inner values at maturity
C0 = math.exp(-r * T) * np.mean(hT)  # Monte Carlo estimator

# Result Output
print('Value of the European Call Option %5.3f' % C0)

本小节中相当简单的算法示例说明了 Python,以其非常直观的语法,非常适合补充经典的科学语言英语和数学。似乎将Python加入到科学语言集合中使其更加完整。我们有

  • 英语用于书写、讨论科学和金融问题等。
  • 数学用于简洁而准确地描述和建模抽象方面、算法、复杂量等。
  • Python用于技术上建模和实现抽象方面、算法、复杂量等。

数学和 Python 语法

几乎没有任何编程语言能像 Python 一样接近数学语法。因此,数值算法从数学表示转换为Python实现非常简单。这使得在这些领域中使用 Python 进行原型设计、开发和代码维护非常高效。

在一些领域,使用伪代码并以此引入第四个语言家族成员是常见做法。伪代码的作用是更技术化地表示金融算法,这既与数学表示接近,又与技术实现接近。除了算法本身,伪代码还考虑了计算机原理。

这种做法通常源于大多数编程语言的技术实现与其正式数学表示相距甚远。大多数编程语言需要包含许多仅在技术上需要的元素,以至于很难看出数学和代码之间的等价性。

如今,Python 通常以伪代码方式使用,因为其语法几乎与数学相似,并且由于技术“开销”保持最小。这是通过语言中体现的一些高级概念实现的,这些概念不仅具有优势,而且一般都伴随着风险和/或其他成本。但是,可以肯定的是,使用 Python 可以在需要时遵循其他语言可能从一开始就需要的严格实现和编码实践。在这个意义上,Python 可以提供最好的两种世界:高级抽象严格实现

通过 Python 提高效率和生产力

在高层次上,使用 Python 的好处可以从三个方面衡量:

效率

Python 如何帮助更快地获得结果,节省成本和节省时间?

生产力

Python 如何帮助提高使用相同资源(人力、资产等)的效率?

质量

Python 允许我们做什么,而其他技术做不到呢?

对这些方面的讨论自然不可能穷尽。但是,它可以突出一些论据作为起点。

更短的结果时间

Python 的效率显而易见的领域之一是交互式数据分析。这是一个极大受益于诸如IPython和像pandas这样的强大工具的领域。

考虑一个金融学生,她正在写她的硕士论文,对 S&P 500 指数值感兴趣。她想要分析历史指数水平,比如说,几年来指数波动率是如何随时间波动的。她想要找到证据表明,与一些典型的模型假设相反,波动率随时间波动,并且远非恒定。结果还应该进行可视化。她主要需要做以下几件事:

  • 从网络检索指数水平数据。
  • 计算对数收益的年化滚动标准差(波动性)。
  • 绘制指数水平数据和结果。

这些任务足够复杂,以至于不久之前人们会认为这是专业金融分析师的事情。如今,即使是金融学生也能轻松应对这些问题。让我们看看这究竟是如何运作的——在这个阶段不必担心语法细节(一切都将在后续章节中详细解释)。

代码语言:javascript复制
In [10]: import numpy as np  # ①
         import pandas as pd  # ①

In [11]: data = pd.read_csv('http://hilpisch.com/tr_eikon_eod_data.csv',
                           index_col=0, parse_dates=True)  # ②
         data = pd.DataFrame(data['.SPX'])  # ③
         data.info()  # ④

         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 1972 entries, 2010-01-04 to 2017-10-31
         Data columns (total 1 columns):
         .SPX    1972 non-null float64
         dtypes: float64(1)
         memory usage: 30.8 KB

In [12]: data['rets'] = np.log(data / data.shift(1))  # ⑤
         data['vola'] = data['rets'].rolling(252).std() * np.sqrt(252)  # ⑥

In [13]: data[['.SPX', 'vola']].plot(subplots=True, figsize=(10, 6));  # ⑦
         plt.savefig('../images/01_chapter/spx_volatility.png')

这导入了NumPypandas

read_csv允许检索远程存储的数据集。

选择数据的一个子集。

这显示了数据集的一些元信息。

对数收益以矢量化方式计算(“无循环”)。

滚动、年化波动率是由此得出的。

最后一行将这两个时间序列绘制出来。

图 1-1 展示了这个简短交互会话的图形结果。几行代码就足以实现在金融分析中经常遇到的三个相当复杂的任务:数据收集、复杂和重复的数学计算,以及结果的可视化。这个例子说明了pandas使得处理整个时间序列几乎与对浮点数执行数学运算一样简单。

图 1-1. S&P 500 收盘价和年化波动率

将其翻译成专业的金融背景下,该示例意味着金融分析师可以——当应用正确的 Python 工具和库,提供高级抽象——专注于他们的领域,而不是技术细节。分析师可以更快地做出反应,几乎实时提供有价值的见解,并确保他们领先竞争对手一步。这个提高效率的例子很容易转化为可衡量的底线效应。

确保高性能

总的来说,人们普遍认为 Python 语法相对简洁,编码相对高效。然而,由于 Python 是一种解释语言,导致了“偏见”持续存在,即 Python 通常对金融中的计算密集型任务来说速度太慢了。事实上,根据特定的实现方法,Python 可能确实很慢。但它不一定慢— 它几乎可以在任何应用领域都表现出高性能。原则上,可以区分至少三种不同的策略来提高性能:

范式

通常情况下,Python 中有很多不同的方法可以得到相同的结果,但性能特性却大不相同;“简单地”选择正确的方式(例如特定的库)可以显著提高结果。

编译

如今,有几个性能库可用,它们提供了重要函数的编译版本,或者将 Python 代码静态或动态地(在运行时或调用时)编译为机器代码,速度可以提高数个数量级;其中流行的有CythonNumba

并行化

许多计算任务,特别是在金融领域,都可以从并行执行中获益;这并不是 Python 特有的,但是可以很容易地用 Python 实现。

使用 Python 进行性能计算

Python 本身不是一种高性能计算技术。然而,Python 已经发展成为访问当前性能技术的理想平台。从这个意义上说,Python 已经成为一种 性能计算的粘合语言

后面的章节将详细介绍这三种技术。目前,我们希望使用一个简单但仍然现实的例子,涉及这三种技术。

在金融分析中,一个相当常见的任务是在大量数字数组上评估复杂的数学表达式。为此,Python 本身提供了一切所需的:

代码语言:javascript复制
In [14]: loops = 2500000
         import math
         a = range(1, loops)
         def f(x):
             return 3 * math.log(x)   math.cos(x) ** 2
         %timeit r = [f(x) for x in a]

         1.52 s ± 29.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

在这种情况下,Python 解释器需要 1.5 秒来评估函数 f 250 万次。

同样的任务可以使用NumPy来实现,它提供了优化(即,预编译)的函数来处理这种基于数组的操作:

代码语言:javascript复制
In [15]: import numpy as np
         a = np.arange(1, loops)
         %timeit r = 3 * np.log(a)   np.cos(a) ** 2

         83.3 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

使用NumPy可以大大减少执行时间至 90 毫秒。

然而,甚至有一个专门致力于这种任务的库。它被称为 numexpr,表示“数值表达式”。它将表达式编译以改进NumPy的一般功能,例如,在此过程中避免数组的内存副本:

代码语言:javascript复制
In [16]: import numexpr as ne
         ne.set_num_threads(1)
         f = '3 * log(a)   cos(a) ** 2'
         %timeit r = ne.evaluate(f)

         78.2 ms ± 4.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

使用这种更专业的方法进一步将执行时间缩短到 80 毫秒。然而,numexpr 也具有内置功能来并行执行相应的操作。这使我们能够使用 CPU 的多个线程:

代码语言:javascript复制
In [17]: ne.set_num_threads(4)
         %timeit r = ne.evaluate(f)

         21.9 ms ± 113 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

这将执行时间进一步缩短到约 25 毫秒,在此情况下,使用了四个线程。总体而言,这是性能提升超过 50 倍。特别注意,这种改进是可能的,而不需要改变基本的问题/算法,也不需要了解编译或并行化问题。即使是非专家也可以从高层次访问这些功能。当然,必须意识到存在哪些功能和选项。

该示例显示了 Python 提供了许多选项,以充分利用现有资源——即,提高生产力。使用顺序方法,每秒完成大约 31 百万次评估,而并行方法允许每秒进行超过 100 百万次评估——在这种情况下,只需告诉 Python 使用所有可用的 CPU 线程,而不是只使用一个线程。

从原型设计到生产

在交互式分析中的效率以及在执行速度方面的性能无疑是考虑 Python 的两个好处。然而,使用 Python 进行金融交易的另一个主要好处乍一看可能似乎有点微妙;第二次看可能会表现为一个重要的战略因素。这就是可以从原型设计到生产都可以使用 Python 的可能性。

当涉及到金融开发流程时,全球金融机构的实践通常以分离的、两步的过程为特征。一方面,有量化分析师(“量化分析师”)负责模型开发和技术原型设计。他们喜欢使用像MatlabR这样的工具和环境,这些工具和环境允许进行快速、交互式的应用程序开发。在开发工作的这个阶段,性能、稳定性、异常管理、数据访问和分析的分离等问题并不那么重要。主要是在寻找概念验证和/或展示算法或整个应用程序主要期望功能的原型。

一旦原型完成,IT 部门与其开发人员接管并负责将现有的原型代码转换为可靠、可维护和高性能的生产代码。通常,在这个阶段会发生范式转变,使用像CJava这样的语言来满足生产的要求。此外,还会应用正式的开发流程,包括专业工具、版本控制等。

这种两步法的做法通常会产生一些通常意义上不期而遇的后果:

低效

原型代码不可重用;算法必须实现两次;冗余的工作需要时间和资源。

多样化的技能组合

不同部门展示出不同的技能集,并使用不同的语言来实现“相同的事物”。

遗留代码

代码可用且必须用不同的语言进行维护,通常使用不同的实现风格(例如,从架构的角度来看)。

另一方面,使用 Python 可以实现从最初的交互式原型设计步骤到高度可靠且高效可维护的生产代码的简化端到端流程。不同部门之间的沟通变得更容易。员工培训也更加简化,因为只有一种主要语言涵盖了金融应用构建的所有领域。它还避免了在开发过程的不同步骤中使用不同技术时固有的低效和冗余。总而言之,Python 几乎可以为金融应用开发和算法实现的几乎所有任务提供一致的技术框架

人工智能优先金融

数据可用性

机器学习和深度学习

传统与人工智能优先金融

结论

Python 作为一种语言——但更多作为一个生态系统——是金融行业的理想技术框架。它具有许多优点,如优雅的语法、高效的开发方法以及适用于原型设计生产等方面的可用性。凭借其大量可用的库和工具,Python 似乎对金融行业的最新发展所提出的大多数问题都有答案,例如分析、数据量和频率、合规性和监管,以及技术本身。它有潜力提供一个单一、强大、一致的框架,可以使端到端的开发和生产工作流程变得更加顺畅,即使是在较大的金融机构之间也是如此。

进一步阅读

以下书籍由同一作者撰写,详细介绍了本章中只是简要提及的许多方面(例如衍生品分析):

  • Hilpisch, Yves (2015): Python 衍生品分析. Wiley Finance, Chichester, England. http://derivatives-analytics-with-python.com

本章引用的语录来自以下资源:

  • Crosman, Penny (2013): “银行将如何使用其 2014 年 IT 预算的 8 种方式。” 银行技术新闻
  • Deutsche Börse Group (2008): “全球衍生品市场——简介。” 白皮书。
  • Ding, Cubillas (2010): “优化场外交易定价和估值基础设施。” Celent 研究
  • Lewis, Michael (2014): 闪电少年. W. W. Norton & Company, New York.
  • Patterson, Scott (2010): 量化分析师. Crown Business, New York.

¹ 例如,Python 是纽约市立大学巴鲁克学院金融工程硕士课程中使用的主要语言(请参阅http://mfe.baruch.cuny.edu)。

² 请参阅http://wiki.python.org/moin/BeginnersGuide,在那里您将找到许多对于初学者和非开发人员来说开始使用 Python 非常有价值的资源链接。

³ [Link to Come]提供了在随机数生成的背景下使用现代 GPGPU 的好处的示例。

第二章:Python 基础设施

在建造房屋时,有木材选择的问题。 重要的是,木匠的目标是携带能够良好切割的设备,并在有时间时磨削这些设备。 宫本武藏(《五轮书》)

介绍

对于新接触 Python 的人来说,Python 部署似乎一切都不简单。对于可以选择安装的丰富库和包也是如此。首先,Python 不只有 一个 版本。 Python 有许多不同的版本,如 CPython,Jython,IronPython 或 PyPy。然后仍然存在 Python 2.7 和 3.x 世界之间的差异。[¹] 接下来,本章重点关注 CPython,迄今为止最流行的 Python 编程语言版本,以及 版本 3.6

即使专注于 CPython 3.6(以下简称Python),部署也因许多其他原因变得困难:

  • 解释器(标准 CPython 安装)只带有所谓的 标准库(例如,涵盖典型的数学函数)
  • 可选的 Python 包需要单独安装 —— 而且有数百个
  • 由于依赖关系和操作系统特定要求,编译/构建这些非标准包可能会很棘手
  • 要注意这些依赖关系并保持版本一致性随着时间的推移(即维护)通常是繁琐且耗时的。
  • 对某些包的更新和升级可能导致需要重新编译大量其他包
  • 更改或替换一个包可能会在(许多)其他地方引起麻烦

幸运的是,有一些工具和策略可用于帮助解决 Python 部署问题。本章介绍了以下几种有助于 Python 部署的技术类型:

  • 包管理器:像pipconda这样的包管理器有助于安装、更新和删除 Python 包;它们还有助于不同包的版本一致性
  • 虚拟环境管理器:虚拟环境管理器如virtualenvconda允许并行管理多个 Python 安装(例如,在单个计算机上同时拥有 Python 2.7 和 3.6 安装,或者在不冒风险的情况下测试最新的流行 Python 包的开发版本)
  • 容器:Docker容器代表包含运行某个软件所需的所有系统部件的完整文件系统,如代码、运行时或系统工具;例如,你可以在运行 Mac OS 或 Windows 10 的机器上运行一个包含 Ubuntu 16.04 操作系统、Python 3.6 安装和相应 Python 代码的 Docker 容器中运行。
  • 云实例:部署用于算法交易的 Python 代码通常需要高可用性、安全性和性能;这些要求通常只能通过使用现在以有吸引力条件提供的专业计算和存储基础设施来满足,这些基础设施形式可从相对较小到真正大型和强大的云实例;与长期租用的专用服务器相比,云实例,即虚拟服务器的一个好处是,用户通常只需支付实际使用的小时数;另一个优点是,如果需要,这些云实例可以在一两分钟内立即使用,这有助于敏捷开发和可扩展性。

本章的结构如下所示

“Conda 作为包管理器”

本节介绍了conda作为 Python 的包管理器。

“作为虚拟环境管理器的 Conda”

本节重点介绍conda作为虚拟环境管理器的功能。

“使用 Docker 容器化”

本节简要介绍 Docker 作为容器化技术,并侧重于构建具有 Python 3.6 安装的基于 Ubuntu 的容器。

“使用云实例”

本节展示了如何在云中部署 Python 和 Jupyter Notebook —— 作为强大的基于浏览器的工具套件 —— 用于 Python 开发。

本章的目标是在专业基础设施上具有正确的 Python 安装,并可用最重要的数值和数据分析包,然后这种组合作为在后续章节中实现和部署 Python 代码的骨干,无论是交互式的金融分析代码还是脚本和模块形式的代码。

Conda 作为包管理器

虽然conda可以单独安装,但高效的方式是通过 Miniconda,这是一个包括conda作为包和虚拟环境管理器的最小 Python 发行版。

安装 Miniconda 3.6

您可以在Miniconda 页面上下载不同版本的 Miniconda。在接下来的内容中,假定使用 Python 3.6 64 位版本,该版本适用于 Linux、Windows 和 Mac OS。本小节的主要示例是在基于 Ubuntu 的 Docker 容器中进行会话,通过 wget 下载 Linux 64 位安装程序,然后安装 Miniconda。如所示的代码应在任何其他基于 Linux 或 Mac OS 的机器上无需修改即可正常工作。

代码语言:javascript复制
$ docker run -ti -h py4fi -p 9999:9999 ubuntu:latest /bin/bash

root@py4fi:/# apt-get update; apt-get upgrade -y
...
root@py4fi:/# apt-get install wget bzip2 gcc
...
root@py4fi:/# wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
--2017-11-04 10:52:09--  https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
Resolving repo.continuum.io (repo.continuum.io)... 104.16.19.10, 104.16.18.10, 2400:cb00:2048:1::6810:120a, ...
Connecting to repo.continuum.io (repo.continuum.io)|104.16.19.10|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 54167345 (52M) [application/x-sh]
Saving to: 'Miniconda3-latest-Linux-x86_64.sh'

Miniconda3-latest-Lin 100%[=======================>]  51.66M  1.57MB/s    in 32s

2017-11-04 10:52:41 (1.62 MB/s) - 'Miniconda3-latest-Linux-x86_64.sh' saved [54167345/54167345]

root@py4fi:/# bash Miniconda3-latest-Linux-x86_64.sh

Welcome to Miniconda3 4.3.30

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>>

简单地按下ENTER键即可开始安装过程。在审查许可协议后,通过回答yes来批准条款。

代码语言:javascript复制
Do you approve the license terms? [yes|no]
>>> yes

Miniconda3 will now be installed into this location:
/root/miniconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/root/miniconda3] >>>
PREFIX=/root/miniconda3
installing: python-3.6.3-hc9025b9_1 ...
Python 3.6.3 :: Anaconda, Inc.
installing: ca-certificates-2017.08.26-h1d4fec5_0 ...
installing: conda-env-2.6.0-h36134e3_1 ...
installing: libgcc-ng-7.2.0-h7cc24e2_2 ...
installing: libstdcxx-ng-7.2.0-h7a57d05_2 ...
installing: libffi-3.2.1-h4deb6c0_3 ...
installing: ncurses-6.0-h06874d7_1 ...
installing: openssl-1.0.2l-h077ae2c_5 ...
installing: tk-8.6.7-h5979e9b_1 ...
installing: xz-5.2.3-h2bcbf08_1 ...
installing: yaml-0.1.7-h96e3832_1 ...
installing: zlib-1.2.11-hfbfcf68_1 ...
installing: libedit-3.1-heed3624_0 ...
installing: readline-7.0-hac23ff0_3 ...
installing: sqlite-3.20.1-h6d8b0f3_1 ...
installing: asn1crypto-0.22.0-py36h265ca7c_1 ...
installing: certifi-2017.7.27.1-py36h8b7b77e_0 ...
installing: chardet-3.0.4-py36h0f667ec_1 ...
installing: idna-2.6-py36h82fb2a8_1 ...
installing: pycosat-0.6.2-py36h1a0ea17_1 ...
installing: pycparser-2.18-py36hf9f622e_1 ...
installing: pysocks-1.6.7-py36hd97a5b1_1 ...
installing: ruamel_yaml-0.11.14-py36ha2fb22d_2 ...
installing: six-1.10.0-py36hcac75e4_1 ...
installing: cffi-1.10.0-py36had8d393_1 ...
installing: setuptools-36.5.0-py36he42e2e1_0 ...
installing: cryptography-2.0.3-py36ha225213_1 ...
installing: wheel-0.29.0-py36he7f4e38_1 ...
installing: pip-9.0.1-py36h8ec8b28_3 ...
installing: pyopenssl-17.2.0-py36h5cc804b_0 ...
installing: urllib3-1.22-py36hbe7ace6_0 ...
installing: requests-2.18.4-py36he2e5f8d_1 ...
installing: conda-4.3.30-py36h5d9f9f4_0 ...
installation finished.

在您同意许可条款并确认安装位置之后,您应该允许 Miniconda 将新的 Miniconda 安装位置添加到 PATH 环境变量中,通过再次回答 yes

代码语言:javascript复制
Do you wish the installer to prepend the Miniconda3 install location
to PATH in your /root/.bashrc ? [yes|no]
[no] >>> yes

完成这个相当简单的安装过程后,现在既有一个基本的 Python 安装,也有conda可用。基本的 Python 安装已经包含了一些不错的功能,比如 SQLite3 数据库引擎。您可以尝试看看是否可以在新的 shell 实例中启动 Python,或者在追加相关路径到相应的环境变量后启动 Python。

代码语言:javascript复制
root@py4fi:/# export PATH="/root/miniconda3/bin/:$PATH"
root@py4fi:/# python
Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('Hello Algo Trading World.')
Hello Algo Trading World.
>>> exit()
root@py4fi:/#

Conda 基本操作

Conda 可以高效地处理,安装,更新和删除 Python 包,等等。以下列表提供了主要功能的概述。

安装 Python x.x

conda install python=x.x

更新 Python

conda update python

安装一个包

conda install $PACKAGE_NAME

更新一个包

conda update $PACKAGE_NAME

移除一个包

conda remove $PACKAGE_NAME

更新 conda 本身

conda update conda

搜索包

conda search $SEARCH_TERM

列出已安装的包

conda list

有了这些功能,例如,安装NumPy——作为所谓科学堆栈中最重要的库之一——只需一条命令。当在装有英特尔处理器的机器上进行安装时,该过程会自动安装英特尔数学核心库 mkl,这不仅加速了在英特尔机器上的NumPy的数值操作,还加速了其他几个科学 Python 包的数值操作。

代码语言:javascript复制
root@py4fi:/# conda install numpy
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /root/miniconda3:

The following NEW packages will be INSTALLED:

    intel-openmp: 2018.0.0-h15fc484_7
    mkl:          2018.0.0-hb491cac_4
    numpy:        1.13.3-py36ha12f23b_0

Proceed ([y]/n)? y

intel-openmp-2 100% |######################################| Time: 0:00:00   1.78 MB/s
mkl-2018.0.0-h 100% |######################################| Time: 0:01:27   2.08 MB/s
numpy-1.13.3-p 100% |######################################| Time: 0:00:02   1.36 MB/s
root@py4fi:/#

也可以一次性安装多个包。-y 标志表示所有(可能的)问题都应回答“是”。

代码语言:javascript复制
conda install -y ipython matplotlib pandas pytables scipy seaborn

安装完成后,除了标准库之外,一些金融分析中最重要的库也可用。

IPython

一个改进的交互式 Python shell

matplotlib

Python 中的标准绘图库

NumPy

高效处理数值数组

pandas

管理表格数据,如金融时间序列数据

PyTables

用于HDF5库的 Python 封装

SciPy

一组科学类和函数(作为依赖项安装)

Seaborn

一个绘图库,添加了统计功能和良好的绘图默认值

这提供了一套用于一般数据分析和金融分析的基本工具集。下面的示例使用 IPython 并使用 NumPy 绘制一组伪随机数。

代码语言:javascript复制
root@py4fi:/# ipython
Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy as np

In [2]: np.random.standard_normal((5, 4))
Out[2]:
array([[-0.11906643,  0.65717731,  0.3763829 ,  0.04928928],
       [-0.88728758, -0.41263027, -0.70284906, -0.42948971],
       [-0.43964908, -0.21453161,  0.63860994, -0.24972815],
       [-0.50119552, -0.68807591, -0.03928424,  3.03200697],
       [-0.19549982,  0.92946315, -1.86523627,  0.37608079]])
In [3]: exit
root@py4fi:/#

执行 conda list 可以验证安装了哪些包。

代码语言:javascript复制
root@py4fi:/# conda list
# packages in environment at /root/miniconda3:
#
asn1crypto                0.22.0           py36h265ca7c_1
bzip2                     1.0.6                h0376d23_1
ca-certificates           2017.08.26           h1d4fec5_0
certifi                   2017.7.27.1      py36h8b7b77e_0
cffi                      1.10.0           py36had8d393_1
chardet                   3.0.4            py36h0f667ec_1
conda                     4.3.30           py36h5d9f9f4_0
conda-env                 2.6.0                h36134e3_1
cryptography              2.0.3            py36ha225213_1
cycler                    0.10.0           py36h93f1223_0
...
matplotlib                2.1.0            py36hba5de38_0
mkl                       2018.0.0             hb491cac_4
ncurses                   6.0                  h06874d7_1
numexpr                   2.6.2            py36hdd3393f_1
numpy                     1.13.3           py36ha12f23b_0
openssl                   1.0.2l               h077ae2c_5
pandas                    0.20.3           py36h842e28d_2
...
python                    3.6.3                hc9025b9_1
python-dateutil           2.6.1            py36h88d3b88_1
pytz                      2017.2           py36hc2ccc2a_1
qt                        5.6.2               h974d657_12
readline                  7.0                  hac23ff0_3
requests                  2.18.4           py36he2e5f8d_1
ruamel_yaml               0.11.14          py36ha2fb22d_2
scipy                     0.19.1           py36h9976243_3
seaborn                   0.8.0            py36h197244f_0
setuptools                36.5.0           py36he42e2e1_0
simplegeneric             0.8.1            py36h2cb9092_0
sip                       4.18.1           py36h51ed4ed_2
six                       1.10.0           py36hcac75e4_1
sqlite                    3.20.1               h6d8b0f3_1
statsmodels               0.8.0            py36h8533d0b_0
tk                        8.6.7                h5979e9b_1
tornado                   4.5.2            py36h1283b2a_0
traitlets                 4.3.2            py36h674d592_0
urllib3                   1.22             py36hbe7ace6_0
wcwidth                   0.1.7            py36hdf4376a_0
wheel                     0.29.0           py36he7f4e38_1
xz                        5.2.3                h2bcbf08_1
yaml                      0.1.7                h96e3832_1
zlib                      1.2.11               hfbfcf68_1
root@py4fi:/#

如果一个包不再需要,则可以使用 conda remove 高效地移除它。

代码语言:javascript复制
root@py4fi:/# conda remove seaborn
Fetching package metadata ...........
Solving package specifications: .

Package plan for package removal in environment /root/miniconda3:

The following packages will be REMOVED:

    seaborn: 0.8.0-py36h197244f_0

Proceed ([y]/n)? y

root@py4fi:/#

作为包管理器的conda已经非常有用。但是,只有在添加虚拟环境管理时,其全部功能才会显现出来。

提示

conda 作为一个包管理器,使得安装、更新和移除 Python 包变得愉快。不再需要自行构建和编译包 — 有时候这可能会很棘手,因为一个包指定了一长串依赖项,而且还要考虑到在不同操作系统上的特定情况。

Conda 作为虚拟环境管理器

安装了包含 conda 的 Miniconda 后,会根据选择的 Miniconda 版本提供一个默认的 Python 安装。conda 的虚拟环境管理功能允许在 Python 3.6 默认安装中添加一个完全独立的 Python 2.7.x 安装。为此,conda 提供了以下功能。

创建一个虚拟环境

conda create --name $ENVIRONMENT_NAME

激活一个环境

source activate $ENVIRONMENT_NAME

停用一个环境

source deactivate $ENVIRONMENT_NAME

移除一个环境

conda-env remove --name $ENVIRONMENT_NAME

导出到一个环境文件

conda env export > $FILE_NAME

从文件创建一个环境

conda env create -f $FILE_NAME

列出所有环境

conda info --envs

作为一个简单的示例,接下来的示例代码创建了一个名为 py27 的环境,安装了 IPython 并执行了一行 Python 2.7.x 代码。

代码语言:javascript复制
root@py4fi:/# conda create --name py27 python=2.7
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /root/miniconda3/envs/py27:

The following NEW packages will be INSTALLED:

    ca-certificates: 2017.08.26-h1d4fec5_0
    certifi:         2017.7.27.1-py27h9ceb091_0
    libedit:         3.1-heed3624_0
    libffi:          3.2.1-h4deb6c0_3
    libgcc-ng:       7.2.0-h7cc24e2_2
    libstdcxx-ng:    7.2.0-h7a57d05_2
    ncurses:         6.0-h06874d7_1
    openssl:         1.0.2l-h077ae2c_5
    pip:             9.0.1-py27ha730c48_4
    python:          2.7.14-h89e7a4a_22
    readline:        7.0-hac23ff0_3
    setuptools:      36.5.0-py27h68b189e_0
    sqlite:          3.20.1-h6d8b0f3_1
    tk:              8.6.7-h5979e9b_1
    wheel:           0.29.0-py27h411dd7b_1
    zlib:            1.2.11-hfbfcf68_1

Proceed ([y]/n)? y

Fetching package metadata ...........
Solving package specifications: .

...

#
# To activate this environment, use:
# > source activate py27
#
# To deactivate an active environment, use:
# > source deactivate
#

root@py4fi:/#

注意,在激活环境后提示符如何变为 (py27)。²

代码语言:javascript复制
root@py4fi:/# source activate py27
(py27) root@py4fi:/# conda install -y ipython
Fetching package metadata ...........
Solving package specifications: .

...

最后,使用 Python 2.7 语法的 IPython

代码语言:javascript复制
(py27) root@py4fi:/# ipython
Python 2.7.14 |Anaconda, Inc.| (default, Oct 27 2017, 18:21:12)
Type "copyright", "credits" or "license" for more information.

IPython 5.4.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: print "Hello Algo Trading World!"
Hello Algo Trading World!

In [2]: exit
(py27) root@py4fi:/#

正如这个例子所示,conda 作为一个虚拟环境管理器允许在一起安装不同的 Python 版本。它还允许安装某些包的不同版本。默认 Python 安装不受这种过程的影响,也不受同一台机器上可能存在的其他环境的影响。所有可用的环境可以通过 conda info --envs 显示。

代码语言:javascript复制
(py27) root@py4fi:/# conda info --envs
# conda environments:
#
py27                  *  /root/miniconda3/envs/py27
root                     /root/miniconda3

(py27) root@py4fi:/#

有时需要与他人共享环境信息或在多台机器上使用环境信息。为此,可以使用 conda env export 将安装的包列表导出到一个文件中。

代码语言:javascript复制
(py27) root@py4fi:/# conda env export > py27env.yml
(py27) root@py4fi:/# cat py27env.yml
name: py27
channels:
- defaults
dependencies:
- backports=1.0=py27h63c9359_1
- backports.shutil_get_terminal_size=1.0.0=py27h5bc021e_2
- ca-certificates=2017.08.26=h1d4fec5_0
- certifi=2017.7.27.1=py27h9ceb091_0
- decorator=4.1.2=py27h1544723_0
- enum34=1.1.6=py27h99a27e9_1
- ipython=5.4.1=py27h36c99b6_1
- ipython_genutils=0.2.0=py27h89fb69b_0
- libedit=3.1=heed3624_0
- libffi=3.2.1=h4deb6c0_3
- libgcc-ng=7.2.0=h7cc24e2_2
- libstdcxx-ng=7.2.0=h7a57d05_2
- ncurses=6.0=h06874d7_1
- openssl=1.0.2l=h077ae2c_5
- pathlib2=2.3.0=py27h6e9d198_0
- pexpect=4.2.1=py27hcf82287_0
- pickleshare=0.7.4=py27h09770e1_0
- pip=9.0.1=py27ha730c48_4
- prompt_toolkit=1.0.15=py27h1b593e1_0
- ptyprocess=0.5.2=py27h4ccb14c_0
- pygments=2.2.0=py27h4a8b6f5_0
- python=2.7.14=h89e7a4a_22
- readline=7.0=hac23ff0_3
- scandir=1.6=py27hf7388dc_0
- setuptools=36.5.0=py27h68b189e_0
- simplegeneric=0.8.1=py27h19e43cd_0
- six=1.11.0=py27h5f960f1_1
- sqlite=3.20.1=h6d8b0f3_1
- tk=8.6.7=h5979e9b_1
- traitlets=4.3.2=py27hd6ce930_0
- wcwidth=0.1.7=py27h9e3e1ab_0
- wheel=0.29.0=py27h411dd7b_1
- zlib=1.2.11=hfbfcf68_1
- pip:
  - backports.shutil-get-terminal-size==1.0.0
  - ipython-genutils==0.2.0
  - prompt-toolkit==1.0.15
prefix: /root/miniconda3/envs/py27

(py27) root@py4fi:/#

通常,虚拟环境(从技术上讲,不过是一种特定的(子)文件夹结构)是为了进行一些快速测试而创建的。在这种情况下,通过 conda env remove 轻松删除环境。

代码语言:javascript复制
(py27) root@py4fi:/# source deactivate
root@py4fi:/# conda env remove --name py27

Package plan for package removal in environment /root/miniconda3/envs/py27:

The following packages will be REMOVED:

    backports:                          1.0-py27h63c9359_1
    backports.shutil_get_terminal_size: 1.0.0-py27h5bc021e_2
    ca-certificates:                    2017.08.26-h1d4fec5_0
    certifi:                            2017.7.27.1-py27h9ceb091_0
    ...
    traitlets:                          4.3.2-py27hd6ce930_0
    wcwidth:                            0.1.7-py27h9e3e1ab_0
    wheel:                              0.29.0-py27h411dd7b_1
    zlib:                               1.2.11-hfbfcf68_1

Proceed ([y]/n)? y
                                                                           #

这就结束了 conda 作为虚拟环境管理器的概述。

提示

conda 不仅帮助管理包,还是 Python 的虚拟环境管理器。它简化了不同 Python 环境的创建,允许在同一台机器上有多个 Python 版本和可选包可用,而且它们之间互不影响。conda 还允许将环境信息导出,以便在多台机器上轻松复制它或与他人共享。

使用 Docker 容器化

Docker 容器已经风靡了 IT 世界。尽管技术仍然很年轻,但它已经确立了自己作为几乎任何类型软件应用的高效开发和部署的基准之一。

对于我们的目的,将 Docker 容器视为一个分离的(“容器化的”)文件系统足以包含操作系统(例如服务器上的 Ubuntu 16.04),一个(Python)运行时,额外的系统和开发工具以及根据需要的其他(Python)库和软件包。这样的 Docker 容器可以在具有 Windows 10 的本地机器上运行,也可以在具有 Linux 操作系统的云实例上运行,例如。

本节不允许深入探讨 Docker 容器的有趣细节。它更多地是对 Docker 技术在 Python 部署上的简要说明。⁴

Docker 镜像和容器

然而,在进入说明之前,谈论 Docker 时需要区分两个基本术语。第一个是Docker 镜像,可以类比为 Python 类。第二个是Docker 容器,可以类比为相应 Python 类的实例。

在更技术层面上,在Docker 术语表中找到了对Docker 镜像的以下定义:

Docker 镜像是容器的基础。一个镜像是一组有序的根文件系统更改和相应的执行参数,用于容器运行时。一个镜像通常包含叠加在彼此上面的分层文件系统的联合。镜像没有状态,永远不会改变。

同样,您可以在Docker 术语表中找到对Docker 容器的以下定义,它将类比为 Python 类和这些类的实例:

容器是 Docker 镜像的运行时实例。Docker 容器包括:一个 Docker 镜像,一个执行环境和一组标准指令。

根据操作系统的不同,安装 Docker 的方式略有不同。这就是为什么本节不涉及相应细节的原因。详细信息请参阅安装 Docker 引擎页面。

构建一个 Ubuntu 和 Python Docker 镜像

本小节说明了基于最新版本的 Ubuntu 构建 Docker 镜像的过程,该镜像包含 Miniconda 以及一些重要的 Python 包。此外,它还通过更新 Linux 软件包索引,必要时升级软件包,并安装某些额外的系统工具来进行一些 Linux 的维护工作。为此,需要两个脚本。一个是在 Linux 级别执行所有工作的bash脚本。⁵另一个是所谓的Dockerfile,它控制镜像本身的构建过程。

示例 2-1 中的 bash 脚本负责安装,由三个主要部分组成。第一部分处理 Linux 的基本事务。第二部分安装 Miniconda,而第三部分安装可选的 Python 包。内联还有更详细的注释。

示例 2-1. 安装 Python 和可选包的脚本
代码语言:javascript复制
#!/bin/bash
#
# Script to Install
# Linux System Tools and
# Basic Python Components
#
# Python for Finance
# (c) Dr. Yves J. Hilpisch
#
# GENERAL LINUX
apt-get update  # updates the package index cache
apt-get upgrade -y  # updates packages
# installs system tools
apt-get install -y bzip2 gcc git htop screen vim wget
apt-get upgrade -y bash  # upgrades bash if necessary
apt-get clean  # cleans up the package index cache

# INSTALL MINICONDA
# downloads Miniconda
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O Miniconda.sh
bash Miniconda.sh -b  # installs it
rm -rf Miniconda.sh  # removes the installer
export PATH="/root/miniconda3/bin:$PATH"  # prepends the new path

# INSTALL PYTHON LIBRARIES
conda install -y pandas  # installs pandas
conda install -y ipython  # installs IPython shell

示例 2-2 中的 Dockerfile 使用 示例 2-1 中的 bash 脚本构建新的 Docker 镜像。它还将其主要部分作为注释内联。

示例 2-2. 构建镜像的 Dockerfile
代码语言:javascript复制
#
# Building a Docker Image with
# the Latest Ubuntu Version and
# Basic Python Install
#
# Python for Finance
# (c) Dr. Yves J. Hilpisch
#

# latest Ubuntu version
FROM ubuntu:latest

# information about maintainer
MAINTAINER yves

# add the bash script
ADD install.sh /
# change rights for the script
RUN chmod u x /install.sh
# run the bash script
RUN /install.sh
# prepend the new path
ENV PATH /root/miniconda3/bin:$PATH

# execute IPython when container is run
CMD ["ipython"]

如果这两个文件在同一个文件夹中,并且已安装 Docker,则构建新的 Docker 镜像就很简单。在这里,标签 ubuntupython 用于该镜像。这个标签在需要引用镜像时很重要,例如在基于它运行容器时。

代码语言:javascript复制
macbookpro:~/Docker$ docker build -t py4fi:basic .
Sending build context to Docker daemon   29.7kB
Step 1/7 : FROM ubuntu:latest
 ---> 747cb2d60bbe

...

Step 7/7 : CMD ["ipython"]
 ---> Running in fddb07301003
Removing intermediate container fddb07301003
 ---> 60a180c9cfa6
Successfully built 60a180c9cfa6
Successfully tagged py4fi:basic
macbookpro:~/Docker$

可以通过 docker images 列出现有的 Docker 镜像。新镜像应该位于列表的顶部。

代码语言:javascript复制
macbookpro:~/Docker$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
py4fi               basic               60a180c9cfa6        About a minute ago   1.65GB
ubuntu              python              9fa4649d110f        4 days ago           2.46GB
<none>              <none>              5ac1e7b7bd9f        4 days ago           2.46GB
ubuntu              base                bcee12a18154        6 days ago           2.29GB
<none>              <none>              032acb2af94c        6 days ago           122MB
tpq                 python              a9dc75f040f6        12 days ago          2.55GB
ubuntu              latest              747cb2d60bbe        3 weeks ago          122MB
macbookpro:~/Docker$

成功构建 ubuntupython 镜像后,可以使用 docker run 运行相应的 Docker 容器。参数组合 -ti 用于在 Docker 容器中运行交互式进程,比如 shell 进程(参见 Docker Run 参考页)。

代码语言:javascript复制
macbookpro:~/Docker$ docker run -ti py4fi:basic

Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy as np

In [2]: a = np.random.standard_normal((5, 3))

In [3]: import pandas as pd

In [4]: df = pd.DataFrame(a, columns=['a', 'b', 'c'])

In [5]: df
Out[5]:
          a         b         c
0  0.062129  0.040233  0.494589
1 -0.681517 -0.307187 -0.476016
2  0.861527  0.438467 -1.656811
3 -1.402893  0.611978  0.238889
4  0.876606  0.746728  0.840246

In [6]:

退出 IPython 也将退出容器,因为它是容器中唯一运行的应用程序。然而,你可以通过

代码语言:javascript复制
Ctrl p --> Ctrl q

从容器中分离后,docker ps 命令显示运行的容器(可能还有其他当前正在运行的容器):

代码语言:javascript复制
macbookpro:~/Dropbox/Platform/algobox/algobook/book/code/Docker$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
98b95440f962        py4fi:basic         "ipython"           3 minutes ago       Up 3 minutes                                 wonderful_neumann
4b85dfc94780        ubuntu:latest       "/bin/bash"         About an hour ago   Up About an hour    0.0.0.0:9999->9999/tcp   musing_einstein
macbookpro:~/Docker$

通过 docker attach $CONTAINER_ID(注意,容器 ID 的几个字母就足够了)连接到 Docker 容器:

代码语言:javascript复制
macbookpro:~/Docker$ docker attach 98b95
In [6]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
a    5 non-null float64
b    5 non-null float64
c    5 non-null float64
dtypes: float64(3)
memory usage: 200.0 bytes

In [7]: exit
macbookpro:~/Docker$

exit 命令终止 IPython,从而终止 Docker 容器。它可以通过 docker rm 删除。

代码语言:javascript复制
macbookpro:~/Docker$ docker rm 98b95
d9efb
macbookpro:~/Docker$

类似地,如果不再需要,可以通过 docker rmi 删除 Docker 镜像 ubuntupython。虽然容器相对轻量级,但单个镜像可能会消耗大量存储空间。在 py4fi:basic 镜像的情况下,其大小超过 1 GB。这就是为什么你可能想定期清理 Docker 镜像列表的原因。

代码语言:javascript复制
macbookpro:~/Docker$ docker rmi 60a180

当然,关于 Docker 容器及其在某些应用场景中的优势还有很多值得说的。对于本书和在线培训课程而言,它们提供了一种现代化的方法,用于部署 Python,在完全分离的(容器化)环境中进行 Python 开发,并为算法交易提供代码。

提示

如果你还没有使用 Docker 容器,应考虑开始使用它们。在 Python 部署和开发工作中,它们提供了许多好处,不仅在本地工作时,而且在使用远程云实例和服务器部署算法交易代码时尤其如此。

使用云实例

本节展示了如何在 DigitalOcean 云实例上设置一个完整的 Python 基础设施。还有许多其他的云服务提供商,其中包括 亚马逊网络服务(AWS)作为领先的提供商。然而,DigitalOcean 以其简单性和相对较低的价格而闻名,他们称之为 droplet 的较小的云实例。最小的 droplet,通常足够用于探索和开发目的,每月只需 5 美元或每小时 0.007 美元。用户只按小时计费,因此您可以轻松地为 2 小时启动一个 droplet,然后销毁它,仅需收取 0.014 美元。如果您还没有账户,请在这个 注册页面 上注册一个账户,这可以为您提供 10 美元的起始信用额。

本节的目标是在 DigitalOcean 上设置一个 droplet,其中包含 Python 3.6 安装和通常所需的包(例如 NumPypandas),以及一个密码保护和安全套接字层(SSL)加密的 Jupyter Notebook 服务器安装。作为一个基于 Web 的工具套件,Jupyter Notebook 提供了三个主要工具,可以通过常规浏览器使用:

  • Jupyter Notebook:这是目前非常流行的交互式开发环境,具有不同语言内核的选择,如 Python、R 和 Julia。
  • 终端:通过浏览器访问的系统 shell 实现,允许进行所有典型的系统管理任务,但也可以使用诸如 Vimgit 等有用工具。
  • 编辑器:第三个主要工具是一个基于浏览器的文件编辑器,具有许多不同的编程语言和文件类型的语法突出显示以及典型的编辑功能。

在一个 droplet 上安装 Jupyter Notebook 允许通过浏览器进行 Python 开发和部署,避免了通过安全外壳(SSH)访问登录云实例的需求。

为了完成本节的目标,需要一些文件。

  • 服务器设置脚本:这个脚本协调所有必要的步骤,比如将其他文件复制到 droplet 上并在 droplet 上运行它们。
  • Python 和 Jupyter 安装脚本:这个脚本安装 Python、额外的包、Jupyter Notebook 并启动 Jupyter Notebook 服务器。
  • Jupyter Notebook 配置文件:此文件用于配置 Jupyter Notebook 服务器,例如密码保护方面。
  • RSA 公钥和私钥文件:这两个文件用于对 Jupyter Notebook 服务器进行 SSL 加密。

接下来,我们将逆向处理这个文件列表。

RSA 公钥和私钥

为了通过任意浏览器安全连接到 Jupyter Notebook 服务器,需要一个由 RSA 公钥和私钥组成的 SSL 证书(参见RSA 维基百科页面)。一般来说,人们期望这样的证书来自所谓的证书颁发机构(CA)。然而,在本书的目的下,自动生成的证书已经“足够好“了。[⁶] 生成 RSA 密钥对的流行工具是OpenSSL。接下来的简要交互式会话生成了适用于 Jupyter Notebook 服务器的证书。

代码语言:javascript复制
macbookpro:~/cloud$ openssl req -x509 -nodes -days 365 -newkey 
> rsa:1024 -out cert.pem -keyout cert.key
Generating a 1024 bit RSA private key
..      
.......      
writing new private key to 'cert.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:Voelklingen
Organization Name (eg, company) [Internet Widgits Pty Ltd]:TPQ GmbH
Organizational Unit Name (eg, section) []:Algo Trading
Common Name (e.g. server FQDN or YOUR name) []:Jupyter
Email Address []:team@tpq.io
macbookpro:~/cloud$ ls
cert.key    cert.pem
macbookpro:~/cloud$

两个文件 cert.keycert.pem 需要复制到滴管上,并且需要被 Jupyter Notebook 配置文件引用。下面会介绍这个文件。

Jupyter Notebook 配置文件

可以按照运行 Notebook 服务器页面上的说明安全地部署一个公共 Jupyter Notebook 服务器。其中,Jupyter Notebook 应该受到密码保护。为此,有一个叫做 passwd 的密码哈希生成函数,可在 notebook.auth 子包中使用。下面的代码生成了一个以 jupyter 为密码的密码哈希代码。

代码语言:javascript复制
macbookpro:~/cloud$ ipython
Python 3.6.1 |Continuum Analytics, Inc.| (default, May 11 2017, 13:04:09)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from notebook.auth import passwd

In [2]: passwd('jupyter')
Out[2]: 'sha1:4c72b4542011:0a8735a18ef2cba12fde3744886e61f76706299b'

In [3]: exit

此哈希代码需要放置在 Jupyter Notebook 配置文件中,如示例 2-3 所示。该代码假定 RSA 密钥文件已复制到了滴管的 /root/.jupyter/ 文件夹中。

示例 2-3. Jupyter Notebook 配置文件
代码语言:javascript复制
#
# Jupyter Notebook Configuration File
#
# Python for Finance
# (c) Dr. Yves J. Hilpisch
#
# SSL ENCRYPTION
# replace the following file names (and files used) by your choice/files
c.NotebookApp.certfile = u'/root/.jupyter/cert.pem'
c.NotebookApp.keyfile = u'/root/.jupyter/cert.key'

# IP ADDRESS AND PORT
# set ip to '*' to bind on all IP addresses of the cloud instance
c.NotebookApp.ip = '*'
# it is a good idea to set a known, fixed default port for server access
c.NotebookApp.port = 8888

# PASSWORD PROTECTION
# here: 'jupyter' as password
# replace the hash code with the one for your password
c.NotebookApp.password = 'sha1:bb5e01be158a:99465f872e0613a3041ec25b7860175f59847702'

# NO BROWSER OPTION
# prevent Jupyter from trying to open a browser
c.NotebookApp.open_browser = False
注意

将 Jupyter Notebook 部署在云中主要会引发一系列安全问题,因为它是一个通过 Web 浏览器可访问的完整的开发环境。因此,使用 Jupyter Notebook 服务器默认提供的安全措施(如密码保护和 SSL 加密)至关重要。但这只是开始,根据在云实例上具体做了什么,可能建议采取进一步的安全措施。

下一步是确保 Python 和 Jupyter Notebook 在滴管上安装。

Python 和 Jupyter Notebook 的安装脚本

用于安装 Python 和 Jupyter Notebook 的 bash 脚本类似于在“使用 Docker 容器化”一节中介绍的用于在 Docker 容器中通过 Miniconda 安装 Python 的脚本。然而,这里的脚本还需要启动 Jupyter Notebook 服务器。所有主要部分和代码行都在内联注释中。

示例 2-4. 安装 Python 并运行 Jupyter Notebook 服务器的 bash 脚本
代码语言:javascript复制
#!/bin/bash
#
# Script to Install
# Linux System Tools and Basic Python Components
# as well as to
# Start Jupyter Notebook Server
#
# Python for Finance
# (c) Dr. Yves J. Hilpisch
#
# GENERAL LINUX
apt-get update  # updates the package index cache
apt-get upgrade -y  # updates packages
# install system tools
apt-get install -y bzip2 gcc git htop screen htop vim wget
apt-get upgrade -y bash  # upgrades bash if necessary
apt-get clean  # cleans up the package index cache

# INSTALLING MINICONDA
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O Miniconda.sh
bash Miniconda.sh -b  # installs Miniconda
rm -rf Miniconda.sh  # removes the installer
# prepends the new path for current session
export PATH="/root/miniconda3/bin:$PATH"
# prepends the new path in the shell configuration
cat >> ~/.profile <<EOF
export PATH="/root/miniconda3/bin:$PATH"
EOF

# INSTALLING PYTHON LIBRARIES
conda install -y jupyter  # interactive data analytics in the browser
conda install -y pytables  # wrapper for HDF5 binary storage
conda install -y pandas  #  data analysis package
conda install -y pandas-datareader  # retrieval of open data
conda install -y matplotlib  # standard plotting library
conda install -y seaborn  # statistical plotting library
conda install -y quandl  # wrapper for Quandl data API
conda install -y scikit-learn  # machine learning library
conda install -y tensorflow  # deep learning library
conda install -y flask  # light weight web framework
conda install -y openpyxl  # library for Excel interaction
conda install -y pyyaml  # library to manage yaml files

pip install --upgrade pip  # upgrading the package manager
pip install q  # logging and debugging
pip install plotly  # interactive D3.js plots
pip install cufflinks  # combining plotly with pandas

# COPYING FILES AND CREATING DIRECTORIES
mkdir /root/.jupyter
mv /root/jupyter_notebook_config.py /root/.jupyter/
mv /root/cert.* /root/.jupyter
mkdir /root/notebook
cd /root/notebook

# STARTING JUPYTER NOTEBOOK
jupyter notebook --allow-root

此脚本需要复制到滴管上,并且需要由下一小节中描述的编排脚本启动。

管理滴管设置的脚本

设置滴水筹的第二个 bash 脚本是最短的。它主要将所有其他文件复制到滴水筹中,其中预期的是相应的 IP 地址作为参数。在最后一行,它启动install.sh bash 脚本,后者又会进行安装并启动 Jupyter Notebook 服务器。

示例 2-5. 用于设置滴水筹的bash脚本
代码语言:javascript复制
#!/bin/bash
#
# Setting up a DigitalOcean Droplet
# with Basic Python Stack
# and Jupyter Notebook
#
# Python for Finance
# (c) Dr Yves J Hilpisch
#

# IP ADDRESS FROM PARAMETER
MASTER_IP=$1

# COPYING THE FILES
scp install.sh root@${MASTER_IP}:
scp cert.* jupyter_notebook_config.py root@${MASTER_IP}:

# EXECUTING THE INSTALLATION SCRIPT
ssh root@${MASTER_IP} bash /root/install.sh

现在一切都准备好尝试设置代码。在 DigitalOcean 上,创建一个新的滴水筹,并选择类似于以下选项:

  • 操作系统:Ubuntu 16.04.3 x64(截至 2017 年 11 月 4 日的默认选择)
  • 规模:1 核心,512 MB,20GB SSD(最小的滴水筹)
  • 数据中心区域:法兰克福(因为作者居住在德国)
  • SSH 密钥:添加(新的)SSH 密钥以实现无密码登录 ^(7)
  • 滴水筹名称:您可以选择预先指定的名称,也可以选择像py4fi这样的名称

最后,点击创建按钮启动滴水筹建过程,通常需要约一分钟。进行设置过程的主要结果是 IP 地址,例如,当您选择法兰克福作为数据中心位置时,可能为 46.101.156.199。现在设置滴水筹非常简单:

代码语言:javascript复制
(py3) macbookpro:~/cloud$ bash setup.sh 46.101.156.199

结果过程可能需要几分钟。当 Jupyter Notebook 服务器出现类似于以下消息时,过程结束:

代码语言:javascript复制
The Jupyter Notebook is running at: https://[all ip addresses on your system]:8888/

在任何当前浏览器中,访问以下地址即可访问正在运行的 Jupyter Notebook(注意https协议):

代码语言:javascript复制
https://46.101.156.199:8888

可能需要添加安全例外,然后 Jupyter Notebook 登录屏幕会提示输入密码(在我们的情况下为jupyter)。现在一切准备就绪,可以通过 Jupyter Notebook 在浏览器中开始 Python 开发,通过终端窗口进行IPython或文本文件编辑器。还提供了其他文件管理功能,如文件上传,删除文件或创建文件夹。

小贴士

像 DigitalOcean 和 Jupyter Notebook 这样的云实例是算法交易员的强大组合,可以利用专业的计算和存储基础设施。专业的云和数据中心提供商确保您的(虚拟)机器在物理上安全且高度可用。使用云实例也可以使探索和开发阶段的成本相对较低,因为通常按小时收费,而无需签订长期协议。

结论

Python 是本书选择的编程语言和技术平台。然而,Python 部署可能最多时甚至令人厌烦和心神不宁。幸运的是,今天有一些技术可用 — 都不到五年的时间 — 有助于解决部署问题。开源软件conda有助于 Python 包和虚拟环境的管理。Docker 容器甚至可以进一步扩展,可以在一个技术上受到保护的"沙箱"中轻松创建完整的文件系统和运行时环境。更进一步,像 DigitalOcean 这样的云提供商在几分钟内提供由专业管理和安全的数据中心提供的计算和存储容量,并按小时计费。这与 Python 3.6 安装和安全的 Jupyter Notebook 服务器安装结合在一起,为算法交易项目的 Python 开发和部署提供了专业的环境。

进一步资源

对于Python 包管理,请参考以下资源:

  • pip包管理器页面
  • conda包管理器页面
  • 官方安装包页面

对于虚拟环境管理,请参阅以下资源:

  • virtualenv环境管理器页面
  • conda环境管理页面

关于Docker 容器的信息请见此处:

  • Docker 首页
  • Matthias、Karl 和 Sean Kane (2015): Docker: Up and Running. O’Reilly, 北京等。

Robbins (2016)提供了对bash 脚本语言的简明介绍和概述。

  • Robbins, Arnold (2016): Bash Pocket Reference. 第 2 版,O’Reilly, 北京等。

如何安全运行公共 Jupyter Notebook 服务器请参阅运行笔记本服务器。

要在 DigitalOcean 上注册一个新帐户并获得 10 美元的起始信用,请访问此注册页面。这可以支付最小水滴两个月的使用费。

¹在撰写本文时,Python 3.7beta 刚刚发布。

²在 Windows 上,激活新环境的命令仅为activate py27 — 省略了source

³在官方文档中,您会找到以下解释:“Python '虚拟环境'允许将 Python 包安装在特定应用程序的隔离位置,而不是全局安装。"参见创建虚拟环境页面。

⁴ 有关 Docker 技术的全面介绍,请参阅 Matthias 和 Kane (2015) 的书籍。

⁵ 要了解 bash 脚本的简明介绍和快速概述,请参考 Robbins (2016) 的书籍。

⁶ 使用这样一个自行生成的证书时,可能需要在浏览器提示时添加安全异常。

⁷ 如果需要帮助,请访问如何在 DigitalOcean Droplets 上使用 SSH 密钥或如何在 DigitalOcean Droplets 上使用 PuTTY(Windows 用户)。

第二部分:掌握基础知识

本书的这部分内容涉及 Python 编程的基础知识。本部分涵盖的主题对于随后部分中的所有其他章节都是基础的。

这些章节按照特定主题组织,使读者可以作为参考,查找与感兴趣的主题相关的示例和详细信息:

  • 第三章 关于 Python 的数据类型和数据结构
  • 第四章 关于 NumPy 及其 ndarray
  • 第五章 关于 pandas 及其 DataFrame
  • 第六章 关于使用 Python 的面向对象编程(OOP)

第三章:数据类型和结构

糟糕的程序员担心代码。优秀的程序员担心数据结构及其关系。 Linus Torvalds

介绍

本章介绍了Python的基本数据类型和数据结构。尽管Python解释器本身已经带来了丰富多样的数据结构,但NumPy和其他库在其中增添了宝贵的内容。

本章组织如下:

“基本数据类型”

第一节介绍了基本数据类型,如intfloatstring

“基本数据结构”

下一节介绍了Python的基本数据结构(例如list对象)并说明了控制结构、函数式编程范式和匿名函数。

本章的精神是在涉及数据类型和结构时提供对Python特定内容的一般介绍。如果您具备来自其他编程语言(例如CMatlab)的背景,那么您应该能够轻松掌握Python用法可能带来的差异。此处介绍的主题对于接下来的章节都是重要且基础的。

本章涵盖以下数据类型和结构:

对象类型

含义

用法/模型

int

整数值

自然数

float

浮点数

实数

bool

布尔值

某种真或假

str

字符串对象

字符、单词、文本

tuple

不可变容器

固定的对象集合、记录

list

可变容器

变化的对象集合

dict

可变容器

键-值存储

set

可变容器

唯一对象的集合

基本数据类型

Python 是一种动态类型语言,这意味着Python解释器在运行时推断对象的类型。相比之下,像C这样的编译语言通常是静态类型的。在这些情况下,对象的类型必须在编译时与对象关联。¹

整数

最基本的数据类型之一是整数,或者int

代码语言:javascript复制
In [1]: a = 10
        type(a)
Out[1]: int

内置函数type提供了标准和内置类型的所有对象的类型信息,以及新创建的类和对象。在后一种情况下,提供的信息取决于程序员与类存储的描述。有一句话说“Python中的一切都是对象。”这意味着,例如,即使是我们刚刚定义的int对象这样的简单对象也有内置方法。例如,您可以通过调用方法bit_length来获取表示内存中的int对象所需的位数:

代码语言:javascript复制
In [2]: a.bit_length()
Out[2]: 4

您会发现,所需的位数随我们分配给对象的整数值的增加而增加:

代码语言:javascript复制
In [3]: a = 100000
        a.bit_length()
Out[3]: 17

一般来说,有很多不同的方法,很难记住所有类和对象的所有方法。高级Python环境,如IPython,提供了可以显示附加到对象的所有方法的制表符完成功能。您只需键入对象名称,然后跟一个点(例如,a.),然后按 Tab 键,例如,a.*tab*。然后,这将提供您可以调用的对象的方法集合。或者,Python内置函数dir提供了任何对象的完整属性和方法列表。

Python的一个特殊之处在于整数可以是任意大的。例如,考虑谷歌数 10¹⁰⁰。Python对于这样的大数没有问题,这些大数在技术上是long对象:

代码语言:javascript复制
In [4]: googol = 10 ** 100
        googol
Out[4]: 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

In [5]: googol.bit_length()
Out[5]: 333

大整数

Python 整数可以是任意大的。解释器会根据需要使用尽可能多的位/字节来表示数字。

整数的算术运算易于实现:

代码语言:javascript复制
In [6]: 1   4
Out[6]: 5

In [7]: 1 / 4
Out[7]: 0.25

In [8]: type(1 / 4)
Out[8]: float

浮点数

最后一个表达式返回通常期望的结果 0.25(在基本的 Python 2.7 中不同)。这使我们进入了下一个基本数据类型,即float对象。在整数值后添加一个点,如1.1.0,会导致Python将对象解释为float。涉及float的表达式通常也返回一个float对象:^(2)

代码语言:javascript复制
In [9]: 1.6 / 4
Out[9]: 0.4

In [10]: type (1.6 / 4)
Out[10]: float

float稍微复杂一些,因为有理数或实数的计算机化表示通常不是精确的,而是取决于所采取的具体技术方法。为了说明这意味着什么,让我们定义另一个float对象b。像这样的float对象总是内部表示为仅具有一定精度的。当向b添加 0.1 时,这变得明显:

代码语言:javascript复制
In [11]: b = 0.35
         type(b)
Out[11]: float

In [12]: b   0.1
Out[12]: 0.44999999999999996

这是因为 float s 在内部以二进制格式表示;也就是说,十进制数0 < n < 1通过形式为n = x 2 y 4 z 8 . . .的系列表示。对于某些浮点数,二进制表示可能涉及大量元素,甚至可能是一个无限级数。然而,给定用于表示此类数字的固定位数-即表示系列中的固定项数-不准确是其结果。其他数字可以完美表示,因此即使只有有限数量的位可用,它们也会被精确地存储。考虑以下示例:

代码语言:javascript复制
In [13]: c = 0.5
         c.as_integer_ratio()
Out[13]: (1, 2)

一半,即 0.5,被准确地存储,因为它有一个精确(有限)的二进制表示:

</mi> <mn>0</mn> <mo>.</mo> <mn>5</mn> <mo>=</mo> <mfrac><mn>1</mn> <mn>2</mn></mfrac> <mi>

。然而,对于b = 0.35,我们得到的结果与期望的有理数

</mi> <mn>0</mn> <mo>.</mo> <mn>35</mn> <mo>=</mo> <mfrac><mn>7</mn> <mn>20</mn></mfrac> <mi>

不同:

代码语言:javascript复制
In [14]: b.as_integer_ratio()
Out[14]: (3152519739159347, 9007199254740992)

精度取决于用于表示数字的位数。一般来说,所有Python运行的平台都使用 IEEE 754 双精度标准(即 64 位)来进行内部表示。³ 这意味着相对精度为 15 位数字。

由于这个主题在金融等几个应用领域非常重要,有时需要确保数字的确切或至少是最佳可能的表示。例如,在对一大堆数字求和时,这个问题可能很重要。在这种情况下,某种类型和/或数量级别的表示误差可能导致与基准值的显著偏差。

模块decimal提供了一个用于浮点数的任意精度对象,以及几个选项来解决使用这些数字时的精度问题:

代码语言:javascript复制
In [15]: import decimal
         from decimal import Decimal

In [16]: decimal.getcontext()
Out[16]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])

In [17]: d = Decimal(1) / Decimal (11)
         d
Out[17]: Decimal('0.09090909090909090909090909091')

您可以通过改变Context对象的相应属性值来改变表示的精度:

代码语言:javascript复制
In [18]: decimal.getcontext().prec = 4  # ①

In [19]: e = Decimal(1) / Decimal (11)
         e
Out[19]: Decimal('0.09091')

In [20]: decimal.getcontext().prec = 50  # ②

In [21]: f = Decimal(1) / Decimal (11)
         f
Out[21]: Decimal('0.090909090909090909090909090909090909090909090909091')

低于默认精度。

高于默认精度。

如有需要,可以通过这种方式调整精度以适应手头的确切问题,并且可以处理具有不同精度的浮点对象:

代码语言:javascript复制
In [22]: g = d   e   f
         g
Out[22]: Decimal('0.27272818181818181818181818181909090909090909090909')

任意精度浮点数

模块decimal提供了一个任意精度浮点数对象。在金融中,有时需要确保高精度并超越 64 位双精度标准。

布尔值

在编程中,评估比较或逻辑表达式,例如4 > 34.5 <= 3.25(4 > 3) and (3 > 2),会产生TrueFalse之一作为输出,这是两个重要的 Python 关键字。其他例如defforif。Python 关键字的完整列表可在keyword模块中找到。

代码语言:javascript复制
In [23]: import keyword

In [24]: keyword.kwlist
Out[24]: ['False',
          'None',
          'True',
          'and',
          'as',
          'assert',
          'break',
          'class',
          'continue',
          'def',
          'del',
          'elif',
          'else',
          'except',
          'finally',
          'for',
          'from',
          'global',
          'if',
          'import',
          'in',
          'is',
          'lambda',
          'nonlocal',
          'not',
          'or',
          'pass',
          'raise',
          'return',
          'try',
          'while',
          'with',
          'yield']

TrueFalsebool数据类型,代表布尔值。以下代码展示了 Python 对相同操作数应用比较操作符后生成的bool对象。

代码语言:javascript复制
In [25]: 4 > 3  # ①
Out[25]: True

In [26]: type(4 > 3)
Out[26]: bool

In [27]: type(False)
Out[27]: bool

In [28]: 4 >= 3  # ②
Out[28]: True

In [29]: 4 < 3  # ③
Out[29]: False

In [30]: 4 <= 3  # ④
Out[30]: False

In [31]: 4 == 3  # ⑤
Out[31]: False

In [32]: 4 != 3  # ⑥
Out[32]: True

更大。

大于或等于。

更小。

小于或等于。

相等。

不相等。

经常,逻辑运算符被应用于bool对象,从而产生另一个bool对象。

代码语言:javascript复制
In [33]: True and True
Out[33]: True

In [34]: True and False
Out[34]: False

In [35]: False and False
Out[35]: False

In [36]: True or True
Out[36]: True

In [37]: True or False
Out[37]: True

In [38]: False or False
Out[38]: False

In [39]: not True
Out[39]: False

In [40]: not False
Out[40]: True

当然,这两种类型的运算符经常结合使用。

代码语言:javascript复制
In [41]: (4 > 3) and (2 > 3)
Out[41]: False

In [42]: (4 == 3) or (2 != 3)
Out[42]: True

In [43]: not (4 != 4)
Out[43]: True

In [44]: (not (4 != 4)) and (2 == 3)
Out[44]: False

其一主要应用是通过其他 Python 关键字(例如ifwhile)来控制代码流程(本章后面将有更多示例)。

代码语言:javascript复制
In [45]: if 4 > 3:  # ①
             print('condition true')  # ②

         condition true

In [46]: i = 0  # ③
         while i < 4:  # ④
             print('condition true, i = ', i)  # ⑤
             i  = 1  # ⑥

         condition true, i =  0
         condition true, i =  1
         condition true, i =  2
         condition true, i =  3

如果条件成立,则执行要跟随的代码。

如果条件成立,则执行要跟随的代码。

使用 0 初始化参数i

只要条件成立,就执行并重复执行后续代码。

打印文本和参数i的值。

将参数值增加 1;i = 1等同于i = i 1

在数值上,Python 将False赋值为 0,将True赋值为 1。通过bool()函数将数字转换为bool对象时,0 给出False,而所有其他数字给出True

代码语言:javascript复制
In [47]: int(True)
Out[47]: 1

In [48]: int(False)
Out[48]: 0

In [49]: float(True)
Out[49]: 1.0

In [50]: float(False)
Out[50]: 0.0

In [51]: bool(0)
Out[51]: False

In [52]: bool(0.0)
Out[52]: False

In [53]: bool(1)
Out[53]: True

In [54]: bool(10.5)
Out[54]: True

In [55]: bool(-2)
Out[55]: True

字符串

现在我们可以表示自然数和浮点数,我们转向文本。在Python中表示文本的基本数据类型是stringstring对象具有许多非常有用的内置方法。事实上,当涉及到处理任何类型和任何大小的文本文件时,Python通常被认为是一个很好的选择。string对象通常由单引号或双引号定义,或者通过使用str函数转换另一个对象(即使用对象的标准或用户定义的string表示)来定义:

代码语言:javascript复制
In [56]: t = 'this is a string object'

关于内置方法,例如,您可以将对象中的第一个单词大写:

代码语言:javascript复制
In [57]: t.capitalize()
Out[57]: 'This is a string object'

或者您可以将其拆分为其单词组件以获得所有单词的list对象(稍后会有关于list对象的更多内容):

代码语言:javascript复制
In [58]: t.split()
Out[58]: ['this', 'is', 'a', 'string', 'object']

您还可以搜索单词并在成功情况下获得单词的第一个字母的位置(即,索引值):

代码语言:javascript复制
In [59]: t.find('string')
Out[59]: 10

如果单词不在string对象中,则该方法返回-1:

代码语言:javascript复制
In [60]: t.find('Python')
Out[60]: -1

replace方法轻松替换字符串中的字符是一个典型的任务:

代码语言:javascript复制
In [61]: t.replace(' ', '|')
Out[61]: 'this|is|a|string|object'

字符串的剥离—即删除某些前导/后置字符—也经常是必要的:

代码语言:javascript复制
In [62]: 'http://www.python.org'.strip('htp:/')
Out[62]: 'www.python.org'

表格 3-1 列出了string对象的许多有用方法。

表格 3-1. 选择的字符串方法

方法

参数

返回/结果

capitalize

()

第一个字母大写的字符串副本

count

(子串[, 开始[, 结束]])

子串出现次数的计数

decode

([编码[, 错误]])

使用编码(例如,UTF-8)的字符串的解码版本

encode

([编码 [, 错误]])

字符串的编码版本

find

(sub[, start[, end]])

找到子字符串的(最低)索引

join

(seq)

将序列seq中的字符串连接起来

replace

(old, new[, count])

将old替换为new的第一个count次

split

([sep[, maxsplit]])

以sep为分隔符的字符串中的单词列表

splitlines

([keepends])

如果keepends为True,则带有行结束符/断行的分隔行

strip

(chars)

删除chars中的前导/尾随字符的字符串的副本

upper

()

复制并将所有字母大写

注意

从 Python 2.7(本书的第一版)到 Python 3.6(本书的第二版使用的版本)的基本变化是字符串对象的编码和解码以及 Unicode 的引入(参见https://docs.python.org/3/howto/unicode.html)。本章不允许详细讨论此上下文中重要的许多细节。对于本书的目的,主要涉及包含英文单词的数字数据和标准字符串,这种省略似乎是合理的。

附录:打印和字符串替换

打印str对象或其他 Python 对象的字符串表示通常是通过print()函数完成的(在 Python 2.7 中是一个语句)。

代码语言:javascript复制
In [63]: print('Python for Finance')  # ①

         Python for Finance

In [64]: print(t)  # ②

         this is a string object

In [65]: i = 0
         while i < 4:
             print(i)  # ③
             i  = 1

         0
         1
         2
         3

In [66]: i = 0
         while i < 4:
             print(i, end='|')  # ④
             i  = 1

         0|1|2|3|

打印一个str对象。

打印由变量名引用的str对象。

打印int对象的字符串表示。

指定打印的最后一个字符(默认为前面看到的换行符n)。

Python 提供了强大的字符串替换操作。有通过%字符进行的旧方法和通过花括号{}format()进行的新方法。两者在实践中仍然适用。本节不能提供所有选项的详尽说明,但以下代码片段显示了一些重要的内容。首先,的方法。

代码语言:javascript复制
In [67]: 'this is an integer %d' % 15  # ①
Out[67]: 'this is an integer 15'

In [68]: 'this is an integer M' % 15  # ②
Out[68]: 'this is an integer   15'

In [69]: 'this is an integer d' % 15  # ③
Out[69]: 'this is an integer 0015'

In [70]: 'this is a float %f' % 15.3456  # ④
Out[70]: 'this is a float 15.345600'

In [71]: 'this is a float %.2f' % 15.3456  # ⑤
Out[71]: 'this is a float 15.35'

In [72]: 'this is a float �' % 15.3456  # ⑥
Out[72]: 'this is a float 15.345600'

In [73]: 'this is a float %8.2f' % 15.3456  # ⑦
Out[73]: 'this is a float    15.35'

In [74]: 'this is a float .2f' % 15.3456  # ⑧
Out[74]: 'this is a float 00015.35'

In [75]: 'this is a string %s' % 'Python'  # ⑨
Out[75]: 'this is a string Python'

In [76]: 'this is a string s' % 'Python'  # ⑩
Out[76]: 'this is a string     Python'

int对象替换。

带有固定数量的字符。

如果必要,带有前导零。

float对象替换。

带有固定数量的小数位数。

带有固定数量的字符(并填充小数)。

带有固定数量的字符和小数位数…

… 以及必要时的前导零。

str对象替换。

带有固定数量的字符。

现在以方式实现相同的示例。请注意,输出在某些地方略有不同。

代码语言:javascript复制
In [77]: 'this is an integer {:d}'.format(15)
Out[77]: 'this is an integer 15'

In [78]: 'this is an integer {:4d}'.format(15)
Out[78]: 'this is an integer   15'

In [79]: 'this is an integer {:04d}'.format(15)
Out[79]: 'this is an integer 0015'

In [80]: 'this is a float {:f}'.format(15.3456)
Out[80]: 'this is a float 15.345600'

In [81]: 'this is a float {:.2f}'.format(15.3456)
Out[81]: 'this is a float 15.35'

In [82]: 'this is a float {:8f}'.format(15.3456)
Out[82]: 'this is a float 15.345600'

In [83]: 'this is a float {:8.2f}'.format(15.3456)
Out[83]: 'this is a float    15.35'

In [84]: 'this is a float {:08.2f}'.format(15.3456)
Out[84]: 'this is a float 00015.35'

In [85]: 'this is a string {:s}'.format('Python')
Out[85]: 'this is a string Python'

In [86]: 'this is a string {:10s}'.format('Python')
Out[86]: 'this is a string Python    '

字符串替换在多次打印操作的上下文中特别有用,其中打印的数据会更新,例如,在while循环期间。

代码语言:javascript复制
In [87]: i = 0
         while i < 4:
             print('the number is %d' % i)
             i  = 1

         the number is 0
         the number is 1
         the number is 2
         the number is 3

In [88]: i = 0
         while i < 4:
             print('the number is {:d}'.format(i))
             i  = 1

         the number is 0
         the number is 1
         the number is 2
         the number is 3

旅行:正则表达式

在处理string对象时,使用正则表达式是一种强大的工具。Python在模块re中提供了这样的功能:

代码语言:javascript复制
In [89]: import re

假设你面对一个大文本文件,例如一个逗号分隔值(CSV)文件,其中包含某些时间序列和相应的日期时间信息。往往情况下,日期时间信息以Python无法直接解释的格式提供。然而,日期时间信息通常可以通过正则表达式描述。考虑以下string对象,其中包含三个日期时间元素,三个整数和三个字符串。请注意,三重引号允许在多行上定义字符串:

代码语言:javascript复制
In [90]: series = """
 '01/18/2014 13:00:00', 100, '1st';
 '01/18/2014 13:30:00', 110, '2nd';
 '01/18/2014 14:00:00', 120, '3rd'
 """

以下正则表达式描述了提供在string对象中的日期时间信息的格式:⁴

代码语言:javascript复制
In [91]: dt = re.compile("'[0-9/:s] '")  # datetime

有了这个正则表达式,我们可以继续找到所有日期时间元素。通常,将正则表达式应用于string对象还会导致典型解析任务的性能改进:

代码语言:javascript复制
In [92]: result = dt.findall(series)
         result
Out[92]: ["'01/18/2014 13:00:00'", "'01/18/2014 13:30:00'", "'01/18/2014 14:00:00'"]

正则表达式

在解析string对象时,考虑使用正则表达式,这可以为此类操作带来便利性和性能。

然后可以解析生成Python datetime对象的结果string对象(参见[Link to Come],了解如何使用Python处理日期和时间数据的概述)。要解析包含日期时间信息的string对象,我们需要提供如何解析的信息 —— 再次作为string对象:

代码语言:javascript复制
In [93]: from datetime import datetime
         pydt = datetime.strptime(result[0].replace("'", ""),
                                  '%m/%d/%Y %H:%M:%S')
         pydt
Out[93]: datetime.datetime(2014, 1, 18, 13, 0)

In [94]: print(pydt)

         2014-01-18 13:00:00

In [95]: print(type(pydt))

         <class 'datetime.datetime'>

后续章节将提供有关日期时间数据的更多信息,以及处理此类数据和datetime对象及其方法。这只是对金融中这一重要主题的一个引子。

基本数据结构

作为一个通用规则,数据结构是包含可能大量其他对象的对象。在Python提供的内置结构中包括:

tuple

一个不可变的任意对象的集合;只有少量方法可用

list

一个可变的任意对象的集合;有许多方法可用

dict

键-值存储对象

set

用于其他唯一对象的无序集合对象

元组

tuple是一种高级数据结构,但在其应用中仍然相当简单且有限。通过在括号中提供对象来定义它:

代码语言:javascript复制
In [96]: t = (1, 2.5, 'data')
         type(t)
Out[96]: tuple

你甚至可以放弃括号,提供多个对象,只需用逗号分隔:

代码语言:javascript复制
In [97]: t = 1, 2.5, 'data'
         type(t)
Out[97]: tuple

像几乎所有的Python数据结构一样,tuple具有内置索引,借助它可以检索单个或多个tuple元素。重要的是要记住,Python使用零基编号,因此tuple的第三个元素位于索引位置 2:

代码语言:javascript复制
In [98]: t[2]
Out[98]: 'data'

In [99]: type(t[2])
Out[99]: str

零基编号

与其他一些编程语言(如Matlab)相比,Python使用零基编号方案。例如,tuple对象的第一个元素的索引值为 0。

这种对象类型提供的特殊方法仅有两个:countindex。第一个方法统计某个对象的出现次数,第二个方法给出其第一次出现的索引值:

代码语言:javascript复制
In [100]: t.count('data')
Out[100]: 1

In [101]: t.index(1)
Out[101]: 0

tuple对象是不可变对象。这意味着一旦定义,它们就不容易更改。

列表

类型为list的对象比tuple对象更加灵活和强大。从财务角度来看,你可以仅使用list对象就能实现很多,比如存储股价报价和添加新数据。list对象通过括号定义,其基本功能和行为与tuple对象相似:

代码语言:javascript复制
In [102]: l = [1, 2.5, 'data']
          l[2]
Out[102]: 'data'

也可以通过使用函数list来定义或转换list对象。以下代码通过转换前面示例中的tuple对象生成一个新的list对象:

代码语言:javascript复制
In [103]: l = list(t)
          l
Out[103]: [1, 2.5, 'data']

In [104]: type(l)
Out[104]: list

除了tuple对象的特性外,list对象还可以通过不同的方法进行扩展和缩减。换句话说,虽然stringtuple对象是不可变序列对象(具有索引),一旦创建就无法更改,但list对象是可变的,并且可以通过不同的操作进行更改。你可以将list对象附加到现有的list对象上,等等:

代码语言:javascript复制
In [105]: l.append([4, 3])  # ①
          l
Out[105]: [1, 2.5, 'data', [4, 3]]

In [106]: l.extend([1.0, 1.5, 2.0])  # ②
          l
Out[106]: [1, 2.5, 'data', [4, 3], 1.0, 1.5, 2.0]

In [107]: l.insert(1, 'insert')  # ③
          l
Out[107]: [1, 'insert', 2.5, 'data', [4, 3], 1.0, 1.5, 2.0]

In [108]: l.remove('data')  # ④
          l
Out[108]: [1, 'insert', 2.5, [4, 3], 1.0, 1.5, 2.0]

In [109]: p = l.pop(3)  # ⑤
          print(l, p)

          [1, 'insert', 2.5, 1.0, 1.5, 2.0] [4, 3]

在末尾附加list对象。

添加list对象的元素。

在索引位置之前插入对象。

删除对象的第一次出现。

删除并返回索引位置的对象。

切片也很容易实现。在这里,切片指的是将数据集分解为较小部分(感兴趣的部分)的操作:

代码语言:javascript复制
In [110]: l[2:5]  # ①
Out[110]: [2.5, 1.0, 1.5]

第三到第五个元素。

Table 3-2 提供了list对象的选定操作和方法的摘要。

表 3-2. list对象的选定操作和方法

方法

参数

返回/结果

l[i] = x

[i]

将第i个元素替换为x

l[i:j:k] = s

[i:j:k]

用s替换从i到j-1的每个第k个元素

append

(x)

将x附加到对象

count

(x)

对象x的出现次数

del l[i:j:k]

[i:j:k]

删除索引值为i到j-1的元素

extend

(s)

将s的所有元素附加到对象

index

(x[, i[, j]])

元素i和j-1之间x的第一个索引

insert

(i, x)

在索引i之前/后插入x

remove

(i)

删除索引为i的元素

pop

(i)

删除索引为i的元素并返回它

reverse

()

将所有项目原地颠倒

sort

([cmp[, key[, reverse]]])

原地对所有项目排序

专题:控制结构

虽然控制结构本身是一个专题,像for循环这样的控制结构可能最好是基于Python中的list对象介绍的。这是因为一般情况下循环是在list对象上进行的,这与其他语言中通常的标准相当不同。看下面的例子。for循环遍历list对象l的元素,索引值为 2 到 4,并打印出相应元素的平方。注意第二行缩进(空格)的重要性:

代码语言:javascript复制
In [111]: for element in l[2:5]:
              print(element ** 2)

          6.25
          1.0
          2.25

这相比于典型的基于计数器的循环提供了非常高的灵活性。基于(标准的)list对象range也可以使用计数器进行循环:

代码语言:javascript复制
In [112]: r = range(0, 8, 1)  # ①
          r
Out[112]: range(0, 8)

In [113]: type(r)
Out[113]: range

参数是startendstep size

为了比较,相同的循环使用range实现如下:

代码语言:javascript复制
In [114]: for i in range(2, 5):
              print(l[i] ** 2)

          6.25
          1.0
          2.25

遍历列表

Python中,你可以遍历任意的list对象,不管对象的内容是什么。这通常避免了引入计数器。

Python还提供了典型的(条件)控制元素ifelifelse。它们在其他语言中的使用方式类似:

代码语言:javascript复制
In [115]: for i in range(1, 10):
              if i % 2 == 0:  # ①
                  print("%d is even" % i)
              elif i % 3 == 0:
                  print("%d is multiple of 3" % i)
              else:
                  print("%d is odd" % i)

          1 is odd
          2 is even
          3 is multiple of 3
          4 is even
          5 is odd
          6 is even
          7 is odd
          8 is even
          9 is multiple of 3

%代表取模。

同样,while提供了另一种控制流的手段:

代码语言:javascript复制
In [116]: total = 0
          while total < 100:
              total  = 1
          print(total)

          100

Python的一个特点是所谓的list 推导式。与遍历现有list对象不同,这种方法以一种相当紧凑的方式通过循环生成list对象:

代码语言:javascript复制
In [117]: m = [i ** 2 for i in range(5)]
          m
Out[117]: [0, 1, 4, 9, 16]

从某种意义上说,这已经提供了一种生成“类似”的向量化代码的第一手段,因为循环相对来说更加隐式而不是显式的(代码的向量化将在本章后面更详细地讨论)。

专题:功能编程

Python还提供了一些功能编程支持工具,即将函数应用于整套输入(在我们的情况下是list对象)。其中包括filtermapreduce。然而,我们首先需要一个函数定义。首先从一个非常简单的函数开始,考虑一个返回输入x的平方的函数f

代码语言:javascript复制
In [118]: def f(x):
              return x ** 2
          f(2)
Out[118]: 4

当然,函数可以是任意复杂的,具有多个输入/参数对象,甚至多个输出(返回对象)。但是,考虑以下函数:

代码语言:javascript复制
In [119]: def even(x):
              return x % 2 == 0
          even(3)
Out[119]: False

返回对象是一个布尔值。这样的函数可以通过使用 map 应用于整个 list 对象:

代码语言:javascript复制
In [120]: list(map(even, range(10)))
Out[120]: [True, False, True, False, True, False, True, False, True, False]

为此,我们还可以直接将函数定义作为 map 的参数提供,通过使用 lambda匿名函数:

代码语言:javascript复制
In [121]: list(map(lambda x: x ** 2, range(10)))
Out[121]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

函数也可以用于过滤 list 对象。在下面的示例中,过滤器返回符合由 even 函数定义的布尔条件的 list 对象元素:

代码语言:javascript复制
In [122]: list(filter(even, range(15)))
Out[122]: [0, 2, 4, 6, 8, 10, 12, 14]

列表推导、函数式编程、匿名函数

Python 级别尽可能避免使用循环被认为是良好的实践list 推导和函数式编程工具如 mapfilterreduce 提供了编写没有(显式)循环的代码的方法,这种代码既紧凑又通常更可读。在这种情况下,lambda 或匿名函数也是强大的工具。

字典

dict 对象是字典,也是可变序列,允许通过可以是 string 对象的键来检索数据。它们被称为键值存储。虽然 list 对象是有序且可排序的,但 dict 对象是无序且不可排序的。通过示例可以更好地说明与 list 对象的进一步差异。花括号是定义 dict 对象的标志:

代码语言:javascript复制
In [123]: d = {
               'Name' : 'Angela Merkel',
               'Country' : 'Germany',
               'Profession' : 'Chancelor',
               'Age' : 63
               }
          type(d)
Out[123]: dict

In [124]: print(d['Name'], d['Age'])

          Angela Merkel 63

同样,这类对象具有许多内置方法:

代码语言:javascript复制
In [125]: d.keys()
Out[125]: dict_keys(['Name', 'Country', 'Profession', 'Age'])

In [126]: d.values()
Out[126]: dict_values(['Angela Merkel', 'Germany', 'Chancelor', 63])

In [127]: d.items()
Out[127]: dict_items([('Name', 'Angela Merkel'), ('Country', 'Germany'), ('Profession', 'Chancelor'), ('Age', 63)])

In [128]: birthday = True
          if birthday is True:
              d['Age']  = 1
          print(d['Age'])

          64

有几种方法可以从 dict 对象获取 iterator 对象。当进行迭代时,这些对象的行为类似于 list 对象:

代码语言:javascript复制
In [129]: for item in d.items():
              print(item)

          ('Name', 'Angela Merkel')
          ('Country', 'Germany')
          ('Profession', 'Chancelor')
          ('Age', 64)

In [130]: for value in d.values():
              print(type(value))

          <class 'str'>
          <class 'str'>
          <class 'str'>
          <class 'int'>

表 3-3 提供了 dict 对象的选定操作和方法的摘要。

表 3-3. dict 对象的选定操作和方法

方法

参数

返回/结果

d[k]

[k]

d 中具有键 k 的项目

d[k] = x

[k]

将项目键 k 设置为 x

del d[k]

[k]

删除具有键 k 的项目

clear

()

删除所有项目

copy

()

复制一个副本

has_key

(k)

如果 k 是键,则为 True

items

()

迭代器遍历所有项目

keys

()

迭代器遍历所有键

values

()

迭代器遍历所有值

popitem

(k)

返回并删除具有键 k 的项目

update

([e])

用 e 中的项目更新项目

集合

我们将考虑的最后一个数据结构是 set 对象。尽管集合论是数学和金融理论的基石,但对于 set 对象的实际应用并不太多。这些对象是其他对象的无序集合,每个元素只包含一次:

代码语言:javascript复制
In [131]: s = set(['u', 'd', 'ud', 'du', 'd', 'du'])
          s
Out[131]: {'d', 'du', 'u', 'ud'}

In [132]: t = set(['d', 'dd', 'uu', 'u'])

使用 set 对象,您可以像在数学集合论中一样实现操作。例如,您可以生成并集、交集和差异:

代码语言:javascript复制
In [133]: s.union(t)  # ①
Out[133]: {'d', 'dd', 'du', 'u', 'ud', 'uu'}

In [134]: s.intersection(t)  # ②
Out[134]: {'d', 'u'}

In [135]: s.difference(t)  # ③
Out[135]: {'du', 'ud'}

In [136]: t.difference(s)  # ④
Out[136]: {'dd', 'uu'}

In [137]: s.symmetric_difference(t)  # ⑤
Out[137]: {'dd', 'du', 'ud', 'uu'}

st 的全部。

st 中都有。

s 中但不在 t 中。

t 中但不在 s 中。

在其中一个但不是两者都。

set对象的一个应用是从list对象中消除重复项。例如:

代码语言:javascript复制
In [138]: from random import randint
          l = [randint(0, 10) for i in range(1000)]  # ①
          len(l)  # ②
Out[138]: 1000

In [139]: l[:20]
Out[139]: [10, 9, 2, 4, 5, 1, 7, 4, 6, 10, 9, 5, 4, 6, 10, 3, 4, 7, 0, 5]

In [140]: s = set(l)
          s
Out[140]: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

1,000 个 0 到 10 之间的随机整数。

l 中的元素数量。

结论

基本的Python解释器已经提供了丰富灵活的数据结构。从金融的角度来看,以下可以被认为是最重要的:

基本数据类型

在金融中,intfloatstring类提供了原子数据类型。

标准数据结构

tuplelistdictset类在金融领域有许多应用领域,其中list通常是最灵活的通用工作马。

进一步资源

本章重点讨论可能对金融算法和应用特别重要的问题。但是,它只能代表探索Python中数据结构和数据建模的起点。有许多宝贵的资源可供进一步深入了解。

书籍形式的良好参考资料包括:

  • Goodrich, Michael 等(2013):Python 数据结构与算法. John Wiley & Sons, Hoboken, NJ.
  • Harrison, Matt (2017): Python 3 图解指南. Treading on Python Series.
  • Ramalho, Luciano (2016): 流畅的 Python. O’Reilly, Beijing et al.

¹ Cython库将静态类型和编译功能引入Python,与C中的相似。实际上,CythonPythonC的混合语言。

² 在这里和后续讨论中,诸如floatfloat 对象等术语可互换使用,承认每个float也是一个对象。对于其他对象类型也是如此。

³ 参考http://en.wikipedia.org/wiki/Double-precision_floating-point_format

⁴ 在这里不可能详细介绍,但互联网上有大量关于正则表达式的信息,特别是针对Python。关于这个主题的介绍,请参阅 Fitzgerald, Michael (2012): 正则表达式入门. O’Reilly, Sebastopol, CA. ‘Angela Merkel’) (‘Country’, ‘Germany’) (‘Profession’, ‘Chancelor’) (‘Age’, 64)

In [130]: for value in d.values(): print(type(value))

代码语言:javascript复制
      <class 'str'>
      <class 'str'>
      <class 'str'>
      <class 'int'>
代码语言:javascript复制
表 3-3 提供了 `dict` 对象的选定操作和方法的摘要。

表 3-3. `dict` 对象的选定操作和方法

| 方法 | 参数 | 返回/结果 |
| --- | --- | --- |
| `d[k]` | `[k]` | `d` 中具有键 `k` 的项目 |
| `d[k] = x` | `[k]` | 将项目键 `k` 设置为 `x` |
| `del d[k]` | `[k]` | 删除具有键 `k` 的项目 |
| `clear` | `()` | 删除所有项目 |
| `copy` | `()` | 复制一个副本 |
| `has_key` | `(k)` | 如果 `k` 是键,则为 `True` |
| `items` | `()` | 迭代器遍历所有项目 |
| `keys` | `()` | 迭代器遍历所有键 |
| `values` | `()` | 迭代器遍历所有值 |
| `popitem` | `(k)` | 返回并删除具有键 `k` 的项目 |
| `update` | `([e])` | 用 `e` 中的项目更新项目 |

## 集合

我们将考虑的最后一个数据结构是 `set` 对象。尽管集合论是数学和金融理论的基石,但对于 `set` 对象的实际应用并不太多。这些对象是其他对象的无序集合,每个元素只包含一次:

```py
In [131]: s = set(['u', 'd', 'ud', 'du', 'd', 'du'])
          s
Out[131]: {'d', 'du', 'u', 'ud'}

In [132]: t = set(['d', 'dd', 'uu', 'u'])

使用 set 对象,您可以像在数学集合论中一样实现操作。例如,您可以生成并集、交集和差异:

代码语言:javascript复制
In [133]: s.union(t)  # ①
Out[133]: {'d', 'dd', 'du', 'u', 'ud', 'uu'}

In [134]: s.intersection(t)  # ②
Out[134]: {'d', 'u'}

In [135]: s.difference(t)  # ③
Out[135]: {'du', 'ud'}

In [136]: t.difference(s)  # ④
Out[136]: {'dd', 'uu'}

In [137]: s.symmetric_difference(t)  # ⑤
Out[137]: {'dd', 'du', 'ud', 'uu'}

st 的全部。

st 中都有。

s 中但不在 t 中。

t 中但不在 s 中。

在其中一个但不是两者都。

set对象的一个应用是从list对象中消除重复项。例如:

代码语言:javascript复制
In [138]: from random import randint
          l = [randint(0, 10) for i in range(1000)]  # ①
          len(l)  # ②
Out[138]: 1000

In [139]: l[:20]
Out[139]: [10, 9, 2, 4, 5, 1, 7, 4, 6, 10, 9, 5, 4, 6, 10, 3, 4, 7, 0, 5]

In [140]: s = set(l)
          s
Out[140]: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

1,000 个 0 到 10 之间的随机整数。

l 中的元素数量。

结论

基本的Python解释器已经提供了丰富灵活的数据结构。从金融的角度来看,以下可以被认为是最重要的:

基本数据类型

在金融中,intfloatstring类提供了原子数据类型。

标准数据结构

tuplelistdictset类在金融领域有许多应用领域,其中list通常是最灵活的通用工作马。

进一步资源

本章重点讨论可能对金融算法和应用特别重要的问题。但是,它只能代表探索Python中数据结构和数据建模的起点。有许多宝贵的资源可供进一步深入了解。

书籍形式的良好参考资料包括:

  • Goodrich, Michael 等(2013):Python 数据结构与算法. John Wiley & Sons, Hoboken, NJ.
  • Harrison, Matt (2017): Python 3 图解指南. Treading on Python Series.
  • Ramalho, Luciano (2016): 流畅的 Python. O’Reilly, Beijing et al.

¹ Cython库将静态类型和编译功能引入Python,与C中的相似。实际上,CythonPythonC的混合语言。

² 在这里和后续讨论中,诸如floatfloat 对象等术语可互换使用,承认每个float也是一个对象。对于其他对象类型也是如此。

³ 参考http://en.wikipedia.org/wiki/Double-precision_floating-point_format

⁴ 在这里不可能详细介绍,但互联网上有大量关于正则表达式的信息,特别是针对Python。关于这个主题的介绍,请参阅 Fitzgerald, Michael (2012): 正则表达式入门. O’Reilly, Sebastopol, CA.

0 人点赞