2.3 量化平台常用语言—Python
在量化平台中,既有用户比较熟悉的Python语言,也有现在用途不太广泛的Matlab语言,当然,无论哪种语言,只有用户能很好地应用才行。出于对系统维护升级的考虑,建议选择Python语言进行开发。
2.3.1 Python简介
Python语言的创始人吉多·范罗苏姆(Guido van Rossum)是一名荷兰计算机程序员。1989年范罗苏姆在阿姆斯特丹,为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,来作为ABC语言的继承。Python语言的名字被译为大蟒蛇是因为范罗苏姆很喜欢看一个英国电视节目“Monty Python”(飞行马戏团),从20世纪90年代初Python语言诞生至今,它已被逐渐广泛应用于系统管理任务的处理和Web编程中。
Python是由ABC、Modula-3、C、C++、Algol-68、SmallTalk、UNIX Shell及其他的脚本语言发展而来的。
Python是一个高层次的结合了解释性、编译性、互动性的面向对象的脚本语言,可以应用于Web开发、Internet开发、科学计算和统计、教育、桌面界面开发、软件开发、后端开发等领域。而且在开发过程中没有编译环节。类似于PHP和Perl语言。可以在一个Python提示符(>>>)后直接执行代码。相比其他编程语言经常使用英文关键字和一些特殊的标点符号而言,它更具有特色语法结构,是一种交互式语言。它支持面向对象的风格或将代码封装在对象的编程技术。
Python已经成为非常受欢迎的程序设计语言。从2004年以后,Python的使用率呈线性增长。在2019年3月TIOBE的编程语言排行榜中,Java继续保持第一名,而Python超越C++排在第三名,如图2-5所示。
图2-5
2.3.2 量化基础语法及数据结构
Python的基础语法包括如下几种。
1.Python标识符
在Python里,标识符由字母、数字、下画线组成,所有标识符可以包括字母、数字及下画线(_),但不能以数字开头。Python中的标识符区分大小写。
以下画线开头的标识符是有特殊意义的。
· 以单下画线开头(_foo),代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用from xxx import * 来导入。
· 以双下画线开头(__foo),代表类的私有成员。
·以双下画线开头和结尾(__foo__),代表Python里特殊方法专用的标识,如__init__()代表类的构造函数。
Python有5种标准的数据类型:Numbers(数字)、String(字符串)、List(列表)、Tuple(元组)和Dictionary(字典)。
Python支持4种不同的数字类型:int(符号整型)、long(长整型,也可以代表八进制数和十六进制数)、float(浮点型)和complex(复数)。
Python的字符串列表有两种取值顺序:从左到右索引默认是从0开始的,最大范围是字符串长度减1、从右到左索引默认是从-1开始的,最大范围是字符串开头。
List(列表)是Python中使用最频繁的数据类型。
列表可以完成大多数集合类的数据结构实现。它支持字符、数字、字符串,甚至可以包含列表(即嵌套)。列表用“[]”标识,是Python最通用的复合数据类型。列表中值的切割也可以用到变量 [头下标:尾下标],从而可以截取相应的列表,从左到右索引默认从0开始,从右到左索引默认从-1开始,下标可以为空,表示取到头或尾。
加号(+)是列表连接运算符,星号(*)表示重复操作,如表2-1所示。
表2-1
Python包含的方法及其描述如表2-2所示。
表2-2
元组是另一个数据类型,类似于列表。元组用“()”标识。内部元素用逗号隔开。但是元组不能进行二次赋值,相当于只读列表。
Python的元组与列表类似,不同之处在于:元组的元素不能修改,元组使用小括号,列表使用方括号。元组内置函数及其描述如表2-3所示。
表2-3
字典是另一种可变容器模型,且可存储任意类型的对象。字典用“{}”标识。字典是除列表外,Python中最灵活的内置数据结构类型。
列表是有序的对象集合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键存取的,而不是通过偏移存取的。
字典由索引(key)和它对应的值value组成。字典的每个键值(key/value)对用冒号(:)分隔,整个字典包括在大括号({})中。
Python字典包含的内置函数及其描述如表2-4所示。
表2-4
Python字典包含的内置方法及其描述如表2-5所示。
表2-5
2.Python数据类型转换
有时候,我们需要对数据内置的类型进行转换,进行数据类型的转换,只需要将数据类型作为函数名即可。
表2-6中列举的内置函数可以执行数据类型之间的转换。这些函数将返回一个新的对象,表示转换的值。
表2-6
3.Python运算符
Python中的运算符包括算术运算符、比较(关系)运算符、赋值运算符、逻辑运算符、位运算符、成员运算符、身份运算符、运算符优先级。
· Python算术运算符如表2-7所示(假设变量a为10,变量b为20)。
表2-7
· Python比较运算符如表2-8所示(假设变量a为10,变量b为20)。
表2-8
· Python赋值运算符如表2-9所示。
表2-9
· Python位运算符如表2-10所示。
表2-10
表2-10中变量a为60, b为13,二进制格式如下:
a = 00111100 b = 00001101 —————— a&b = 00001100 a|b = 00111101 a^b = 00110001 ~a = 11000011
· Python成员运算符如表2-11所示。
除了以上的一些运算符,Python还支持成员运算符,测试实例中包括了一系列的成员,包括字符串、列表或元组。
表2-11
4.Python循环语句
· Python提供了for循环和while循环(在Python中没有do…while循环),如表2-12所示。
表2-12
· 循环控制语句可以更改语句执行的顺序。Python支持的循环控制语句如表2-13所示。
表2-13
5.Python函数
· Python数学函数及其描述如表2-14所示。
表2-14
· 随机数可以用于数学、游戏、安全等领域中,还经常被嵌入到算法中,用以提高算法效率,并提高程序的安全性。Python随机函数及其描述如表2-15所示。随机函数用于产生随机数。
表2-15
· Python三角函数及其描述如表2-16所示。
表2-16
· 匿名函数lambda。
Python使用lambda来创建匿名函数。lambda只是一个表达式,函数体比def简单很多。lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda函数拥有自己的命名空间,并且不能访问自有参数列表之外或全局命名空间里的参数。虽然lambda函数看起来只能写一行,却不等同于C语言或C++语言的内联函数,内联函数的目的是调用小函数时不占用栈内存从而增加运行效率。
示例代码如下:
sum = lambda arg1, arg2: arg1 + arg2; print "相加后的值为 : ", sum( 10, 20 ) //输出30
6.Python数学常量
· Python数学常量如表2-17所示。
表2-17
7.Python字符串
· 当需要在字符中使用特殊字符时,Python使用反斜杠(\)来表示,称为转义字符,如表2-18所示。
表2-18
· Python字符串运算符如表2-19所示。
其中,实例变量a值为字符串 "Hello",变量b值为"Python"。
表2-19
· Python字符串格式化。
Python支持格式化字符串的输出。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入一个有字符串格式符 %s的字符串中。
在Python中,字符串格式化使用的语法与C语言中sprintf()函数一样。
示例代码如下:
#! /usr/bin/Python print "My name is %s and weight is %d kg! " % ('BROWN',33)
以上示例输出结果如下:
My name is BROWN and weight is 33 kg!
Python字符串格式化符号及其描述如表2-20所示。
表2-20
8.Python import语句
· from...import语句。
Python的from语句可以从模块中导入一个指定的部分到当前命名空间中。语句如下:
from modname import name1[, name2[, ... nameN]]
例如,要导入模块fib的fibonacci函数,可以使用如下语句:
from fib import fibonacci
这个声明不会把整个fib模块导入当前的命名空间中,它只会将fib里的fibonacci单个引入到执行这个声明的模块的全局符号表中。
· from...import*语句。
把一个模块的所有内容全都导入当前的命名空间也是可行的,只需使用如下语句:
from modname import*
这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不可以被过多地使用。
例如,我们想一次性引入math模块中所有的东西,语句如下:
from math import*
9.Python文件操作
1)打开和关闭文件
Python提供了必要的函数和方法对文件进行基本的操作。可以使用file对象对大部分的文件进行操作。
· open()函数。
用户必须先用Python内置的open()函数打开一个文件,创建一个与file对象相关的方法才可以调用该函数进行读/写操作。
语句如下:
file object = open(file_name [, access_mode][, buffering])
各个参数的说明如下。
➢ file_name:包含了一个要访问的文件名称的字符串值。
➢ access_mode:决定了打开文件的模式,包括只读、写入、追加等。所有可取值如表2-21所示。这个参数是非强制的,默认文件访问模式为只读(r)。
➢ buffering:如果buffering的值被设为0,就不会有寄存。如果buffering的值被设为1,访问文件时会寄存行。如果buffering的值被设为大于1的整数,表明这就是寄存区的缓冲大小。如果buffering的值被设为负值,寄存区的缓冲大小则为系统默认。
表2-21
file对象的属性:当一个文件被打开后,将会有一个file对象,并且可以得到有关该文件的各种信息。与file对象相关的所有属性列表如表2-22所示。
表2-22
· close()方法:file对象的close()方法可以刷新缓冲区里任何还没有写入的信息,并关闭该文件,这之后便不能再进行写入。
当一个文件对象的引用被重新指定给另一个文件时,Python会关闭之前的文件。用close()方法关闭文件是一个很好的习惯。
语句如下:
fileObject.close();
· write()方法:将任何字符串写入一个打开的文件中。需要重点注意的是,Python字符串可以是二进制数据,而不仅仅是文字。
write()方法不会在字符串的结尾添加换行符('\n')。
语句如下:
fileObject.write(string);
· read()方法:从一个打开的文件中读取一个字符串。需要重点注意的是,Python字符串可以是二进制数据,而不仅仅是文字。
语句如下:
fileObject.read([count]);
· tell()方法:tell()方法指定文件内的当前位置;换句话说,下一次的读/写会发生在文件开头的字节之后。
· seek(offset[, from])方法:改变当前文件的位置。offset变量表示要移动的字节数。from变量指定开始移动字节的参考位置。
如果from被设为0,这意味着将文件的开头作为移动字节的参考位置;如果from被设为1,则使用当前的位置作为参考位置;如果from被设为2,那么将该文件的末尾作为参考位置。
2)重命名和删除文件
Python的OS模块提供了执行文件处理操作的方法,比如重命名和删除文件。要使用这个模块,必须先导入它,然后才可以调用相关的功能。
· remove()方法:可以使用remove()方法删除文件,需要将要删除的文件名作为参数。
Python里的所有文件都包含在各个不同的目录下,即使这样Python也能轻松处理。OS模块提供了许多创建、删除和更改目录的方法。
· mkdir()方法:可以使用OS模块的mkdir()方法在当前目录下创建新的目录。用户需要提供一个包含了要创建的目录名称的参数。
语句如下:
os.mkdir("newdir")
· chdir()方法:可以使用chdir()方法来更改当前的目录。chdir()方法需要的一个参数是:想设成当前目录的目录名称。
语句如下:
os.chdir("newdir")
· rmdir()方法:可以使用rmdir()方法删除目录,目录名称以参数传递。
在删除某个目录之前,这个目录中的所有内容应该先被清除。
语句如下:
os.rmdir('dirname')
file对象方法和OS对象方法可以对Windows和UNIX操作系统上的文件及目录进行广泛且实用的处理及操控。
· file对象方法:file对象提供了操作文件的一系列方法。
· OS对象方法:OS对象提供了处理文件及目录的一系列方法。
10.Python file方法
file对象使用open()函数来创建,file对象常用的函数及其描述如表2-23所示。
表2-23
使用Python语言需要注意以下问题。
· 大小写敏感:即字母是区分大小写的。所以如果把前面例子代码中的若干个字母从小写变成大写,系统将会报错。
· 要用英文字符:冒号、逗号、分号、括号、引号等各种符号必须用英文,使用中文字符将会报错。
· 注释:为了让人们更好地理解代码的含义,通常都会在代码中写入注释。注释是给人看的,计算机会忽略(需要注意的是,空行也会被忽略),所以用中文记录思路也是可以的。笔者强烈建议养成写注释的好习惯。注释的写法为“#”,表示会把所在行的其后所有内容设定为注释。
举例如下。
解决Python中不能输入汉字的问题,我们在Python的IDE中有时候会输入中文,Python对中文不太友好。在一般情况下,在代码前加入“# coding: utf-8”就可以了。示例代码如下:
# coding: utf-8 reload(sys) sys.setdefaultencoding("utf-8")
11.Python数据类型
1)列表(List)
在Python中没有数组的概念,与数组最接近的概念就是列表和元组。列表是用来存储一连串元素的容器,用“[]”来表示。例如,可以用序列表示数据库中一个人的信息,第一个元素是姓名,第二个元素是年龄,根据上述内容定义一个列表(列表元素通过逗号分隔,写在方括号中),示例代码如下:
# 定义一个列表,列表是一个可变序列 edward = ['Edward Gumby',42] # 打印列表 edward
以上示例输出结果如下:
['Edward Gumby',42]
2)元组(Tuple)
在Python中与数组类似的还有元组,元组中的元素可以进行索引计算。列表和元组的区别在于:列表中元素的值可以修改,而元组中元素的值不可以修改,只可以读取;另外,列表的符号是“[]”,而元组的符号是“()”。示例代码如下:
# 定义一个元组,元组是一个不可变序列 tom = ('Tom Teddy',37) # 打印元组 tom
以上示例输出结果如下:
('Tom Teddy',37)
3)字典(Dictionary)
在Python中,字典也叫作关联数组,可以理解为列表的升级版,用大括号括起来,格式为{key1: value1, key2: value2, …, keyn: valuen},即字典的每个键值(key/value)对用冒号分隔,每个对之间用逗号分隔,整个字典包括在大括号中。示例代码如下:
# 定义一个字典,字典是一个由键值对构成的序列 age = {’张三’:27, ’李四’:29} # 打印字典 print(age)
以上示例输出结果如下:
{’张三’:27, ’李四’:29}
4)字符串或串(String)
字符串或串是由数字、字母、下画线组成的一串字符。一般记为s=“a1a2…an”(n≥0)。它在编程语言中表示文本的数据类型。在程序设计中,字符串为符号或数值的一个连续序列,如符号串(一串字符)或二进制数字串(一串二进制数字)。示例代码如下:
# Python数据类型:字符串、整数、浮点数 a = ’中国’ b = 25 c = 3.14
5)软件包(numPy)
软件包是Python的一个扩展程序库,支持大量的维度数组与矩阵运算,此外它也针对数组运算提供了大量的数学函数库。示例代码如下:
# 软件包numpy例子 # 导入库 import numpy as np # 创建一个3*5的多维数组(数据类型) a = np.arange(15).reshape(3, 5) a
输出结果如下:
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
再举一个例子,代码如下:
# 软件包pandas例子 # 导入库 import numpy as np import pandas as pd df = pd.DataFrame([’张三’, ’李四’, ’王五’], columns = {’姓名’}, index = {1,2,3}) df
输出结果如图2-6所示。
图2-6
2.3.3 量化中函数的定义及使用方法
Python中的函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。它能提高应用的模块性能和代码的重复利用率。支持递归、默认参数值、可变参数,但不支持函数重载。Python提供了许多内建函数,如print()。但也可以自己创建函数,称为用户自定义函数。
定义一个想要实现某种功能的函数,要遵循以下规则。
函数代码块以def关键词开头,后接函数标识符名称和小括号。任何传入参数和自变量必须放在小括号中间。小括号之间可以定义参数。函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。函数内容以冒号起始,并且缩进。例如,return [表达式]结束函数,选择性地返回一个值给调用方。不带表达式的return返回None。
1.函数语法
Python定义函数使用def关键字,一般格式如下:
def函数名 (参数列表): 函数体
在默认情况下,参数值和参数名称是按函数声明中定义的顺序进行匹配的。
例如,使用函数来输出"Hello BROWN! ",示例代码如下:
def hell(): print("Hello BROWN! ") hell() x = itertools.compress(range(5), (True, False, True, True, False)) print(list(x)) Hello BROWN!
举一个更复杂的例子,在函数中带上参数变量,示例代码如下:
# 计算面积函数 def area(width, height): return width * height def print_welcome(name): print("Welcome", name) print_welcome("Runoob") w = 7 h = 8 print("width = ", "height ", h, "area ", area(w, h))
以上示例输出结果如下:
('Welcome', 'Runoob') ('width = ', 'height ', 8, 'area ', 56)
2.函数调用
定义一个函数:赋予函数一个名称,指定函数里包含的参数和代码块结构。
这个函数的基本结构完成以后,可以通过另一个函数调用执行,也可以直接使用Python命令提示符执行。
调用printme() 函数的示例代码如下:
# 定义函数 def printme (str): # 打印任何传入的字符串 print (str) return # 调用函数 printme("调用王东泽函数!") printme("再次调用王东泽函数")
以上示例输出结果如下:
调用王东泽函数! 再次调用王东泽函数
sort()函数用于对原列表进行排序,如果指定参数,则使用比较函数指定的函数。
sort()语句如下:
list.sort(key = None, reverse = False)
参数说明如下。
· key:主要是用来进行比较的元素,只有一个参数,具体函数的参数取自可迭代对象,指定可迭代对象中的一个元素来进行排序。
· reverse:排序规则,reverse = True表示降序,reverse = False表示升序(默认)。sort()函数没有返回值,但是会对列表的对象进行排序。
以下示例展示了sort() 函数的使用方法:
aList = [’量化网’, ’优矿’, ’谷歌’, ’百度’] aList.sort() print ( "List : ", aList)
以上示例输出结果如下:
aList = [’量化网’, ’优矿’, ’谷歌’, ’百度’]
以下示例按降序输出列表:
# 列表 vowels = ['d', 'r', 'w', 'c', 'q'] # 降序 vowels.sort(reverse=True) # 输出结果 print(’降序输出:', vowels)
以上示例输出结果如下:
降序输出: ['w', 'r', 'q', 'd', 'c']
以下示例演示了通过指定列表中的元素排序来输出列表:
# 获取列表第二个元素 def takeSecond(elem): return elem[1] # 列表 random = [(7,6), (3,5), (4,7), (2,1)] # 指定第二个元素排序 random.sort(key = takeSecond) # 输出类别 print (’排序列表: ', random)
以上示例输出结果如下:
排序列表: [(2, 1), (3, 5), (7, 6), (4, 7)]
2.3.4 面向对象编程OOP的定义及使用方法
Python从设计之初就是一门面向对象的语言,所以很容易在Python中创建一个类和对象。
对象:以类为单位来管理所有代码。对象包括两个数据成员(类变量和实例变量)和方法。并且加入了类机制,可以包含任意数量和类型的数据。
Python中的类是对象的“抽象部分”,提供了面向对象编程的所有基本功能:类的继承机制允许有多个基类,派生类可覆盖基类方法,方法中可调用基类的同名方法。
语法格式如下:
class ClassName: <statement-1> . . . <statement-N>
将类实例化可以使用其属性,即创建一个类之后,可以通过类名访问其属性。
类对象支持属性引用和实例化两种操作方法。其属性引用使用的语法为:obj.name。类对象创建后,所有的命名都是有效属性名。类定义代码如下:
class MyClass: """一个简单的类实例""" i = 3.14159265 def f(self): return 'hello BROWN' # 实例化类 x = MyClass() # 访问类的属性和方法 print("MyClass类的属性i为: ", x.i) print("MyClass类的方法f输出为: ", x.f())
以上代码创建了一个新的类实例并将该对象赋给局部变量x, x是一个空的对象。
执行以上代码后,输出的结果如下:
MyClass类的属性i为: 3.14159265 MyClass类的方法f输出为: hello BROWN
类有一个名为 __init__()的特殊构造方法,该方法在类实例化时可自动调用,例如:
def __init__(self): self.data = []
类定义了__init__()方法,类的实例化操作会自动调用__init__()方法。例如,实例化类MyClass,对应的__init__()方法就会被调用,例如:
x = MyClass()
__init__() 方法也可以设置参数,参数可以通过 __init__()传递到类。例如:
class Complex: def __init__(self, realpart, imagpart): self.r = realpart self.i = imagpart x = Complex(7.8, -2.6) print(x.r, x.i)
执行以上代码后,输出的结果如下:
(7.8, -2.6)
self代表类的实例而不是类。类的方法必须有一个额外的第一个参数名称,按照惯例它的名称是self。这是与普通函数的唯一区别。例如:
class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt()
执行以上代码后,输出的结果如下:
<__main__.Test object at 0x000001C9FBB43F60> <class'__main__.Test'>
在类的内部,如果使用def关键字来定义一个方法就必须包含参数self,且为第一个参数,self代表的是类的实例。这一点与一般函数的定义不同。
# 类定义 class people: # 定义基本属性 name = '' age = 0 # 定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 # 定义构造方法 def __init__(self, n, a, w): self.name = n self.age = a self.__weight = w def speak(self): print("%s说:王东泽 %d岁。" %(self.name, self.age)) # 实例化类 p = people('runoob',31,30) p.speak()
执行以上代码后,输出的结果如下:
runoob说:王东泽 31岁。
· 单继承:Python支持类的继承,否则类就失去了意义。单继承的类定义如下:
class DerivedClassName(BaseClassName1): <statement-1> . . . <statement-N>
注意小括号中基类的顺序,如果基类中有相同的方法名,而子类使用时未指定,Python将从左到右搜索,即方法在子类中未找到时,可以从左到右查找基类中是否包含方法。
BaseClassName(示例中的基类名)必须与派生类定义在一个作用域内。基类定义在另一个模块中时表达式非常有用,代码如下:
class DerivedClassName(modname.BaseClassName):
示例代码如下:
# 类定义 class people: # 定义基本属性 name = '' age = 0 # 定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 # 定义构造方法 def __init__(self, n, a, w): self.name = n self.age = a self.__weight = w def speak(self): print("%s说:我 %d岁。" %(self.name, self.age)) # 单继承 class student(people): grade = '' def __init__(self, n, a, w, g): # 调用父类的构造方法 people.__init__(self, n, a, w) self.grade = g # 覆写父类的方法 def speak(self): print("%s说:王东泽 %d岁了,王东泽在读 %d年级博士后"%(self.name, self. age, self.grade)) s = student('ken',31,60,2) s.speak()
执行以上代码后,输出的结果如下:
ken说:王东泽 31岁了,王东泽在读 2年级博士后
· 多继承:Python同样支持多继承形式。多继承的类定义如下:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意小括号中父类的顺序,若父类中有相同的方法名,而在子类使用时未指定,Python将从左到右搜索,即方法在子类中未找到时,从左到右查找父类中是否包含方法。从单继承到多继承的示例代码如下:
# 类定义 class people: # 定义基本属性 name = '' age = 0 # 定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 # 定义构造方法 def __init__(self, n, a, w): self.name = n self.age = a self.__weight = w def speak(self): print("%s说:我 %d岁。" %(self.name, self.age)) # 单继承 class student(people): grade = '' def __init__(self, n, a, w, g): # 调用父类的构造方法 people.__init__(self, n, a, w) self.grade = g # 覆写父类的方法 def speak(self): print("%s说:我 %d岁了,我在读 %d年级"%(self.name, self.age, self.grade)) # 定义另一个类 class speaker(): topic = '' name = '' def __init__(self, n, t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一名作家,我的写作主题是 %s"%(self.name, self.topic)) # 多继承 class sample(speaker, student): a ='' def __init__(self, n, a, w, g, t): student.__init__(self, n, a, w, g) speaker.__init__(self, n, t) test = sample("王东泽",25,80,4, "Python") test.speak() # 与方法名相同,默认调用的是在括号中排名靠前的父类的方法
执行以上程序后,输出的结果如下:
我叫 王东泽,我是一名作家,我的写作主题是Python
2.3.5 itertools的使用方法
itertools迭代器(生成器)是Python中一种很常用也很好用的数据结构,与列表相比,迭代器最大的优势就是延迟计算、按需使用,从而提高开发者的体验度和运行效率。所以在Python3中map、filter等操作返回的不再是列表而是迭代器。经常用到的迭代器是range,但是通过iter()函数把列表对象转化为迭代器对象会多此一举,所以使用itertools更合适一些。
itertools中的函数大多会返回各种迭代器对象,其中很多函数的作用需要我们写很多代码才能发挥,在运行效率上很低,因为其是系统库。下面列举itertools的使用方法。
itertools.accumulate表示累加。例如:
import itertools x = itertools.accumulate(range(10)) print(list(x))
执行以上程序后,输出的结果如下:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
itertools.chain表示连接多个列表或迭代器。例如:
x = itertools.chain(range(3), range(4), [3,2,1]) print(list(x))
执行以上程序后,输出的结果如下:
[0, 1, 2, 0, 1, 2, 3, 3, 2, 1]
itertools.combinations_with_replacement表示允许重复元素的组合。例如:
x = itertools.combinations_with_replacement('ABC', 2) print(list(x))
执行以上程序后,输出的结果如下:
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
itertools.compress表示按照真值表筛选元素。例如:
x = itertools.compress(range(5), (True, False, True, True, False)) print(list(x))
执行以上程序后,输出的结果如下:
[0, 2, 3]
itertools.count是一个计数器,可以指定起始位置和步长。例如:
x = itertools.count(start=20, step=-1) print(list(itertools.islice(x, 0, 10, 1)))
执行以上程序后,输出的结果如下:
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11]
itertools.dropwhile表示按照真值函数,丢弃列表和迭代器前面的元素。例如:
x = itertools.dropwhile(lambda e: e < 5, range(10)) print(list(x))
执行以上程序后,输出的结果如下:
[5, 6, 7, 8, 9]
itertools.filterfalse表示保留对应真值为False的元素。例如:
x = itertools.filterfalse(lambda e: e < 5, (1, 5, 3, 6, 9, 4)) print(list(x))
执行以上程序后,输出的结果如下:
[5, 6, 9]
itertools.groupby表示按照分组函数的值对元素进行分组。例如:
x = itertools.groupby(range(10), lambda x: x < 5 or x > 8) for condition, numbers in x: print(condition, list(numbers))
执行以上程序后,输出的结果如下:
(True, [0, 1, 2, 3, 4]) (False, [5, 6, 7, 8]) (True, [9])
itertools.islice表示对迭代器进行切片操作。例如:
x = itertools.islice(range(10), 0, 9, 2) print(list(x))
执行以上程序后,输出的结果如下:
[0, 2, 4, 6, 8]
itertools.permutations返回可迭代对象的所有数学全排列方式。例如:
x = itertools.permutations(range(4), 3) print(list(x))
执行以上程序后,输出的结果如下:
[(0, 1, 2), (0, 1, 3), (0, 2, 1), (0, 2, 3), (0, 3, 1), (0, 3, 2), (1, 0, 2), (1, 0, 3), (1, 2, 0), (1, 2, 3), (1, 3, 0), (1, 3, 2), (2, 0, 1), (2, 0, 3), (2, 1, 0), (2, 1, 3), (2, 3, 0), (2, 3, 1), (3, 0, 1), (3, 0, 2), (3, 1, 0), (3, 1, 2), (3, 2, 0), (3, 2, 1)]
itertools.repeat表示简单地生成一个拥有指定数目元素的迭代器。例如:
x = itertools.repeat(0, 5) print(list(x))
执行以上程序后,输出的结果如下:
[0, 0, 0, 0, 0]
itertools.starmap与map类似。例如:
x = itertools.starmap(str.islower, 'aBCDefGhI') print(list(x))
执行以上程序后,输出的结果如下:
[True, False, False, False, True, True, False, True, False]
itertools.takewhile与dropwhile相反,保留元素直到真值函数值为假。例如:
x = itertools.takewhile(lambda e: e < 5, range(10)) print(list(x))
执行以上程序后,输出的结果如下:
[0, 1, 2, 3, 4]
itertools.zip_longest与zip类似,但是需要以较长的列表和迭代器的长度为准。例如:
x = itertools.zip_longest(range(3), range(5)) y = zip(range(3), range(5)) print(list(x))
执行以上程序后,输出的结果如下:
[(0, 0), (1, 1), (2, 2), (None, 3), (None, 4)] [(0, 0), (1, 1), (2, 2)]
2.4 量化投资工具—Matplotlib
Matplotlib是Python 2D-绘图领域使用非常广泛的套件。它能让使用者轻松地将数据图形化,并且提供多样化的输出格式。下面来介绍Matplotlib的常见用法。
读者要想使用Python绘制K线图最好在Anaconda网站下载安装包。安装步骤如下。
(1)单击“Windows”然后单击“64-Bit Graphical Installer(614.3MB)”,如图2-7所示。
图2-7
(2)点击相应链接进行下载,并选择保存的文件夹,下载完成后,即可在文件夹中找到下载的安装包,如图2-8所示。
图2-8
(3)双击安装包文件,在弹出的安装对话框中单击“Next”按钮,然后单击“I Agree”按钮,如图2-9所示。
图2-9
(4)根据提示继续单击相应按钮进行安装。直到出现如图2-10右图所示的对话框,然后依次单击“Install Microsoft VSCode”→“Cancel”按钮,完成安装。
图2-10
Matplotlib仅需要几行代码,便可以生成绘图、直方图、功率谱、条形图、错误图、散点图等。
2.4.1 Matplotlib基础知识
Matplotlib的基础知识如下。
(1)Matplotlib中基本图表的元素包括x轴和y轴、水平和垂直的轴线。
x轴和y轴使用刻度对坐标轴进行分隔,包括最小刻度和最大刻度;x轴和y轴刻度标签表示特定坐标轴的值、绘图区域等。
(2)hold属性默认为True,允许在一幅图中绘制多条曲线。
(3)使用grid方法为图添加网格线,方法为设置grid参数(参数与plot()函数相同), lw代表linewidth(线的粗细), Alpha表示线的明暗程度。
(4)axis方法如果没有任何参数,则返回当前坐标轴的上下限。
(5)除了plt.axis方法,还可以通过xlim、ylim方法设置坐标轴范围。
(6)legend方法如下。
· 初级绘制。
这一节中,我们将从简到繁:先尝试用默认配置在同一张图上绘制正弦函数和余弦函数图像,然后逐步美化它。
首先取得正弦函数和余弦函数的值,示例代码如下:
from pylab import * X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X)
X是一个numpy数组,包含了-π到+π之间的256个值。C和S分别是这256个值对应的余弦函数和正弦函数值组成的numpy数组。
· 使用默认配置。
Matplotlib的默认配置允许用户进行自定义。可以调整大多数的默认配置:图片大小和分辨率(dpi)、线条宽度、颜色、风格、坐标轴、网格的属性、文字和字体属性等。不过,Matplotlib的默认配置在大多数情况下已经做得足够好了,可能只有在特殊的情况下才会想要更改这些默认配置。示例代码如下:
import numpy as np import matplotlib.pyplot as plt X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X) plot(X, C) plot(X, S) show()
执行以上程序后,输出的结果如图2-11所示(由于本书是黑白印刷,涉及的颜色无法在书中呈现,请读者结合软件界面进行辨识)。
图2-11
在下面的代码中,我们展现了Matplotlib的默认配置并辅以注释说明,这部分配置包含了有关绘图样式的所有配置。代码中的配置与默认配置完全相同,可以在交互模式中修改其中的值来观察效果:
# 导入Matplotlib的所有内容 from pylab import * # 创建一个 9*7 点阵图,并设置分辨率为80像素 figure(figsize=(9,7), dpi=80) # 创建一个新的 1*1 的子图,接下来的图样绘制在其中的第1 块 subplot(1,1,1) X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X) # 绘制余弦曲线,使用蓝色的、连续的、宽度为1 像素的线条 plot(X, C, color="blue", linewidth=1.0, linestyle="-") # 绘制正弦曲线,使用绿色的、连续的、宽度为1 像素的线条 plot(X, S, color="green", linewidth=1.0, linestyle="-") # 设置横轴的上下限 xlim(-3.0,3.0) # 设置横轴记号 xticks(np.linspace(-3,3,9, endpoint=True)) # 设置纵轴的上下限 ylim(-1.0,1.0) # 设置纵轴记号 yticks(np.linspace(-2,2,5, endpoint=True)) # 以分辨率 72像素来保存图片 # savefig("exercice_2.png", dpi=72) # 在屏幕上显示 show()
执行以上程序后,输出的结果如图2-12所示(由于本书是黑白印刷,涉及的颜色无法在书中呈现,请读者结合软件界面进行辨识)。
图2-12
现在来改变线条的颜色和粗细。我们首先以蓝色和红色分别表示余弦函数和正弦函数,然后将线条变粗一点,接下来,我们以水平方向拉伸整个图。示例代码如下:
figure(figsize=(10,6), dpi=70) plot(X, C, color="blue", linewidth=2.5, linestyle="-") plot(X, S, color="red", linewidth=2.5, linestyle="-")
执行以上程序后,输出的结果如图2-13所示(由于本书是黑白印刷,涉及的颜色无法在书中呈现,请读者结合软件界面进行辨识)。
图2-13
2.4.2 Matplotlib可视化工具基础
可视化工具可以高效获取信息。人眼是一个高带宽的巨量信号输入并行处理器,具有超强的模式识别能力,对可视符号的感知速度比对数字或文本快多个数量级,而可视化就是迎合了人眼的这种特点,才使得获取信息的难度大大降低。这才会有“一图胜千言”的说法,一堆数据很难快速看明白,但生成图形就会一目了然。比如用图来表达国际象棋与围棋的复杂度,如图2-14所示。
图2-14
可视化工具可以辅助人们在人脑之外保存待处理信息,从而补充人脑有限的记忆内存,提高信息认知的效率。虽然人们能够有意识地集中注意力,但不能长时间保持视觉搜索的高效状态。而图形化符号可以高效地传递信息,将用户的注意力引导到重要的目标上。
可视化的作用体现在多个方面,如揭示想法和关系、形成论点或意见、观察事物演化的趋势、总结或积聚数据、存档和整理、寻求真相和真理、传播知识和探索性数据分析等。
在计算机学科的分类中,利用人眼的感知能力对数据进行交互的可视表达,以增强认知的技术,称为可视化。它将不可见或难以直接显示的数据转化为可感知的图形、符号、颜色、纹理等,来增强数据识别效率,传递有效信息。
如果要同时绘制多个图表,可以给figure() 传递一个整数参数来指定Figure对象的序号,如果序号所指定的Figure对象已经存在,只需要让它成为当前的Figure对象即可。示例代码如下:
import numpy as np # 创建图表1和图表2 plt.figure(1) plt.figure(2) # 在图表2中创建子图1和子图2 ax1 = plt.subplot(211) ax2 = plt.subplot(212) x = np.linspace(0, 3, 100) for i in xrange(5): # 选择图表1 plt.figure(1) plt.plot(x, np.exp(i*x/3)) plt.sca(ax1) # 选择图表2的子图1 plt.plot(x, np.sin(i*x)) plt.sca(ax2) # 选择图表2的子图2 plt.plot(x, np.cos(i*x))
执行以上程序后,输出的结果如图2-15所示。
图2-15
2.4.3 Matplotlib子画布及loc的使用方法
定义画布的方法如下:
import matplotlib.pyplot as plt Populating the interactive namespace from numpy and matplotlib
Matplotlib的图像都位于Figure画布中,可以使用plt.figure创建一个新画布。
如果要在一个图表中绘制多个子图,可使用subplot,示例代码如下:
# 创建一个新的Figure fig = plt.figure() #不能通过空Figure绘图,必须用add_subplot创建一个或多个subplot ax1 = fig.add_subplot(2, 2, 1) ax2 = fig.add_subplot(2, 2, 2) ax3 = fig.add_subplot(2, 2, 3) from numpy.random import randn # 没有指定具体subplot的绘图命令时,会在最后一个用过的subplot上进行绘制 plt.plot(randn(50).cumsum(), 'k--') _ = ax1.hist(randn(100), bins=25, color='k', alpha=0.4) # 这里加分号可以屏蔽不必要的输出 ax2.scatter(np.arange(50), np.arange(50) + 3*randn(50)) ;
执行以上代码后,输出的结果如图2-16所示。
图2-16
使用上述命令可以绘制一些图例,并可用丰富的数学符号进行标注,还可在一个画布里容纳多个子图形,这些都是很常用的功能。
在一张图中显示多个子图,示例代码如下:
# sub = 子 x = np.arange(-10, 10, 0.1) plt.figure(figsize=(12, 9)) # 1行3列的第1个子图 axes = plt.subplot(1, 3, 1) axes.plot(x, np.sin(x)) # 设置网格颜色、样式、宽度 axes.grid(color='r', linestyle='--', linewidth=2) # 1行3列的第2个子图 axes2 = plt.subplot(1, 3, 2) axes2.plot(x, np.cos(x)) # 设置网格颜色、样式、宽度 axes2.grid(color='g', linestyle='-.', linewidth=2) # 1行3列的第3个子图 axes3 = plt.subplot(1, 3, 3) axes3.plot(x, np.sin(x)) # 设置网格颜色、样式、宽度 axes3.grid(color='b', linestyle=':', linewidth=2)
执行以上代码后,输出的结果如图2-17所示。
图2-17
下面来绘制一个子画布,示例代码如下:
import matplotlib.pyplot as plt import numpy as np def f(t): return np.exp(-t) * np.cos(2 * np.pi * t) # t = np.arange(0, 5, 0.2) t1 = np.arange(0, 5, 0.1) t2 = np.arange(0, 5, 0.02) # plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^') plt.figure(12) plt.subplot(221) plt.plot(t1, f(t1), 'bo', t2, f(t2), 'r--') plt.subplot(222) plt.plot(t2, np.cos(2 * np.pi * t2), 'r--') plt.subplot(212) plt.plot([1, 2, 3, 4, 5], [1, 5, 8, 13, 18]) plt.show()
执行以上代码后,输出的结果如图2-18所示。
图2-18
2.5 Matplotlib绘制K线图的方法
2.5.1 安装财经数据接口包(TuShare)和绘图包(mpl_finance)
TuShare是一个开源的Python财经数据接口包,主要实现对股票等金融数据从数据采集、清洗加工到数据存储的过程,能够为金融分析人员提供快速、整洁和多样的便于分析的数据,极大地减轻他们获取数据来源方面的工作量,使他们更加专注于策略和模型的研究与实现上。由于Python pandas包在金融量化分析中有突出的优势,而且TuShare返回的绝大部分数据格式都是pandas DataFrame类型,所以非常便于用pandas、numPy、Matplotlib进行数据分析和可视化。安装方法如下:
pip install tushare
mpl_finance是Python中用来画蜡烛图、线图的分析工具,目前已经从Matplotlib中独立出来。安装方法如下:
pip install mpl_finance
2.5.2 绘制K线图示例
我们来绘制一张000001平安银行的股价走势图。该示例分为3个部分,步骤如下。
(1)粘贴或输入如下代码:
# 2D绘图包Matplotlib import tushare as ts # 需要先安装 import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec from matplotlib.pylab import date2num import mpl_finance as mpf # 需要先安装 import datetime # 下面输入想要的股票代码并下载数据 wdyx = ts.get_k_data('000001', '2017-01-01') # wdyx.info() # print(wdyx.head())
(2)下载完成平安银行数据后,就可以利用这个数据绘制图形了,示例代码如下:
def date_to_num(dates): num_time = []# 2D绘图包Matplotlib import tushare as ts # 需要先安装 import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec from matplotlib.pylab import date2num import mpl_finance as mpf # 需要先安装 import datetime # 下面输入想要的股票代码并下载数据 wdyx = ts.get_k_data('000001', '2017-01-01') # wdyx.info() # print(wdyx.head()) def date_to_num(dates): num_time = [] for date in dates: date_time = datetime.datetime.strptime(date, '%Y-%m-%d') num_date = date2num(date_time) num_time.append(num_date) return num_time
(3)输入股票代码后,可以将其转换为二维数组,示例代码如下:
# 将Dataframe转换为二维数组 mat_wdyx = wdyx.values num_time = date_to_num(mat_wdyx[:, 0]) mat_wdyx[:, 0] = num_time # 接下来就可以绘制K线图了 # 画布大小 Fig, (ax0, ax1) = plt.subplots(2, sharex=True, figsize=(15, 8)) # 调整两个子画布大小(两种方法) # ax0 = plt.subplot2grid((3, 1), (0, 0), rowspan=2) # ax1 = plt.subplot2grid((3, 1), (2, 0)) gs = GridSpec(3, 1) # 调整上下间隔(两种方法) # gs.update(hspace=0.05) plt.subplots_adjust(hspace=0.05) ax0 = plt.subplot(gs[0:2]) ax1 = plt.subplot(gs[2]) # 在第一个子画布上画K线,在第二个子画布上画量的柱线 mpf.candlestick_ochl(ax0, mat_wdyx, width=1, colorup='r', colordown='g', alpha=1.0) ax0.set_title('000001') ax0.set_ylabel('Price') ax0.grid(True) plt.bar(mat_wdyx[:, 0]-0.4, mat_wdyx[:, 5], width=0.8) ax1.xaxis_date() ax1.set_ylabel('Volume') plt.show() for date in dates: date_time = datetime.datetime.strptime(date, '%Y-%m-%d') num_date = date2num(date_time) num_time.append(num_date) return num_time # 将Dataframe转换为二维数组 mat_wdyx = wdyx.values num_time = date_to_num(mat_wdyx[:, 0]) mat_wdyx[:, 0] = num_time # 接下来就可以绘制K线图了 # 画布大小 fig, (ax0, ax1) = plt.subplots(2, sharex=True, figsize=(15, 8)) # 调整两个子画布大小(两种方法) # ax0 = plt.subplot2grid((3, 1), (0, 0), rowspan=2) # ax1 = plt.subplot2grid((3, 1), (2, 0)) gs = GridSpec(3, 1) # 调整上下间隔(两种方法) # gs.update(hspace=0.05) plt.subplots_adjust(hspace=0.05) ax0 = plt.subplot(gs[0:2]) ax1 = plt.subplot(gs[2]) # 在第一个子画布上画K线,在第二个子画布上画量的柱线 mpf.candlestick_ochl(ax0, mat_wdyx, width=1, colorup='r', colordown='g', alpha=1.0) ax0.set_title('000001') ax0.set_ylabel('Price') ax0.grid(True) plt.bar(mat_wdyx[:, 0]-0.4, mat_wdyx[:, 5], width=0.8) ax1.xaxis_date() ax1.set_ylabel('Volume') plt.show()
按Ctrl+Enter快捷键运行以上程序,输出的结果如图2-19所示。
图2-19